@usejarvis/brain 0.1.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 +278 -0
- package/bin/jarvis.ts +413 -0
- package/package.json +74 -0
- package/scripts/ensure-bun.cjs +8 -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 +252 -0
- package/src/actions/browser/session.ts +437 -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 +321 -0
- package/src/actions/tools/builtin.ts +846 -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/goals.ts +376 -0
- package/src/actions/tools/local-tools-guard.ts +20 -0
- package/src/actions/tools/registry.ts +171 -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 +576 -0
- package/src/agents/role-discovery.ts +61 -0
- package/src/agents/sub-agent-runner.ts +307 -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 +297 -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 +241 -0
- package/src/cli/deps.ts +449 -0
- package/src/cli/doctor.ts +230 -0
- package/src/cli/helpers.ts +401 -0
- package/src/cli/onboard.ts +580 -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/desktop-notify.ts +114 -0
- package/src/comms/example.ts +129 -0
- package/src/comms/index.ts +129 -0
- package/src/comms/streaming.ts +142 -0
- package/src/comms/voice.test.ts +152 -0
- package/src/comms/voice.ts +291 -0
- package/src/comms/websocket.test.ts +409 -0
- package/src/comms/websocket.ts +473 -0
- package/src/config/README.md +387 -0
- package/src/config/index.ts +6 -0
- package/src/config/loader.test.ts +137 -0
- package/src/config/loader.ts +142 -0
- package/src/config/types.ts +260 -0
- package/src/daemon/README.md +232 -0
- package/src/daemon/agent-service-interface.ts +9 -0
- package/src/daemon/agent-service.ts +600 -0
- package/src/daemon/api-routes.ts +2119 -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/event-classifier.ts +239 -0
- package/src/daemon/event-coalescer.ts +123 -0
- package/src/daemon/event-reactor.ts +214 -0
- package/src/daemon/health.ts +220 -0
- package/src/daemon/index.ts +1004 -0
- package/src/daemon/llm-settings.ts +316 -0
- package/src/daemon/observer-service.ts +150 -0
- package/src/daemon/pid.ts +98 -0
- package/src/daemon/research-queue.ts +155 -0
- package/src/daemon/services.ts +175 -0
- package/src/daemon/ws-service.ts +788 -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 +348 -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 +386 -0
- package/src/llm/gemini.ts +371 -0
- package/src/llm/index.ts +19 -0
- package/src/llm/manager.ts +153 -0
- package/src/llm/ollama.ts +307 -0
- package/src/llm/openai.ts +350 -0
- package/src/llm/provider.test.ts +231 -0
- package/src/llm/provider.ts +60 -0
- package/src/llm/test.ts +87 -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 +119 -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 +194 -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 +190 -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/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 +260 -0
- package/src/vault/conversations.ts +173 -0
- package/src/vault/entities.ts +180 -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 +126 -0
- package/src/vault/retrieval.ts +227 -0
- package/src/vault/schema.ts +658 -0
- package/src/vault/settings.ts +38 -0
- package/src/vault/vectors.ts +92 -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-j75njzc1.css +1199 -0
- package/ui/dist/index-p2zh407q.js +80603 -0
- package/ui/dist/index.html +13 -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,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CalendarSync — Google Calendar Observer
|
|
3
|
+
*
|
|
4
|
+
* Polls Calendar API every 2 minutes with a 30-minute look-ahead.
|
|
5
|
+
* Tracks announced event IDs to avoid duplicate alerts.
|
|
6
|
+
* Graceful: if no Google tokens, logs warning and stays no-op.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Observer, ObserverEventHandler } from './index';
|
|
10
|
+
import type { GoogleAuth } from '../integrations/google-auth.ts';
|
|
11
|
+
import { listUpcomingEvents } from '../integrations/google-api.ts';
|
|
12
|
+
|
|
13
|
+
const POLL_INTERVAL_MS = 2 * 60_000; // 2 minutes
|
|
14
|
+
const LOOK_AHEAD_MS = 30 * 60_000; // 30 minutes
|
|
15
|
+
|
|
16
|
+
export class CalendarSync implements Observer {
|
|
17
|
+
name = 'calendar';
|
|
18
|
+
private running = false;
|
|
19
|
+
private handler: ObserverEventHandler | null = null;
|
|
20
|
+
private pollTimer: Timer | null = null;
|
|
21
|
+
private googleAuth: GoogleAuth | null;
|
|
22
|
+
private announcedEventIds: Set<string> = new Set();
|
|
23
|
+
private calendarId: string;
|
|
24
|
+
|
|
25
|
+
constructor(googleAuth?: GoogleAuth, calendarId?: string) {
|
|
26
|
+
this.googleAuth = googleAuth ?? null;
|
|
27
|
+
this.calendarId = calendarId ?? 'primary';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async start(): Promise<void> {
|
|
31
|
+
this.running = true;
|
|
32
|
+
|
|
33
|
+
if (!this.googleAuth || !this.googleAuth.isAuthenticated()) {
|
|
34
|
+
console.log('[calendar] No Google auth configured — calendar monitoring disabled');
|
|
35
|
+
console.log('[calendar] Run: bun run src/scripts/google-setup.ts to set up Calendar');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log('[calendar] Observer started — polling Calendar every 2min (30min look-ahead)');
|
|
40
|
+
|
|
41
|
+
// Initial poll
|
|
42
|
+
this.poll();
|
|
43
|
+
|
|
44
|
+
// Set up recurring poll
|
|
45
|
+
this.pollTimer = setInterval(() => this.poll(), POLL_INTERVAL_MS);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async stop(): Promise<void> {
|
|
49
|
+
this.running = false;
|
|
50
|
+
|
|
51
|
+
if (this.pollTimer) {
|
|
52
|
+
clearInterval(this.pollTimer);
|
|
53
|
+
this.pollTimer = null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log('[calendar] Observer stopped');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
isRunning(): boolean {
|
|
60
|
+
return this.running;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
onEvent(handler: ObserverEventHandler): void {
|
|
64
|
+
this.handler = handler;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private async poll(): Promise<void> {
|
|
68
|
+
if (!this.googleAuth || !this.handler) return;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const accessToken = await this.googleAuth.getAccessToken();
|
|
72
|
+
const now = new Date();
|
|
73
|
+
const later = new Date(now.getTime() + LOOK_AHEAD_MS);
|
|
74
|
+
|
|
75
|
+
const events = await listUpcomingEvents(
|
|
76
|
+
accessToken,
|
|
77
|
+
this.calendarId,
|
|
78
|
+
now.toISOString(),
|
|
79
|
+
later.toISOString(),
|
|
80
|
+
10
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
for (const event of events) {
|
|
84
|
+
// Skip already-announced events
|
|
85
|
+
if (this.announcedEventIds.has(event.id)) continue;
|
|
86
|
+
this.announcedEventIds.add(event.id);
|
|
87
|
+
|
|
88
|
+
this.handler({
|
|
89
|
+
type: 'calendar',
|
|
90
|
+
data: {
|
|
91
|
+
id: event.id,
|
|
92
|
+
summary: event.summary,
|
|
93
|
+
description: event.description,
|
|
94
|
+
start: event.start,
|
|
95
|
+
end: event.end,
|
|
96
|
+
location: event.location,
|
|
97
|
+
attendees: event.attendees,
|
|
98
|
+
htmlLink: event.htmlLink,
|
|
99
|
+
},
|
|
100
|
+
timestamp: Date.now(),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Cap announcedEventIds to prevent unbounded growth
|
|
105
|
+
if (this.announcedEventIds.size > 500) {
|
|
106
|
+
const arr = Array.from(this.announcedEventIds);
|
|
107
|
+
this.announcedEventIds = new Set(arr.slice(arr.length - 250));
|
|
108
|
+
}
|
|
109
|
+
} catch (err) {
|
|
110
|
+
console.error('[calendar] Poll error:', err);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClipboardMonitor - Monitors clipboard changes
|
|
3
|
+
*
|
|
4
|
+
* Polls the system clipboard at regular intervals and emits events when content changes.
|
|
5
|
+
* Uses platform-specific commands to read clipboard content.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Observer, ObserverEvent, ObserverEventHandler } from './index';
|
|
9
|
+
|
|
10
|
+
export class ClipboardMonitor implements Observer {
|
|
11
|
+
name = 'clipboard';
|
|
12
|
+
private interval: Timer | null = null;
|
|
13
|
+
private lastContent: string = '';
|
|
14
|
+
private handler: ObserverEventHandler | null = null;
|
|
15
|
+
private running = false;
|
|
16
|
+
private pollMs: number;
|
|
17
|
+
|
|
18
|
+
constructor(pollMs: number = 1000) {
|
|
19
|
+
this.pollMs = pollMs;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async start(): Promise<void> {
|
|
23
|
+
if (this.running) {
|
|
24
|
+
console.log('[clipboard] Already running');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(`[clipboard] Starting clipboard monitoring (polling every ${this.pollMs}ms)...`);
|
|
29
|
+
|
|
30
|
+
// Initialize with current clipboard content
|
|
31
|
+
try {
|
|
32
|
+
this.lastContent = await this.readClipboard();
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('[clipboard] Failed to read initial clipboard:', error);
|
|
35
|
+
this.lastContent = '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Start polling
|
|
39
|
+
this.interval = setInterval(async () => {
|
|
40
|
+
try {
|
|
41
|
+
const content = await this.readClipboard();
|
|
42
|
+
|
|
43
|
+
if (content !== this.lastContent) {
|
|
44
|
+
this.lastContent = content;
|
|
45
|
+
|
|
46
|
+
if (this.handler) {
|
|
47
|
+
const event: ObserverEvent = {
|
|
48
|
+
type: 'clipboard',
|
|
49
|
+
data: {
|
|
50
|
+
content,
|
|
51
|
+
length: content.length,
|
|
52
|
+
},
|
|
53
|
+
timestamp: Date.now(),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
this.handler(event);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
// Silent fail on read errors to avoid spam
|
|
61
|
+
// console.error('[clipboard] Failed to read clipboard:', error);
|
|
62
|
+
}
|
|
63
|
+
}, this.pollMs);
|
|
64
|
+
|
|
65
|
+
this.running = true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async stop(): Promise<void> {
|
|
69
|
+
if (!this.running) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log('[clipboard] Stopping clipboard monitoring...');
|
|
74
|
+
|
|
75
|
+
if (this.interval) {
|
|
76
|
+
clearInterval(this.interval);
|
|
77
|
+
this.interval = null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.running = false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
isRunning(): boolean {
|
|
84
|
+
return this.running;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
onEvent(handler: ObserverEventHandler): void {
|
|
88
|
+
this.handler = handler;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Read clipboard content using platform-specific commands
|
|
93
|
+
*/
|
|
94
|
+
private async readClipboard(): Promise<string> {
|
|
95
|
+
const platform = process.platform;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
let result: { stdout: Buffer; stderr: Buffer };
|
|
99
|
+
|
|
100
|
+
if (platform === 'linux') {
|
|
101
|
+
// Try xclip first
|
|
102
|
+
try {
|
|
103
|
+
result = await Bun.$`xclip -selection clipboard -o`.quiet();
|
|
104
|
+
return result.stdout.toString().trim();
|
|
105
|
+
} catch {
|
|
106
|
+
// Fall back to xsel
|
|
107
|
+
try {
|
|
108
|
+
result = await Bun.$`xsel --clipboard --output`.quiet();
|
|
109
|
+
return result.stdout.toString().trim();
|
|
110
|
+
} catch {
|
|
111
|
+
// Check if we're in WSL and can use PowerShell
|
|
112
|
+
try {
|
|
113
|
+
result = await Bun.$`powershell.exe Get-Clipboard`.quiet();
|
|
114
|
+
return result.stdout.toString().trim();
|
|
115
|
+
} catch {
|
|
116
|
+
throw new Error('No clipboard tool available (tried xclip, xsel, powershell.exe)');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} else if (platform === 'darwin') {
|
|
121
|
+
// macOS
|
|
122
|
+
result = await Bun.$`pbpaste`.quiet();
|
|
123
|
+
return result.stdout.toString().trim();
|
|
124
|
+
} else if (platform === 'win32') {
|
|
125
|
+
// Windows
|
|
126
|
+
result = await Bun.$`powershell.exe Get-Clipboard`.quiet();
|
|
127
|
+
return result.stdout.toString().trim();
|
|
128
|
+
} else {
|
|
129
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
// Return empty string on error
|
|
133
|
+
return '';
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EmailSync — Gmail Observer
|
|
3
|
+
*
|
|
4
|
+
* Polls Gmail API every 60s for unread messages.
|
|
5
|
+
* Tracks seen message IDs to avoid re-emitting.
|
|
6
|
+
* Fetches detail (subject, from, snippet) for new messages.
|
|
7
|
+
* Graceful: if no Google tokens, logs warning and stays no-op.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Observer, ObserverEventHandler } from './index';
|
|
11
|
+
import type { GoogleAuth } from '../integrations/google-auth.ts';
|
|
12
|
+
import { listUnreadEmails, getEmailDetail } from '../integrations/google-api.ts';
|
|
13
|
+
|
|
14
|
+
const POLL_INTERVAL_MS = 60_000; // 60 seconds
|
|
15
|
+
|
|
16
|
+
export class EmailSync implements Observer {
|
|
17
|
+
name = 'email';
|
|
18
|
+
private running = false;
|
|
19
|
+
private handler: ObserverEventHandler | null = null;
|
|
20
|
+
private pollTimer: Timer | null = null;
|
|
21
|
+
private googleAuth: GoogleAuth | null;
|
|
22
|
+
private seenMessageIds: Set<string> = new Set();
|
|
23
|
+
|
|
24
|
+
constructor(googleAuth?: GoogleAuth) {
|
|
25
|
+
this.googleAuth = googleAuth ?? null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async start(): Promise<void> {
|
|
29
|
+
this.running = true;
|
|
30
|
+
|
|
31
|
+
if (!this.googleAuth || !this.googleAuth.isAuthenticated()) {
|
|
32
|
+
console.log('[email] No Google auth configured — email monitoring disabled');
|
|
33
|
+
console.log('[email] Run: bun run src/scripts/google-setup.ts to set up Gmail');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log('[email] Observer started — polling Gmail every 60s');
|
|
38
|
+
|
|
39
|
+
// Initial poll
|
|
40
|
+
this.poll();
|
|
41
|
+
|
|
42
|
+
// Set up recurring poll
|
|
43
|
+
this.pollTimer = setInterval(() => this.poll(), POLL_INTERVAL_MS);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async stop(): Promise<void> {
|
|
47
|
+
this.running = false;
|
|
48
|
+
|
|
49
|
+
if (this.pollTimer) {
|
|
50
|
+
clearInterval(this.pollTimer);
|
|
51
|
+
this.pollTimer = null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log('[email] Observer stopped');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
isRunning(): boolean {
|
|
58
|
+
return this.running;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onEvent(handler: ObserverEventHandler): void {
|
|
62
|
+
this.handler = handler;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async poll(): Promise<void> {
|
|
66
|
+
if (!this.googleAuth || !this.handler) return;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const accessToken = await this.googleAuth.getAccessToken();
|
|
70
|
+
const messages = await listUnreadEmails(accessToken, 10);
|
|
71
|
+
|
|
72
|
+
for (const msg of messages) {
|
|
73
|
+
// Skip already-seen messages
|
|
74
|
+
if (this.seenMessageIds.has(msg.id)) continue;
|
|
75
|
+
this.seenMessageIds.add(msg.id);
|
|
76
|
+
|
|
77
|
+
// Fetch detail
|
|
78
|
+
try {
|
|
79
|
+
const detail = await getEmailDetail(accessToken, msg.id);
|
|
80
|
+
|
|
81
|
+
this.handler({
|
|
82
|
+
type: 'email',
|
|
83
|
+
data: {
|
|
84
|
+
id: detail.id,
|
|
85
|
+
threadId: detail.threadId,
|
|
86
|
+
subject: detail.subject,
|
|
87
|
+
from: detail.from,
|
|
88
|
+
to: detail.to,
|
|
89
|
+
date: detail.date,
|
|
90
|
+
snippet: detail.snippet,
|
|
91
|
+
labels: detail.labels,
|
|
92
|
+
},
|
|
93
|
+
timestamp: Date.now(),
|
|
94
|
+
});
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error(`[email] Failed to get detail for ${msg.id}:`, err);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Cap seenMessageIds to prevent unbounded growth
|
|
101
|
+
if (this.seenMessageIds.size > 1000) {
|
|
102
|
+
const arr = Array.from(this.seenMessageIds);
|
|
103
|
+
this.seenMessageIds = new Set(arr.slice(arr.length - 500));
|
|
104
|
+
}
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.error('[email] Poll error:', err);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example usage of the Observer Layer
|
|
3
|
+
*
|
|
4
|
+
* This demonstrates how to set up and use the ObserverManager with various observers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
ObserverManager,
|
|
9
|
+
FileWatcher,
|
|
10
|
+
ClipboardMonitor,
|
|
11
|
+
ProcessMonitor,
|
|
12
|
+
NotificationListener,
|
|
13
|
+
CalendarSync,
|
|
14
|
+
EmailSync,
|
|
15
|
+
type ObserverEvent,
|
|
16
|
+
} from './index';
|
|
17
|
+
|
|
18
|
+
async function main() {
|
|
19
|
+
// Create the manager
|
|
20
|
+
const manager = new ObserverManager();
|
|
21
|
+
|
|
22
|
+
// Register observers
|
|
23
|
+
manager.register(new FileWatcher([import.meta.dir + '/../'])); // Watch src directory
|
|
24
|
+
manager.register(new ClipboardMonitor(2000)); // Poll clipboard every 2 seconds
|
|
25
|
+
manager.register(new ProcessMonitor(10000)); // Poll processes every 10 seconds
|
|
26
|
+
manager.register(new NotificationListener()); // Stub
|
|
27
|
+
manager.register(new CalendarSync()); // Stub
|
|
28
|
+
manager.register(new EmailSync()); // Stub
|
|
29
|
+
|
|
30
|
+
// Set up event handler (this would typically be the Vault's ingestion function)
|
|
31
|
+
manager.setEventHandler((event: ObserverEvent) => {
|
|
32
|
+
console.log(`[EVENT] ${event.type} at ${new Date(event.timestamp).toISOString()}`);
|
|
33
|
+
console.log(' Data:', JSON.stringify(event.data, null, 2));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Start all observers
|
|
37
|
+
await manager.startAll();
|
|
38
|
+
|
|
39
|
+
// Get status
|
|
40
|
+
console.log('\n[STATUS] Observer running status:', manager.getStatus());
|
|
41
|
+
|
|
42
|
+
// Run for 30 seconds, then stop
|
|
43
|
+
console.log('\n[INFO] Running observers for 30 seconds...\n');
|
|
44
|
+
|
|
45
|
+
await new Promise(resolve => setTimeout(resolve, 30000));
|
|
46
|
+
|
|
47
|
+
// Stop all observers
|
|
48
|
+
await manager.stopAll();
|
|
49
|
+
|
|
50
|
+
console.log('\n[INFO] Example complete');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Run example if executed directly
|
|
54
|
+
if (import.meta.main) {
|
|
55
|
+
main().catch(console.error);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { main };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileWatcher - Monitors file system changes
|
|
3
|
+
*
|
|
4
|
+
* Watches specified directories recursively and emits events when files change.
|
|
5
|
+
* Includes debouncing to avoid duplicate rapid-fire events.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { watch, type FSWatcher } from 'node:fs';
|
|
9
|
+
import type { Observer, ObserverEvent, ObserverEventHandler } from './index';
|
|
10
|
+
|
|
11
|
+
type DebounceEntry = {
|
|
12
|
+
path: string;
|
|
13
|
+
timestamp: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export class FileWatcher implements Observer {
|
|
17
|
+
name = 'file-watcher';
|
|
18
|
+
private watchers: FSWatcher[] = [];
|
|
19
|
+
private paths: string[];
|
|
20
|
+
private handler: ObserverEventHandler | null = null;
|
|
21
|
+
private running = false;
|
|
22
|
+
private recentChanges: Map<string, number> = new Map();
|
|
23
|
+
private debounceMs = 100; // Ignore duplicate events within 100ms
|
|
24
|
+
|
|
25
|
+
constructor(paths: string[]) {
|
|
26
|
+
this.paths = paths;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async start(): Promise<void> {
|
|
30
|
+
if (this.running) {
|
|
31
|
+
console.log('[file-watcher] Already running');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log('[file-watcher] Starting file system monitoring...');
|
|
36
|
+
|
|
37
|
+
for (const path of this.paths) {
|
|
38
|
+
try {
|
|
39
|
+
const watcher = watch(path, { recursive: true }, (eventType, filename) => {
|
|
40
|
+
this.handleFileEvent(path, eventType, filename);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
watcher.on('error', (error) => {
|
|
44
|
+
console.error(`[file-watcher] Error watching ${path}:`, error);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this.watchers.push(watcher);
|
|
48
|
+
console.log(`[file-watcher] Watching: ${path}`);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(`[file-watcher] Failed to watch ${path}:`, error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.running = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async stop(): Promise<void> {
|
|
58
|
+
if (!this.running) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log('[file-watcher] Stopping file system monitoring...');
|
|
63
|
+
|
|
64
|
+
for (const watcher of this.watchers) {
|
|
65
|
+
watcher.close();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.watchers = [];
|
|
69
|
+
this.recentChanges.clear();
|
|
70
|
+
this.running = false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
isRunning(): boolean {
|
|
74
|
+
return this.running;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onEvent(handler: ObserverEventHandler): void {
|
|
78
|
+
this.handler = handler;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private handleFileEvent(
|
|
82
|
+
basePath: string,
|
|
83
|
+
eventType: 'rename' | 'change',
|
|
84
|
+
filename: string | null
|
|
85
|
+
): void {
|
|
86
|
+
if (!filename || !this.handler) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const fullPath = `${basePath}/${filename}`;
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
|
|
93
|
+
// Debounce: skip if same file changed within debounceMs
|
|
94
|
+
const lastChange = this.recentChanges.get(fullPath);
|
|
95
|
+
if (lastChange && now - lastChange < this.debounceMs) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.recentChanges.set(fullPath, now);
|
|
100
|
+
|
|
101
|
+
// Clean up old entries to prevent memory leak
|
|
102
|
+
if (this.recentChanges.size > 1000) {
|
|
103
|
+
const cutoff = now - this.debounceMs * 2;
|
|
104
|
+
for (const [path, timestamp] of this.recentChanges.entries()) {
|
|
105
|
+
if (timestamp < cutoff) {
|
|
106
|
+
this.recentChanges.delete(path);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const event: ObserverEvent = {
|
|
112
|
+
type: 'file_change',
|
|
113
|
+
data: {
|
|
114
|
+
path: fullPath,
|
|
115
|
+
eventType,
|
|
116
|
+
filename,
|
|
117
|
+
basePath,
|
|
118
|
+
},
|
|
119
|
+
timestamp: now,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
this.handler(event);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observer Layer - Monitors system events and emits observations to the Vault
|
|
3
|
+
*
|
|
4
|
+
* All observers implement a common interface and emit standardized events.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Common Observer Interface
|
|
8
|
+
export type ObserverEvent = {
|
|
9
|
+
type: string;
|
|
10
|
+
data: Record<string, unknown>;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type ObserverEventHandler = (event: ObserverEvent) => void;
|
|
15
|
+
|
|
16
|
+
export interface Observer {
|
|
17
|
+
name: string;
|
|
18
|
+
start(): Promise<void>;
|
|
19
|
+
stop(): Promise<void>;
|
|
20
|
+
isRunning(): boolean;
|
|
21
|
+
onEvent(handler: ObserverEventHandler): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Export all observers
|
|
25
|
+
export { FileWatcher } from './file-watcher';
|
|
26
|
+
export { ClipboardMonitor } from './clipboard';
|
|
27
|
+
export { NotificationListener } from './notifications';
|
|
28
|
+
export { ProcessMonitor } from './processes';
|
|
29
|
+
export { CalendarSync } from './calendar';
|
|
30
|
+
export { EmailSync } from './email';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* ObserverManager - Centralized coordinator for all observers
|
|
34
|
+
*/
|
|
35
|
+
export class ObserverManager {
|
|
36
|
+
private observers: Map<string, Observer> = new Map();
|
|
37
|
+
private eventHandler: ObserverEventHandler | null = null;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Register a new observer
|
|
41
|
+
*/
|
|
42
|
+
register(observer: Observer): void {
|
|
43
|
+
this.observers.set(observer.name, observer);
|
|
44
|
+
|
|
45
|
+
// If we already have a handler, apply it to the new observer
|
|
46
|
+
if (this.eventHandler) {
|
|
47
|
+
observer.onEvent(this.eventHandler);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(`[ObserverManager] Registered observer: ${observer.name}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Set the global event handler for all observers
|
|
55
|
+
* This is typically the Vault's observation ingestion function
|
|
56
|
+
*/
|
|
57
|
+
setEventHandler(handler: ObserverEventHandler): void {
|
|
58
|
+
this.eventHandler = handler;
|
|
59
|
+
|
|
60
|
+
// Apply handler to all registered observers
|
|
61
|
+
for (const observer of this.observers.values()) {
|
|
62
|
+
observer.onEvent(handler);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(`[ObserverManager] Event handler configured for ${this.observers.size} observers`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Start all registered observers
|
|
70
|
+
*/
|
|
71
|
+
async startAll(): Promise<void> {
|
|
72
|
+
console.log('[ObserverManager] Starting all observers...');
|
|
73
|
+
|
|
74
|
+
const promises = Array.from(this.observers.values()).map(async (observer) => {
|
|
75
|
+
try {
|
|
76
|
+
await observer.start();
|
|
77
|
+
console.log(`[ObserverManager] ✓ Started ${observer.name}`);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(`[ObserverManager] ✗ Failed to start ${observer.name}:`, error);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await Promise.all(promises);
|
|
84
|
+
console.log('[ObserverManager] All observers started');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Stop all registered observers
|
|
89
|
+
*/
|
|
90
|
+
async stopAll(): Promise<void> {
|
|
91
|
+
console.log('[ObserverManager] Stopping all observers...');
|
|
92
|
+
|
|
93
|
+
const promises = Array.from(this.observers.values()).map(async (observer) => {
|
|
94
|
+
try {
|
|
95
|
+
await observer.stop();
|
|
96
|
+
console.log(`[ObserverManager] ✓ Stopped ${observer.name}`);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error(`[ObserverManager] ✗ Failed to stop ${observer.name}:`, error);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await Promise.all(promises);
|
|
103
|
+
console.log('[ObserverManager] All observers stopped');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Start a specific observer by name
|
|
108
|
+
*/
|
|
109
|
+
async startObserver(name: string): Promise<void> {
|
|
110
|
+
const observer = this.observers.get(name);
|
|
111
|
+
if (!observer) {
|
|
112
|
+
throw new Error(`Observer not found: ${name}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (observer.isRunning()) {
|
|
116
|
+
console.log(`[ObserverManager] Observer ${name} is already running`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await observer.start();
|
|
121
|
+
console.log(`[ObserverManager] Started ${name}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Stop a specific observer by name
|
|
126
|
+
*/
|
|
127
|
+
async stopObserver(name: string): Promise<void> {
|
|
128
|
+
const observer = this.observers.get(name);
|
|
129
|
+
if (!observer) {
|
|
130
|
+
throw new Error(`Observer not found: ${name}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!observer.isRunning()) {
|
|
134
|
+
console.log(`[ObserverManager] Observer ${name} is not running`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await observer.stop();
|
|
139
|
+
console.log(`[ObserverManager] Stopped ${name}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get running status of all observers
|
|
144
|
+
*/
|
|
145
|
+
getStatus(): Record<string, boolean> {
|
|
146
|
+
const status: Record<string, boolean> = {};
|
|
147
|
+
for (const [name, observer] of this.observers) {
|
|
148
|
+
status[name] = observer.isRunning();
|
|
149
|
+
}
|
|
150
|
+
return status;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* List all registered observer names
|
|
155
|
+
*/
|
|
156
|
+
listObservers(): string[] {
|
|
157
|
+
return Array.from(this.observers.keys());
|
|
158
|
+
}
|
|
159
|
+
}
|