@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,299 @@
|
|
|
1
|
+
import { getDb, generateId } from './schema.ts';
|
|
2
|
+
|
|
3
|
+
export type CommitmentPriority = 'low' | 'normal' | 'high' | 'critical';
|
|
4
|
+
export type CommitmentStatus = 'pending' | 'active' | 'completed' | 'failed' | 'escalated';
|
|
5
|
+
|
|
6
|
+
export type RetryPolicy = {
|
|
7
|
+
max_retries: number;
|
|
8
|
+
interval_ms: number;
|
|
9
|
+
escalate_after: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type Commitment = {
|
|
13
|
+
id: string;
|
|
14
|
+
what: string;
|
|
15
|
+
when_due: number | null;
|
|
16
|
+
context: string | null;
|
|
17
|
+
priority: CommitmentPriority;
|
|
18
|
+
status: CommitmentStatus;
|
|
19
|
+
retry_policy: RetryPolicy | null;
|
|
20
|
+
created_from: string | null;
|
|
21
|
+
assigned_to: string | null;
|
|
22
|
+
created_at: number;
|
|
23
|
+
completed_at: number | null;
|
|
24
|
+
result: string | null;
|
|
25
|
+
sort_order: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type CommitmentRow = {
|
|
29
|
+
id: string;
|
|
30
|
+
what: string;
|
|
31
|
+
when_due: number | null;
|
|
32
|
+
context: string | null;
|
|
33
|
+
priority: CommitmentPriority;
|
|
34
|
+
status: CommitmentStatus;
|
|
35
|
+
retry_policy: string | null;
|
|
36
|
+
created_from: string | null;
|
|
37
|
+
assigned_to: string | null;
|
|
38
|
+
created_at: number;
|
|
39
|
+
completed_at: number | null;
|
|
40
|
+
result: string | null;
|
|
41
|
+
sort_order: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse commitment row from database, deserializing JSON fields
|
|
46
|
+
*/
|
|
47
|
+
function parseCommitment(row: CommitmentRow): Commitment {
|
|
48
|
+
return {
|
|
49
|
+
...row,
|
|
50
|
+
retry_policy: row.retry_policy ? JSON.parse(row.retry_policy) : null,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create a new commitment
|
|
56
|
+
*/
|
|
57
|
+
export function createCommitment(
|
|
58
|
+
what: string,
|
|
59
|
+
opts?: {
|
|
60
|
+
when_due?: number;
|
|
61
|
+
context?: string;
|
|
62
|
+
priority?: CommitmentPriority;
|
|
63
|
+
retry_policy?: RetryPolicy;
|
|
64
|
+
created_from?: string;
|
|
65
|
+
assigned_to?: string;
|
|
66
|
+
}
|
|
67
|
+
): Commitment {
|
|
68
|
+
const db = getDb();
|
|
69
|
+
const id = generateId();
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
const priority = opts?.priority ?? 'normal';
|
|
72
|
+
|
|
73
|
+
const stmt = db.prepare(
|
|
74
|
+
'INSERT INTO commitments (id, what, when_due, context, priority, status, retry_policy, created_from, assigned_to, created_at, completed_at, result) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
stmt.run(
|
|
78
|
+
id,
|
|
79
|
+
what,
|
|
80
|
+
opts?.when_due ?? null,
|
|
81
|
+
opts?.context ?? null,
|
|
82
|
+
priority,
|
|
83
|
+
'pending',
|
|
84
|
+
opts?.retry_policy ? JSON.stringify(opts.retry_policy) : null,
|
|
85
|
+
opts?.created_from ?? null,
|
|
86
|
+
opts?.assigned_to ?? null,
|
|
87
|
+
now,
|
|
88
|
+
null,
|
|
89
|
+
null
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
id,
|
|
94
|
+
what,
|
|
95
|
+
when_due: opts?.when_due ?? null,
|
|
96
|
+
context: opts?.context ?? null,
|
|
97
|
+
priority,
|
|
98
|
+
status: 'pending',
|
|
99
|
+
retry_policy: opts?.retry_policy ?? null,
|
|
100
|
+
created_from: opts?.created_from ?? null,
|
|
101
|
+
assigned_to: opts?.assigned_to ?? null,
|
|
102
|
+
created_at: now,
|
|
103
|
+
completed_at: null,
|
|
104
|
+
result: null,
|
|
105
|
+
sort_order: 0,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get a commitment by ID
|
|
111
|
+
*/
|
|
112
|
+
export function getCommitment(id: string): Commitment | null {
|
|
113
|
+
const db = getDb();
|
|
114
|
+
const stmt = db.prepare('SELECT * FROM commitments WHERE id = ?');
|
|
115
|
+
const row = stmt.get(id) as CommitmentRow | null;
|
|
116
|
+
|
|
117
|
+
if (!row) return null;
|
|
118
|
+
|
|
119
|
+
return parseCommitment(row);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Find commitments matching query criteria
|
|
124
|
+
*/
|
|
125
|
+
export function findCommitments(query: {
|
|
126
|
+
status?: CommitmentStatus;
|
|
127
|
+
priority?: CommitmentPriority;
|
|
128
|
+
assigned_to?: string;
|
|
129
|
+
overdue?: boolean;
|
|
130
|
+
}): Commitment[] {
|
|
131
|
+
const db = getDb();
|
|
132
|
+
const conditions: string[] = [];
|
|
133
|
+
const params: unknown[] = [];
|
|
134
|
+
|
|
135
|
+
if (query.status) {
|
|
136
|
+
conditions.push('status = ?');
|
|
137
|
+
params.push(query.status);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (query.priority) {
|
|
141
|
+
conditions.push('priority = ?');
|
|
142
|
+
params.push(query.priority);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (query.assigned_to) {
|
|
146
|
+
conditions.push('assigned_to = ?');
|
|
147
|
+
params.push(query.assigned_to);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (query.overdue) {
|
|
151
|
+
conditions.push('when_due IS NOT NULL AND when_due <= ?');
|
|
152
|
+
params.push(Date.now());
|
|
153
|
+
conditions.push("status IN ('pending', 'active')");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
157
|
+
const stmt = db.prepare(`SELECT * FROM commitments ${where} ORDER BY sort_order ASC, created_at DESC`);
|
|
158
|
+
const rows = stmt.all(...params as any[]) as CommitmentRow[];
|
|
159
|
+
|
|
160
|
+
return rows.map(parseCommitment);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get upcoming commitments, ordered by due date
|
|
165
|
+
*/
|
|
166
|
+
export function getUpcoming(limit: number = 10): Commitment[] {
|
|
167
|
+
const db = getDb();
|
|
168
|
+
const stmt = db.prepare(
|
|
169
|
+
"SELECT * FROM commitments WHERE status IN ('pending', 'active') AND when_due IS NOT NULL ORDER BY when_due ASC LIMIT ?"
|
|
170
|
+
);
|
|
171
|
+
const rows = stmt.all(limit) as CommitmentRow[];
|
|
172
|
+
|
|
173
|
+
return rows.map(parseCommitment);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Mark a commitment as completed
|
|
178
|
+
*/
|
|
179
|
+
export function completeCommitment(id: string, result?: string): Commitment | null {
|
|
180
|
+
const db = getDb();
|
|
181
|
+
const commitment = getCommitment(id);
|
|
182
|
+
if (!commitment) return null;
|
|
183
|
+
|
|
184
|
+
const stmt = db.prepare(
|
|
185
|
+
'UPDATE commitments SET status = ?, completed_at = ?, result = ? WHERE id = ?'
|
|
186
|
+
);
|
|
187
|
+
stmt.run('completed', Date.now(), result ?? null, id);
|
|
188
|
+
|
|
189
|
+
return getCommitment(id);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Mark a commitment as failed
|
|
194
|
+
*/
|
|
195
|
+
export function failCommitment(id: string, reason?: string): Commitment | null {
|
|
196
|
+
const db = getDb();
|
|
197
|
+
const commitment = getCommitment(id);
|
|
198
|
+
if (!commitment) return null;
|
|
199
|
+
|
|
200
|
+
const stmt = db.prepare(
|
|
201
|
+
'UPDATE commitments SET status = ?, completed_at = ?, result = ? WHERE id = ?'
|
|
202
|
+
);
|
|
203
|
+
stmt.run('failed', Date.now(), reason ?? null, id);
|
|
204
|
+
|
|
205
|
+
return getCommitment(id);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Escalate a commitment
|
|
210
|
+
*/
|
|
211
|
+
export function escalateCommitment(id: string): Commitment | null {
|
|
212
|
+
const db = getDb();
|
|
213
|
+
const commitment = getCommitment(id);
|
|
214
|
+
if (!commitment) return null;
|
|
215
|
+
|
|
216
|
+
const stmt = db.prepare('UPDATE commitments SET status = ? WHERE id = ?');
|
|
217
|
+
stmt.run('escalated', id);
|
|
218
|
+
|
|
219
|
+
return getCommitment(id);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get commitments that are currently due
|
|
224
|
+
*/
|
|
225
|
+
export function getDueCommitments(): Commitment[] {
|
|
226
|
+
const db = getDb();
|
|
227
|
+
const now = Date.now();
|
|
228
|
+
const stmt = db.prepare(
|
|
229
|
+
"SELECT * FROM commitments WHERE when_due IS NOT NULL AND when_due <= ? AND status IN ('pending', 'active') ORDER BY when_due ASC"
|
|
230
|
+
);
|
|
231
|
+
const rows = stmt.all(now) as CommitmentRow[];
|
|
232
|
+
|
|
233
|
+
return rows.map(parseCommitment);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Update a commitment's status to any valid status.
|
|
238
|
+
* Sets completed_at for terminal states (completed, failed).
|
|
239
|
+
*/
|
|
240
|
+
export function updateCommitmentStatus(
|
|
241
|
+
id: string,
|
|
242
|
+
status: CommitmentStatus,
|
|
243
|
+
result?: string
|
|
244
|
+
): Commitment | null {
|
|
245
|
+
const db = getDb();
|
|
246
|
+
const commitment = getCommitment(id);
|
|
247
|
+
if (!commitment) return null;
|
|
248
|
+
|
|
249
|
+
const isTerminal = status === 'completed' || status === 'failed';
|
|
250
|
+
const completedAt = isTerminal ? Date.now() : null;
|
|
251
|
+
|
|
252
|
+
db.prepare(
|
|
253
|
+
'UPDATE commitments SET status = ?, completed_at = ?, result = ? WHERE id = ?'
|
|
254
|
+
).run(status, completedAt, result ?? null, id);
|
|
255
|
+
|
|
256
|
+
return getCommitment(id);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Update a commitment's assigned_to field.
|
|
261
|
+
*/
|
|
262
|
+
export function updateCommitmentAssignee(id: string, assignedTo: string): Commitment | null {
|
|
263
|
+
const db = getDb();
|
|
264
|
+
const commitment = getCommitment(id);
|
|
265
|
+
if (!commitment) return null;
|
|
266
|
+
|
|
267
|
+
db.prepare('UPDATE commitments SET assigned_to = ? WHERE id = ?').run(assignedTo, id);
|
|
268
|
+
return getCommitment(id);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Update a commitment's due date.
|
|
273
|
+
*/
|
|
274
|
+
export function updateCommitmentDue(id: string, when_due: number | null): Commitment | null {
|
|
275
|
+
const db = getDb();
|
|
276
|
+
const commitment = getCommitment(id);
|
|
277
|
+
if (!commitment) return null;
|
|
278
|
+
|
|
279
|
+
db.prepare('UPDATE commitments SET when_due = ? WHERE id = ?').run(when_due, id);
|
|
280
|
+
return getCommitment(id);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Bulk update sort order for commitments (used by kanban drag & drop).
|
|
285
|
+
*/
|
|
286
|
+
export function reorderCommitments(
|
|
287
|
+
items: { id: string; sort_order: number }[]
|
|
288
|
+
): void {
|
|
289
|
+
const db = getDb();
|
|
290
|
+
const stmt = db.prepare('UPDATE commitments SET sort_order = ? WHERE id = ?');
|
|
291
|
+
|
|
292
|
+
const transaction = db.transaction(() => {
|
|
293
|
+
for (const item of items) {
|
|
294
|
+
stmt.run(item.sort_order, item.id);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
transaction();
|
|
299
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { getDb, generateId } from './schema.ts';
|
|
2
|
+
|
|
3
|
+
/** Escape SQL LIKE wildcard characters in user input */
|
|
4
|
+
function escapeLike(s: string): string {
|
|
5
|
+
return s.replace(/[%_\\]/g, '\\$&');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const CONTENT_STAGES = [
|
|
9
|
+
'idea', 'research', 'outline', 'draft', 'assets', 'review', 'scheduled', 'published',
|
|
10
|
+
] as const;
|
|
11
|
+
|
|
12
|
+
export type ContentStage = typeof CONTENT_STAGES[number];
|
|
13
|
+
|
|
14
|
+
export const CONTENT_TYPES = [
|
|
15
|
+
'youtube', 'blog', 'twitter', 'instagram', 'tiktok', 'linkedin',
|
|
16
|
+
'podcast', 'newsletter', 'short_form', 'other',
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
export type ContentType = typeof CONTENT_TYPES[number];
|
|
20
|
+
|
|
21
|
+
export type ContentItem = {
|
|
22
|
+
id: string;
|
|
23
|
+
title: string;
|
|
24
|
+
body: string;
|
|
25
|
+
content_type: ContentType;
|
|
26
|
+
stage: ContentStage;
|
|
27
|
+
tags: string[];
|
|
28
|
+
scheduled_at: number | null;
|
|
29
|
+
published_at: number | null;
|
|
30
|
+
published_url: string | null;
|
|
31
|
+
created_by: string;
|
|
32
|
+
sort_order: number;
|
|
33
|
+
created_at: number;
|
|
34
|
+
updated_at: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type ContentStageNote = {
|
|
38
|
+
id: string;
|
|
39
|
+
content_id: string;
|
|
40
|
+
stage: ContentStage;
|
|
41
|
+
note: string;
|
|
42
|
+
author: string;
|
|
43
|
+
created_at: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type ContentAttachment = {
|
|
47
|
+
id: string;
|
|
48
|
+
content_id: string;
|
|
49
|
+
filename: string;
|
|
50
|
+
disk_path: string;
|
|
51
|
+
mime_type: string;
|
|
52
|
+
size_bytes: number;
|
|
53
|
+
label: string | null;
|
|
54
|
+
created_at: number;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type ContentRow = Omit<ContentItem, 'tags'> & { tags: string | null };
|
|
58
|
+
|
|
59
|
+
function parseRow(row: ContentRow): ContentItem {
|
|
60
|
+
return {
|
|
61
|
+
...row,
|
|
62
|
+
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// --- Content Items CRUD ---
|
|
67
|
+
|
|
68
|
+
export function createContent(title: string, opts?: {
|
|
69
|
+
body?: string;
|
|
70
|
+
content_type?: ContentType;
|
|
71
|
+
stage?: ContentStage;
|
|
72
|
+
tags?: string[];
|
|
73
|
+
created_by?: string;
|
|
74
|
+
}): ContentItem {
|
|
75
|
+
const db = getDb();
|
|
76
|
+
const id = generateId();
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
|
|
79
|
+
db.prepare(
|
|
80
|
+
`INSERT INTO content_items (id, title, body, content_type, stage, tags, created_by, created_at, updated_at)
|
|
81
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
82
|
+
).run(
|
|
83
|
+
id,
|
|
84
|
+
title,
|
|
85
|
+
opts?.body ?? '',
|
|
86
|
+
opts?.content_type ?? 'blog',
|
|
87
|
+
opts?.stage ?? 'idea',
|
|
88
|
+
opts?.tags ? JSON.stringify(opts.tags) : null,
|
|
89
|
+
opts?.created_by ?? 'user',
|
|
90
|
+
now,
|
|
91
|
+
now,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
id, title,
|
|
96
|
+
body: opts?.body ?? '',
|
|
97
|
+
content_type: (opts?.content_type ?? 'blog') as ContentType,
|
|
98
|
+
stage: (opts?.stage ?? 'idea') as ContentStage,
|
|
99
|
+
tags: opts?.tags ?? [],
|
|
100
|
+
scheduled_at: null,
|
|
101
|
+
published_at: null,
|
|
102
|
+
published_url: null,
|
|
103
|
+
created_by: opts?.created_by ?? 'user',
|
|
104
|
+
sort_order: 0,
|
|
105
|
+
created_at: now,
|
|
106
|
+
updated_at: now,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function getContent(id: string): ContentItem | null {
|
|
111
|
+
const db = getDb();
|
|
112
|
+
const row = db.prepare('SELECT * FROM content_items WHERE id = ?').get(id) as ContentRow | null;
|
|
113
|
+
return row ? parseRow(row) : null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function findContent(query: {
|
|
117
|
+
stage?: ContentStage;
|
|
118
|
+
content_type?: ContentType;
|
|
119
|
+
tag?: string;
|
|
120
|
+
}): ContentItem[] {
|
|
121
|
+
const db = getDb();
|
|
122
|
+
const conditions: string[] = [];
|
|
123
|
+
const params: unknown[] = [];
|
|
124
|
+
|
|
125
|
+
if (query.stage) {
|
|
126
|
+
conditions.push('stage = ?');
|
|
127
|
+
params.push(query.stage);
|
|
128
|
+
}
|
|
129
|
+
if (query.content_type) {
|
|
130
|
+
conditions.push('content_type = ?');
|
|
131
|
+
params.push(query.content_type);
|
|
132
|
+
}
|
|
133
|
+
if (query.tag) {
|
|
134
|
+
conditions.push("tags LIKE ? ESCAPE '\\'");
|
|
135
|
+
params.push(`%"${escapeLike(query.tag)}"%`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
139
|
+
const rows = db.prepare(
|
|
140
|
+
`SELECT * FROM content_items ${where} ORDER BY sort_order ASC, updated_at DESC`
|
|
141
|
+
).all(...params as any[]) as ContentRow[];
|
|
142
|
+
|
|
143
|
+
return rows.map(parseRow);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function updateContent(id: string, updates: {
|
|
147
|
+
title?: string;
|
|
148
|
+
body?: string;
|
|
149
|
+
content_type?: ContentType;
|
|
150
|
+
stage?: ContentStage;
|
|
151
|
+
tags?: string[];
|
|
152
|
+
scheduled_at?: number | null;
|
|
153
|
+
published_at?: number | null;
|
|
154
|
+
published_url?: string | null;
|
|
155
|
+
sort_order?: number;
|
|
156
|
+
}): ContentItem | null {
|
|
157
|
+
const db = getDb();
|
|
158
|
+
const existing = getContent(id);
|
|
159
|
+
if (!existing) return null;
|
|
160
|
+
|
|
161
|
+
const sets: string[] = ['updated_at = ?'];
|
|
162
|
+
const params: unknown[] = [Date.now()];
|
|
163
|
+
|
|
164
|
+
if (updates.title !== undefined) { sets.push('title = ?'); params.push(updates.title); }
|
|
165
|
+
if (updates.body !== undefined) { sets.push('body = ?'); params.push(updates.body); }
|
|
166
|
+
if (updates.content_type !== undefined) { sets.push('content_type = ?'); params.push(updates.content_type); }
|
|
167
|
+
if (updates.stage !== undefined) { sets.push('stage = ?'); params.push(updates.stage); }
|
|
168
|
+
if (updates.tags !== undefined) { sets.push('tags = ?'); params.push(JSON.stringify(updates.tags)); }
|
|
169
|
+
if (updates.scheduled_at !== undefined) { sets.push('scheduled_at = ?'); params.push(updates.scheduled_at); }
|
|
170
|
+
if (updates.published_at !== undefined) { sets.push('published_at = ?'); params.push(updates.published_at); }
|
|
171
|
+
if (updates.published_url !== undefined) { sets.push('published_url = ?'); params.push(updates.published_url); }
|
|
172
|
+
if (updates.sort_order !== undefined) { sets.push('sort_order = ?'); params.push(updates.sort_order); }
|
|
173
|
+
|
|
174
|
+
params.push(id);
|
|
175
|
+
db.prepare(`UPDATE content_items SET ${sets.join(', ')} WHERE id = ?`).run(...params as any[]);
|
|
176
|
+
|
|
177
|
+
return getContent(id);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function deleteContent(id: string): boolean {
|
|
181
|
+
const db = getDb();
|
|
182
|
+
const result = db.prepare('DELETE FROM content_items WHERE id = ?').run(id);
|
|
183
|
+
return result.changes > 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function advanceStage(id: string): ContentItem | null {
|
|
187
|
+
const item = getContent(id);
|
|
188
|
+
if (!item) return null;
|
|
189
|
+
const idx = CONTENT_STAGES.indexOf(item.stage);
|
|
190
|
+
if (idx >= CONTENT_STAGES.length - 1) return null;
|
|
191
|
+
return updateContent(id, { stage: CONTENT_STAGES[idx + 1] });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function regressStage(id: string): ContentItem | null {
|
|
195
|
+
const item = getContent(id);
|
|
196
|
+
if (!item) return null;
|
|
197
|
+
const idx = CONTENT_STAGES.indexOf(item.stage);
|
|
198
|
+
if (idx <= 0) return null;
|
|
199
|
+
return updateContent(id, { stage: CONTENT_STAGES[idx - 1] });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// --- Stage Notes ---
|
|
203
|
+
|
|
204
|
+
export function addStageNote(
|
|
205
|
+
contentId: string,
|
|
206
|
+
stage: ContentStage,
|
|
207
|
+
note: string,
|
|
208
|
+
author: string = 'user'
|
|
209
|
+
): ContentStageNote {
|
|
210
|
+
const db = getDb();
|
|
211
|
+
const id = generateId();
|
|
212
|
+
const now = Date.now();
|
|
213
|
+
|
|
214
|
+
db.prepare(
|
|
215
|
+
'INSERT INTO content_stage_notes (id, content_id, stage, note, author, created_at) VALUES (?, ?, ?, ?, ?, ?)'
|
|
216
|
+
).run(id, contentId, stage, note, author, now);
|
|
217
|
+
|
|
218
|
+
return { id, content_id: contentId, stage, note, author, created_at: now };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function getStageNotes(contentId: string, stage?: ContentStage): ContentStageNote[] {
|
|
222
|
+
const db = getDb();
|
|
223
|
+
if (stage) {
|
|
224
|
+
return db.prepare(
|
|
225
|
+
'SELECT * FROM content_stage_notes WHERE content_id = ? AND stage = ? ORDER BY created_at ASC'
|
|
226
|
+
).all(contentId, stage) as ContentStageNote[];
|
|
227
|
+
}
|
|
228
|
+
return db.prepare(
|
|
229
|
+
'SELECT * FROM content_stage_notes WHERE content_id = ? ORDER BY created_at ASC'
|
|
230
|
+
).all(contentId) as ContentStageNote[];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// --- Attachments ---
|
|
234
|
+
|
|
235
|
+
export function addAttachment(
|
|
236
|
+
contentId: string,
|
|
237
|
+
filename: string,
|
|
238
|
+
diskPath: string,
|
|
239
|
+
mimeType: string,
|
|
240
|
+
sizeBytes: number,
|
|
241
|
+
label?: string
|
|
242
|
+
): ContentAttachment {
|
|
243
|
+
const db = getDb();
|
|
244
|
+
const id = generateId();
|
|
245
|
+
const now = Date.now();
|
|
246
|
+
|
|
247
|
+
db.prepare(
|
|
248
|
+
'INSERT INTO content_attachments (id, content_id, filename, disk_path, mime_type, size_bytes, label, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
|
|
249
|
+
).run(id, contentId, filename, diskPath, mimeType, sizeBytes, label ?? null, now);
|
|
250
|
+
|
|
251
|
+
return { id, content_id: contentId, filename, disk_path: diskPath, mime_type: mimeType, size_bytes: sizeBytes, label: label ?? null, created_at: now };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function getAttachment(id: string): ContentAttachment | null {
|
|
255
|
+
const db = getDb();
|
|
256
|
+
return (db.prepare('SELECT * FROM content_attachments WHERE id = ?').get(id) as ContentAttachment) ?? null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function getAttachments(contentId: string): ContentAttachment[] {
|
|
260
|
+
const db = getDb();
|
|
261
|
+
return db.prepare(
|
|
262
|
+
'SELECT * FROM content_attachments WHERE content_id = ? ORDER BY created_at ASC'
|
|
263
|
+
).all(contentId) as ContentAttachment[];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function deleteAttachment(id: string): boolean {
|
|
267
|
+
const db = getDb();
|
|
268
|
+
const result = db.prepare('DELETE FROM content_attachments WHERE id = ?').run(id);
|
|
269
|
+
return result.changes > 0;
|
|
270
|
+
}
|