@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
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
export type UserProfileQuestionId =
|
|
2
|
+
| 'preferred_name'
|
|
3
|
+
| 'pronouns'
|
|
4
|
+
| 'location_timezone'
|
|
5
|
+
| 'work_role'
|
|
6
|
+
| 'interests'
|
|
7
|
+
| 'current_projects'
|
|
8
|
+
| 'goals_next_90_days'
|
|
9
|
+
| 'communication_preferences'
|
|
10
|
+
| 'pet_peeves'
|
|
11
|
+
| 'routines_constraints'
|
|
12
|
+
| 'tools_stack'
|
|
13
|
+
| 'important_people'
|
|
14
|
+
| 'anything_else';
|
|
15
|
+
|
|
16
|
+
export type UserProfileQuestion = {
|
|
17
|
+
id: UserProfileQuestionId;
|
|
18
|
+
step: number;
|
|
19
|
+
step_title: string;
|
|
20
|
+
label: string;
|
|
21
|
+
prompt: string;
|
|
22
|
+
description: string;
|
|
23
|
+
placeholder?: string;
|
|
24
|
+
multiline?: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type UserProfileRecord = {
|
|
28
|
+
version: 1;
|
|
29
|
+
answers: Partial<Record<UserProfileQuestionId, string>>;
|
|
30
|
+
created_at: number;
|
|
31
|
+
updated_at: number;
|
|
32
|
+
completed_at: number | null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const USER_PROFILE_SETTING_KEY = 'user.profile.v1';
|
|
36
|
+
|
|
37
|
+
export const USER_PROFILE_QUESTIONS: UserProfileQuestion[] = [
|
|
38
|
+
{
|
|
39
|
+
id: 'preferred_name',
|
|
40
|
+
step: 1,
|
|
41
|
+
step_title: 'Identity',
|
|
42
|
+
label: 'Preferred Name',
|
|
43
|
+
prompt: 'What should JARVIS call you?',
|
|
44
|
+
description: 'Use the name you actually want the assistant to use in conversation.',
|
|
45
|
+
placeholder: 'e.g. Alex',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'pronouns',
|
|
49
|
+
step: 1,
|
|
50
|
+
step_title: 'Identity',
|
|
51
|
+
label: 'Pronouns',
|
|
52
|
+
prompt: 'What pronouns do you want JARVIS to use, if any?',
|
|
53
|
+
description: 'Optional, but useful for natural and respectful replies.',
|
|
54
|
+
placeholder: 'e.g. she/her, he/him, they/them',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'location_timezone',
|
|
58
|
+
step: 1,
|
|
59
|
+
step_title: 'Identity',
|
|
60
|
+
label: 'Location / Timezone',
|
|
61
|
+
prompt: 'What location or timezone should JARVIS keep in mind?',
|
|
62
|
+
description: 'This helps with scheduling, recommendations, and time references.',
|
|
63
|
+
placeholder: 'e.g. Miami, FL / America/New_York',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'work_role',
|
|
67
|
+
step: 1,
|
|
68
|
+
step_title: 'Identity',
|
|
69
|
+
label: 'Work / Role',
|
|
70
|
+
prompt: 'What do you do, or what roles do you usually operate in?',
|
|
71
|
+
description: 'Career, studies, side hustles, and the kinds of responsibilities you handle.',
|
|
72
|
+
placeholder: 'e.g. founder, student, engineer, creator',
|
|
73
|
+
multiline: true,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'interests',
|
|
77
|
+
step: 2,
|
|
78
|
+
step_title: 'Interests',
|
|
79
|
+
label: 'Interests',
|
|
80
|
+
prompt: 'What are you genuinely interested in?',
|
|
81
|
+
description: 'Topics, hobbies, communities, subjects, and rabbit holes you care about.',
|
|
82
|
+
placeholder: 'e.g. AI, fitness, cars, anime, startups',
|
|
83
|
+
multiline: true,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'current_projects',
|
|
87
|
+
step: 2,
|
|
88
|
+
step_title: 'Interests',
|
|
89
|
+
label: 'Current Projects',
|
|
90
|
+
prompt: 'What are you actively working on right now?',
|
|
91
|
+
description: 'Projects, businesses, classes, routines, or personal efforts already in motion.',
|
|
92
|
+
placeholder: 'What is already on your plate?',
|
|
93
|
+
multiline: true,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'goals_next_90_days',
|
|
97
|
+
step: 2,
|
|
98
|
+
step_title: 'Interests',
|
|
99
|
+
label: 'Goals',
|
|
100
|
+
prompt: 'What do you want to accomplish over the next 30 to 90 days?',
|
|
101
|
+
description: 'Short-term outcomes that JARVIS should optimize around.',
|
|
102
|
+
placeholder: 'What should JARVIS help push forward?',
|
|
103
|
+
multiline: true,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'communication_preferences',
|
|
107
|
+
step: 3,
|
|
108
|
+
step_title: 'Working Style',
|
|
109
|
+
label: 'Communication Preferences',
|
|
110
|
+
prompt: 'How do you want JARVIS to communicate with you?',
|
|
111
|
+
description: 'Tone, directness, detail level, structure, reminders, and how much pushback you want.',
|
|
112
|
+
placeholder: 'e.g. blunt, concise, actionable, no fluff',
|
|
113
|
+
multiline: true,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'pet_peeves',
|
|
117
|
+
step: 3,
|
|
118
|
+
step_title: 'Working Style',
|
|
119
|
+
label: 'Pet Peeves',
|
|
120
|
+
prompt: 'What annoys you or wastes your time?',
|
|
121
|
+
description: 'Patterns to avoid in planning, writing, or collaboration.',
|
|
122
|
+
placeholder: 'What should JARVIS not do?',
|
|
123
|
+
multiline: true,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'routines_constraints',
|
|
127
|
+
step: 3,
|
|
128
|
+
step_title: 'Working Style',
|
|
129
|
+
label: 'Routines & Constraints',
|
|
130
|
+
prompt: 'What routines, limits, or constraints should JARVIS know?',
|
|
131
|
+
description: 'Schedule constraints, health habits, budget limits, availability, or boundaries.',
|
|
132
|
+
placeholder: 'Anything that should shape reminders or recommendations',
|
|
133
|
+
multiline: true,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: 'tools_stack',
|
|
137
|
+
step: 4,
|
|
138
|
+
step_title: 'Context',
|
|
139
|
+
label: 'Tools & Stack',
|
|
140
|
+
prompt: 'What tools, apps, or technical stack do you use most?',
|
|
141
|
+
description: 'Software, devices, languages, frameworks, and workflows JARVIS should assume.',
|
|
142
|
+
placeholder: 'e.g. GitHub, Bun, React, Notion, Telegram, Windows',
|
|
143
|
+
multiline: true,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'important_people',
|
|
147
|
+
step: 4,
|
|
148
|
+
step_title: 'Context',
|
|
149
|
+
label: 'Important People',
|
|
150
|
+
prompt: 'Who are the important people, teams, or audiences around you?',
|
|
151
|
+
description: 'Managers, cofounders, clients, family, friends, or communities that matter in your context.',
|
|
152
|
+
placeholder: 'Who should JARVIS keep in mind?',
|
|
153
|
+
multiline: true,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: 'anything_else',
|
|
157
|
+
step: 4,
|
|
158
|
+
step_title: 'Context',
|
|
159
|
+
label: 'Anything Else',
|
|
160
|
+
prompt: 'What else should JARVIS know to be useful from day one?',
|
|
161
|
+
description: 'Any extra context that does not fit the earlier prompts.',
|
|
162
|
+
placeholder: 'Anything important you want carried forward',
|
|
163
|
+
multiline: true,
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
export function createEmptyUserProfile(): UserProfileRecord {
|
|
168
|
+
const now = Date.now();
|
|
169
|
+
return {
|
|
170
|
+
version: 1,
|
|
171
|
+
answers: {},
|
|
172
|
+
created_at: now,
|
|
173
|
+
updated_at: now,
|
|
174
|
+
completed_at: null,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function normalizeUserProfileAnswers(
|
|
179
|
+
input: Record<string, unknown>,
|
|
180
|
+
): Partial<Record<UserProfileQuestionId, string>> {
|
|
181
|
+
const answers: Partial<Record<UserProfileQuestionId, string>> = {};
|
|
182
|
+
|
|
183
|
+
for (const question of USER_PROFILE_QUESTIONS) {
|
|
184
|
+
const raw = input[question.id];
|
|
185
|
+
if (typeof raw !== 'string') continue;
|
|
186
|
+
const value = raw.trim();
|
|
187
|
+
if (!value) continue;
|
|
188
|
+
answers[question.id] = value;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return answers;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function countAnsweredUserProfileQuestions(profile: UserProfileRecord | null): number {
|
|
195
|
+
if (!profile) return 0;
|
|
196
|
+
return USER_PROFILE_QUESTIONS.filter((question) => Boolean(profile.answers[question.id]?.trim())).length;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function hasUserProfile(profile: UserProfileRecord | null): boolean {
|
|
200
|
+
return countAnsweredUserProfileQuestions(profile) > 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function formatUserProfileForPrompt(profile: UserProfileRecord | null): string | undefined {
|
|
204
|
+
if (!profile) return undefined;
|
|
205
|
+
|
|
206
|
+
const lines: string[] = [];
|
|
207
|
+
for (const question of USER_PROFILE_QUESTIONS) {
|
|
208
|
+
const answer = profile.answers[question.id]?.trim();
|
|
209
|
+
if (!answer) continue;
|
|
210
|
+
lines.push(`- ${question.label}: |`);
|
|
211
|
+
lines.push(indentPromptValue(answer));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (lines.length === 0) return undefined;
|
|
215
|
+
return lines.join('\n');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function indentPromptValue(value: string): string {
|
|
219
|
+
return value
|
|
220
|
+
.replace(/\r\n/g, '\n')
|
|
221
|
+
.split('\n')
|
|
222
|
+
.map((line) => ` ${line}`)
|
|
223
|
+
.join('\n');
|
|
224
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Vault Module
|
|
2
|
+
|
|
3
|
+
Knowledge graph storage and extraction system for J.A.R.V.I.S.
|
|
4
|
+
|
|
5
|
+
## Modules
|
|
6
|
+
|
|
7
|
+
- **`schema.ts`**: Database schema and initialization
|
|
8
|
+
- **`entities.ts`**: Entity CRUD operations
|
|
9
|
+
- **`facts.ts`**: Fact storage and queries
|
|
10
|
+
- **`relationships.ts`**: Relationship management
|
|
11
|
+
- **`commitments.ts`**: Promise and task tracking
|
|
12
|
+
- **`observations.ts`**: Raw event storage
|
|
13
|
+
- **`vectors.ts`**: Embedding storage for semantic search
|
|
14
|
+
- **`extractor.ts`**: LLM-powered knowledge extraction
|
|
15
|
+
- **`index.ts`**: Public API exports
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### Initialize Database
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { initDatabase } from '@/vault';
|
|
23
|
+
|
|
24
|
+
initDatabase('./data/jarvis.db');
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Store Knowledge Manually
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { createEntity, createFact, createRelationship } from '@/vault';
|
|
31
|
+
|
|
32
|
+
// Create entity
|
|
33
|
+
const anna = createEntity('person', 'Anna', {
|
|
34
|
+
role: 'software engineer',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Add fact
|
|
38
|
+
createFact(anna.id, 'birthday_is', 'March 15', {
|
|
39
|
+
confidence: 1.0,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Create relationship
|
|
43
|
+
const google = createEntity('tool', 'Google');
|
|
44
|
+
createRelationship(anna.id, google.id, 'works_at');
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Extract Knowledge with LLM
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { extractAndStore } from '@/vault';
|
|
51
|
+
import { AnthropicProvider } from '@/llm/anthropic';
|
|
52
|
+
|
|
53
|
+
const llm = new AnthropicProvider(process.env.ANTHROPIC_API_KEY);
|
|
54
|
+
|
|
55
|
+
const result = await extractAndStore(
|
|
56
|
+
"Anna's birthday is March 15th",
|
|
57
|
+
"I'll remember that!",
|
|
58
|
+
llm
|
|
59
|
+
);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Query Knowledge
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { findEntities, findFacts, findCommitments } from '@/vault';
|
|
66
|
+
|
|
67
|
+
// Find people
|
|
68
|
+
const people = findEntities({ type: 'person' });
|
|
69
|
+
|
|
70
|
+
// Find facts about Anna
|
|
71
|
+
const facts = findFacts({ subject_id: anna.id });
|
|
72
|
+
|
|
73
|
+
// Find pending commitments
|
|
74
|
+
const pending = findCommitments({ status: 'pending' });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Documentation
|
|
78
|
+
|
|
79
|
+
- **Vault Extractor**: [docs/VAULT_EXTRACTOR.md](~/jarvis/docs/VAULT_EXTRACTOR.md)
|
|
80
|
+
- **Entity Types**: person, project, tool, place, concept, event
|
|
81
|
+
- **Commitment Statuses**: pending, active, completed, failed, escalated
|
|
82
|
+
|
|
83
|
+
## Testing
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Test extractor
|
|
87
|
+
bun test src/vault/extractor.test.ts
|
|
88
|
+
|
|
89
|
+
# Test entire vault (if you have other tests)
|
|
90
|
+
bun test src/vault/**/*.test.ts
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Demo
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
bun run examples/extractor-demo.ts
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Database Schema
|
|
100
|
+
|
|
101
|
+
SQLite database with the following tables:
|
|
102
|
+
- `entities`: People, projects, tools, places, concepts, events
|
|
103
|
+
- `facts`: Atomic knowledge with confidence scores
|
|
104
|
+
- `relationships`: Typed edges between entities
|
|
105
|
+
- `commitments`: Promises and tasks
|
|
106
|
+
- `observations`: Raw events
|
|
107
|
+
- `vectors`: Embeddings for semantic search
|
|
108
|
+
- `agent_messages`: Inter-agent communication
|
|
109
|
+
- `personality_state`: Personality model storage
|
|
110
|
+
- `conversations`: Conversation tracking
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault — Awareness CRUD
|
|
3
|
+
*
|
|
4
|
+
* Database operations for screen_captures, awareness_sessions, and awareness_suggestions tables.
|
|
5
|
+
* Follows the same patterns as observations.ts and commitments.ts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getDb, generateId } from './schema.ts';
|
|
9
|
+
import type {
|
|
10
|
+
ScreenCaptureRow,
|
|
11
|
+
SessionRow,
|
|
12
|
+
SuggestionRow,
|
|
13
|
+
SuggestionType,
|
|
14
|
+
AppUsageStat,
|
|
15
|
+
} from '../awareness/types.ts';
|
|
16
|
+
|
|
17
|
+
// ── Screen Captures ──
|
|
18
|
+
|
|
19
|
+
export function createCapture(data: {
|
|
20
|
+
timestamp: number;
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
imagePath?: string;
|
|
23
|
+
thumbnailPath?: string;
|
|
24
|
+
pixelChangePct: number;
|
|
25
|
+
ocrText?: string;
|
|
26
|
+
appName?: string;
|
|
27
|
+
windowTitle?: string;
|
|
28
|
+
url?: string;
|
|
29
|
+
filePath?: string;
|
|
30
|
+
retentionTier?: 'full' | 'key_moment' | 'metadata_only';
|
|
31
|
+
}): ScreenCaptureRow {
|
|
32
|
+
const db = getDb();
|
|
33
|
+
const id = generateId();
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
|
|
36
|
+
db.prepare(`
|
|
37
|
+
INSERT INTO screen_captures
|
|
38
|
+
(id, timestamp, session_id, image_path, thumbnail_path, pixel_change_pct,
|
|
39
|
+
ocr_text, app_name, window_title, url, file_path, retention_tier, created_at)
|
|
40
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
41
|
+
`).run(
|
|
42
|
+
id,
|
|
43
|
+
data.timestamp,
|
|
44
|
+
data.sessionId ?? null,
|
|
45
|
+
data.imagePath ?? null,
|
|
46
|
+
data.thumbnailPath ?? null,
|
|
47
|
+
data.pixelChangePct,
|
|
48
|
+
data.ocrText ?? null,
|
|
49
|
+
data.appName ?? null,
|
|
50
|
+
data.windowTitle ?? null,
|
|
51
|
+
data.url ?? null,
|
|
52
|
+
data.filePath ?? null,
|
|
53
|
+
data.retentionTier ?? 'full',
|
|
54
|
+
now,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
id,
|
|
59
|
+
timestamp: data.timestamp,
|
|
60
|
+
session_id: data.sessionId ?? null,
|
|
61
|
+
image_path: data.imagePath ?? null,
|
|
62
|
+
thumbnail_path: data.thumbnailPath ?? null,
|
|
63
|
+
pixel_change_pct: data.pixelChangePct,
|
|
64
|
+
ocr_text: data.ocrText ?? null,
|
|
65
|
+
app_name: data.appName ?? null,
|
|
66
|
+
window_title: data.windowTitle ?? null,
|
|
67
|
+
url: data.url ?? null,
|
|
68
|
+
file_path: data.filePath ?? null,
|
|
69
|
+
retention_tier: data.retentionTier ?? 'full',
|
|
70
|
+
created_at: now,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getCapture(id: string): ScreenCaptureRow | null {
|
|
75
|
+
const db = getDb();
|
|
76
|
+
return db.prepare('SELECT * FROM screen_captures WHERE id = ?').get(id) as ScreenCaptureRow | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getRecentCaptures(limit: number = 50, appName?: string): ScreenCaptureRow[] {
|
|
80
|
+
const db = getDb();
|
|
81
|
+
if (appName) {
|
|
82
|
+
return db.prepare(
|
|
83
|
+
'SELECT * FROM screen_captures WHERE app_name = ? ORDER BY timestamp DESC LIMIT ?'
|
|
84
|
+
).all(appName, limit) as ScreenCaptureRow[];
|
|
85
|
+
}
|
|
86
|
+
return db.prepare(
|
|
87
|
+
'SELECT * FROM screen_captures ORDER BY timestamp DESC LIMIT ?'
|
|
88
|
+
).all(limit) as ScreenCaptureRow[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function getCapturesInRange(startTime: number, endTime: number): ScreenCaptureRow[] {
|
|
92
|
+
const db = getDb();
|
|
93
|
+
return db.prepare(
|
|
94
|
+
'SELECT * FROM screen_captures WHERE timestamp >= ? AND timestamp <= ? ORDER BY timestamp ASC'
|
|
95
|
+
).all(startTime, endTime) as ScreenCaptureRow[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function getAppUsageStats(startTime: number, endTime: number): AppUsageStat[] {
|
|
99
|
+
const db = getDb();
|
|
100
|
+
const rows = db.prepare(`
|
|
101
|
+
SELECT app_name, COUNT(*) as capture_count
|
|
102
|
+
FROM screen_captures
|
|
103
|
+
WHERE timestamp >= ? AND timestamp <= ? AND app_name IS NOT NULL
|
|
104
|
+
GROUP BY app_name
|
|
105
|
+
ORDER BY capture_count DESC
|
|
106
|
+
`).all(startTime, endTime) as Array<{ app_name: string; capture_count: number }>;
|
|
107
|
+
|
|
108
|
+
const totalCaptures = rows.reduce((sum, r) => sum + r.capture_count, 0);
|
|
109
|
+
|
|
110
|
+
return rows.map(r => ({
|
|
111
|
+
app: r.app_name,
|
|
112
|
+
captureCount: r.capture_count,
|
|
113
|
+
minutes: Math.round((r.capture_count * 7) / 60), // ~7s per capture
|
|
114
|
+
percentage: totalCaptures > 0 ? Math.round((r.capture_count / totalCaptures) * 100) : 0,
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function getCaptureCountSince(timestamp: number): number {
|
|
119
|
+
const db = getDb();
|
|
120
|
+
const row = db.prepare(
|
|
121
|
+
'SELECT COUNT(*) as count FROM screen_captures WHERE timestamp >= ?'
|
|
122
|
+
).get(timestamp) as { count: number };
|
|
123
|
+
return row.count;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function updateCaptureRetention(id: string, tier: 'full' | 'key_moment' | 'metadata_only'): void {
|
|
127
|
+
const db = getDb();
|
|
128
|
+
db.prepare('UPDATE screen_captures SET retention_tier = ? WHERE id = ?').run(tier, id);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function deleteCapturesBefore(timestamp: number, retentionTier: string): number {
|
|
132
|
+
const db = getDb();
|
|
133
|
+
const result = db.prepare(
|
|
134
|
+
'DELETE FROM screen_captures WHERE timestamp < ? AND retention_tier = ?'
|
|
135
|
+
).run(timestamp, retentionTier);
|
|
136
|
+
return result.changes;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function updateCaptureOcrText(id: string, ocrText: string): void {
|
|
140
|
+
const db = getDb();
|
|
141
|
+
db.prepare('UPDATE screen_captures SET ocr_text = ? WHERE id = ?').run(ocrText, id);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function getCapturesForSession(sessionId: string, limit: number = 50): ScreenCaptureRow[] {
|
|
145
|
+
const db = getDb();
|
|
146
|
+
return db.prepare(
|
|
147
|
+
'SELECT * FROM screen_captures WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?'
|
|
148
|
+
).all(sessionId, limit) as ScreenCaptureRow[];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Awareness Sessions ──
|
|
152
|
+
|
|
153
|
+
export function createSession(data: {
|
|
154
|
+
startedAt: number;
|
|
155
|
+
apps?: string[];
|
|
156
|
+
projectContext?: string;
|
|
157
|
+
}): SessionRow {
|
|
158
|
+
const db = getDb();
|
|
159
|
+
const id = generateId();
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
|
|
162
|
+
db.prepare(`
|
|
163
|
+
INSERT INTO awareness_sessions
|
|
164
|
+
(id, started_at, ended_at, topic, apps, project_context, action_types, entity_links, summary, capture_count, created_at)
|
|
165
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
166
|
+
`).run(
|
|
167
|
+
id,
|
|
168
|
+
data.startedAt,
|
|
169
|
+
null,
|
|
170
|
+
null,
|
|
171
|
+
JSON.stringify(data.apps ?? []),
|
|
172
|
+
data.projectContext ?? null,
|
|
173
|
+
JSON.stringify([]),
|
|
174
|
+
JSON.stringify([]),
|
|
175
|
+
null,
|
|
176
|
+
0,
|
|
177
|
+
now,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
id,
|
|
182
|
+
started_at: data.startedAt,
|
|
183
|
+
ended_at: null,
|
|
184
|
+
topic: null,
|
|
185
|
+
apps: JSON.stringify(data.apps ?? []),
|
|
186
|
+
project_context: data.projectContext ?? null,
|
|
187
|
+
action_types: JSON.stringify([]),
|
|
188
|
+
entity_links: JSON.stringify([]),
|
|
189
|
+
summary: null,
|
|
190
|
+
capture_count: 0,
|
|
191
|
+
created_at: now,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function getSession(id: string): SessionRow | null {
|
|
196
|
+
const db = getDb();
|
|
197
|
+
return db.prepare('SELECT * FROM awareness_sessions WHERE id = ?').get(id) as SessionRow | null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function updateSession(id: string, updates: Partial<{
|
|
201
|
+
ended_at: number | null;
|
|
202
|
+
topic: string | null;
|
|
203
|
+
apps: string[];
|
|
204
|
+
project_context: string | null;
|
|
205
|
+
action_types: string[];
|
|
206
|
+
entity_links: string[];
|
|
207
|
+
summary: string | null;
|
|
208
|
+
capture_count: number;
|
|
209
|
+
}>): void {
|
|
210
|
+
const db = getDb();
|
|
211
|
+
const setClauses: string[] = [];
|
|
212
|
+
const params: unknown[] = [];
|
|
213
|
+
|
|
214
|
+
if (updates.ended_at !== undefined) { setClauses.push('ended_at = ?'); params.push(updates.ended_at); }
|
|
215
|
+
if (updates.topic !== undefined) { setClauses.push('topic = ?'); params.push(updates.topic); }
|
|
216
|
+
if (updates.apps !== undefined) { setClauses.push('apps = ?'); params.push(JSON.stringify(updates.apps)); }
|
|
217
|
+
if (updates.project_context !== undefined) { setClauses.push('project_context = ?'); params.push(updates.project_context); }
|
|
218
|
+
if (updates.action_types !== undefined) { setClauses.push('action_types = ?'); params.push(JSON.stringify(updates.action_types)); }
|
|
219
|
+
if (updates.entity_links !== undefined) { setClauses.push('entity_links = ?'); params.push(JSON.stringify(updates.entity_links)); }
|
|
220
|
+
if (updates.summary !== undefined) { setClauses.push('summary = ?'); params.push(updates.summary); }
|
|
221
|
+
if (updates.capture_count !== undefined) { setClauses.push('capture_count = ?'); params.push(updates.capture_count); }
|
|
222
|
+
|
|
223
|
+
if (setClauses.length === 0) return;
|
|
224
|
+
|
|
225
|
+
params.push(id);
|
|
226
|
+
db.prepare(`UPDATE awareness_sessions SET ${setClauses.join(', ')} WHERE id = ?`).run(...params as any[]);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function endSession(id: string, summary?: string): void {
|
|
230
|
+
const db = getDb();
|
|
231
|
+
db.prepare(
|
|
232
|
+
'UPDATE awareness_sessions SET ended_at = ?, summary = ? WHERE id = ?'
|
|
233
|
+
).run(Date.now(), summary ?? null, id);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function getRecentSessions(limit: number = 20): SessionRow[] {
|
|
237
|
+
const db = getDb();
|
|
238
|
+
return db.prepare(
|
|
239
|
+
'SELECT * FROM awareness_sessions ORDER BY started_at DESC LIMIT ?'
|
|
240
|
+
).all(limit) as SessionRow[];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function incrementSessionCaptureCount(id: string): void {
|
|
244
|
+
const db = getDb();
|
|
245
|
+
db.prepare(
|
|
246
|
+
'UPDATE awareness_sessions SET capture_count = capture_count + 1 WHERE id = ?'
|
|
247
|
+
).run(id);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Awareness Suggestions ──
|
|
251
|
+
|
|
252
|
+
export function createSuggestion(data: {
|
|
253
|
+
type: SuggestionType;
|
|
254
|
+
triggerCaptureId?: string;
|
|
255
|
+
title: string;
|
|
256
|
+
body: string;
|
|
257
|
+
context?: Record<string, unknown>;
|
|
258
|
+
}): SuggestionRow {
|
|
259
|
+
const db = getDb();
|
|
260
|
+
const id = generateId();
|
|
261
|
+
const now = Date.now();
|
|
262
|
+
|
|
263
|
+
db.prepare(`
|
|
264
|
+
INSERT INTO awareness_suggestions
|
|
265
|
+
(id, type, trigger_capture_id, title, body, context, delivered, delivered_at, delivery_channel, dismissed, acted_on, created_at)
|
|
266
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
267
|
+
`).run(
|
|
268
|
+
id,
|
|
269
|
+
data.type,
|
|
270
|
+
data.triggerCaptureId ?? null,
|
|
271
|
+
data.title,
|
|
272
|
+
data.body,
|
|
273
|
+
data.context ? JSON.stringify(data.context) : null,
|
|
274
|
+
0, null, null, 0, 0,
|
|
275
|
+
now,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
id,
|
|
280
|
+
type: data.type,
|
|
281
|
+
trigger_capture_id: data.triggerCaptureId ?? null,
|
|
282
|
+
title: data.title,
|
|
283
|
+
body: data.body,
|
|
284
|
+
context: data.context ? JSON.stringify(data.context) : null,
|
|
285
|
+
delivered: 0,
|
|
286
|
+
delivered_at: null,
|
|
287
|
+
delivery_channel: null,
|
|
288
|
+
dismissed: 0,
|
|
289
|
+
acted_on: 0,
|
|
290
|
+
created_at: now,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function markSuggestionDelivered(id: string, channel: string): void {
|
|
295
|
+
const db = getDb();
|
|
296
|
+
db.prepare(
|
|
297
|
+
'UPDATE awareness_suggestions SET delivered = 1, delivered_at = ?, delivery_channel = ? WHERE id = ?'
|
|
298
|
+
).run(Date.now(), channel, id);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function markSuggestionDismissed(id: string): void {
|
|
302
|
+
const db = getDb();
|
|
303
|
+
db.prepare('UPDATE awareness_suggestions SET dismissed = 1 WHERE id = ?').run(id);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function markSuggestionActedOn(id: string): void {
|
|
307
|
+
const db = getDb();
|
|
308
|
+
db.prepare('UPDATE awareness_suggestions SET acted_on = 1 WHERE id = ?').run(id);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function getRecentSuggestions(limit: number = 20, type?: SuggestionType): SuggestionRow[] {
|
|
312
|
+
const db = getDb();
|
|
313
|
+
if (type) {
|
|
314
|
+
return db.prepare(
|
|
315
|
+
'SELECT * FROM awareness_suggestions WHERE type = ? ORDER BY created_at DESC LIMIT ?'
|
|
316
|
+
).all(type, limit) as SuggestionRow[];
|
|
317
|
+
}
|
|
318
|
+
return db.prepare(
|
|
319
|
+
'SELECT * FROM awareness_suggestions ORDER BY created_at DESC LIMIT ?'
|
|
320
|
+
).all(limit) as SuggestionRow[];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function getSuggestionCountSince(timestamp: number): number {
|
|
324
|
+
const db = getDb();
|
|
325
|
+
const row = db.prepare(
|
|
326
|
+
'SELECT COUNT(*) as count FROM awareness_suggestions WHERE created_at >= ?'
|
|
327
|
+
).get(timestamp) as { count: number };
|
|
328
|
+
return row.count;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function getSuggestionStats(startTime: number, endTime: number): { total: number; actedOn: number } {
|
|
332
|
+
const db = getDb();
|
|
333
|
+
const row = db.prepare(`
|
|
334
|
+
SELECT
|
|
335
|
+
COUNT(*) as total,
|
|
336
|
+
SUM(CASE WHEN acted_on = 1 THEN 1 ELSE 0 END) as acted_on
|
|
337
|
+
FROM awareness_suggestions
|
|
338
|
+
WHERE created_at >= ? AND created_at <= ?
|
|
339
|
+
`).get(startTime, endTime) as { total: number; acted_on: number };
|
|
340
|
+
return { total: row.total, actedOn: row.acted_on ?? 0 };
|
|
341
|
+
}
|