@kernel.chat/kbot 3.66.0 → 3.68.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/README.md +26 -5
- package/dist/agent.js +13 -1
- package/dist/buddy.d.ts +77 -2
- package/dist/buddy.js +631 -6
- package/dist/cli.js +119 -1
- package/dist/dream.js +13 -0
- package/dist/tools/behavior-tools.d.ts +2 -0
- package/dist/tools/behavior-tools.js +63 -0
- package/dist/tools/buddy-tools.js +47 -4
- package/dist/tools/index.js +2 -0
- package/dist/tools/watchdog.d.ts +32 -0
- package/dist/tools/watchdog.js +356 -0
- package/dist/user-behavior.d.ts +65 -0
- package/dist/user-behavior.js +301 -0
- package/package.json +2 -2
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// kbot Watchdog Tools — Service monitoring and system health dashboard
|
|
2
|
+
//
|
|
3
|
+
// Monitors all kbot background services running via launchd on macOS:
|
|
4
|
+
// - com.kernel.email-agent
|
|
5
|
+
// - com.kernel.kbot-discovery
|
|
6
|
+
// - com.kernel.kbot-serve
|
|
7
|
+
// - com.kernel.discord-bot
|
|
8
|
+
// - com.kernel.mlx-server
|
|
9
|
+
// - com.kernel.kbot-collective-sync
|
|
10
|
+
// - kbot-local MCP
|
|
11
|
+
// - com.kernel.kbot-daemon (15-min cycle)
|
|
12
|
+
//
|
|
13
|
+
// Tools: service_status, service_restart, system_health
|
|
14
|
+
import { registerTool } from './index.js';
|
|
15
|
+
import { execSync } from 'node:child_process';
|
|
16
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
// ── Known services ──
|
|
20
|
+
const SERVICE_MAP = {
|
|
21
|
+
'email-agent': 'com.kernel.email-agent',
|
|
22
|
+
'discovery': 'com.kernel.kbot-discovery',
|
|
23
|
+
'serve': 'com.kernel.kbot-serve',
|
|
24
|
+
'discord': 'com.kernel.discord-bot',
|
|
25
|
+
'mlx': 'com.kernel.mlx-server',
|
|
26
|
+
'collective-sync': 'com.kernel.kbot-collective-sync',
|
|
27
|
+
'daemon': 'com.kernel.kbot-daemon',
|
|
28
|
+
'kbot-local': 'com.kernel.kbot-local',
|
|
29
|
+
};
|
|
30
|
+
// Reverse map: label -> short name
|
|
31
|
+
const LABEL_TO_SHORT = {};
|
|
32
|
+
for (const [short, label] of Object.entries(SERVICE_MAP)) {
|
|
33
|
+
LABEL_TO_SHORT[label] = short;
|
|
34
|
+
}
|
|
35
|
+
// ── Helpers ──
|
|
36
|
+
function exec(cmd, timeout = 10_000) {
|
|
37
|
+
try {
|
|
38
|
+
return execSync(cmd, { timeout, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function getUid() {
|
|
45
|
+
return exec('id -u');
|
|
46
|
+
}
|
|
47
|
+
function parseServiceLine(line) {
|
|
48
|
+
// launchctl list output: PID\tStatus\tLabel
|
|
49
|
+
const match = line.match(/^(-?\d+|-)?\s+(\d+)\s+(.+)$/);
|
|
50
|
+
if (!match)
|
|
51
|
+
return null;
|
|
52
|
+
const pid = match[1] === '-' || !match[1] ? null : parseInt(match[1], 10);
|
|
53
|
+
const lastExitStatus = parseInt(match[2], 10);
|
|
54
|
+
const label = match[3].trim();
|
|
55
|
+
return { pid, lastExitStatus, label };
|
|
56
|
+
}
|
|
57
|
+
function isPidAlive(pid) {
|
|
58
|
+
try {
|
|
59
|
+
process.kill(pid, 0);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function getProcessStats(pid) {
|
|
67
|
+
const raw = exec(`ps -p ${pid} -o %cpu,%mem,etime=`);
|
|
68
|
+
if (!raw)
|
|
69
|
+
return { cpu: '?', mem: '?', uptime: '?' };
|
|
70
|
+
// Output: " 0.1 1.2 01:23:45" or " 0.0 0.3 12:34"
|
|
71
|
+
const parts = raw.trim().split(/\s+/);
|
|
72
|
+
if (parts.length >= 3) {
|
|
73
|
+
return { cpu: `${parts[0]}%`, mem: `${parts[1]}%`, uptime: parts[2] };
|
|
74
|
+
}
|
|
75
|
+
return { cpu: '?', mem: '?', uptime: '?' };
|
|
76
|
+
}
|
|
77
|
+
function formatUptime(etime) {
|
|
78
|
+
// etime can be: "12:34", "01:23:45", "1-01:23:45"
|
|
79
|
+
if (etime === '?')
|
|
80
|
+
return etime;
|
|
81
|
+
const dayMatch = etime.match(/^(\d+)-/);
|
|
82
|
+
if (dayMatch) {
|
|
83
|
+
const days = parseInt(dayMatch[1], 10);
|
|
84
|
+
return `${days}d`;
|
|
85
|
+
}
|
|
86
|
+
const parts = etime.split(':');
|
|
87
|
+
if (parts.length === 3) {
|
|
88
|
+
const h = parseInt(parts[0], 10);
|
|
89
|
+
return h > 0 ? `${h}h` : `${parseInt(parts[1], 10)}m`;
|
|
90
|
+
}
|
|
91
|
+
if (parts.length === 2) {
|
|
92
|
+
const m = parseInt(parts[0], 10);
|
|
93
|
+
return m > 0 ? `${m}m` : `${parseInt(parts[1], 10)}s`;
|
|
94
|
+
}
|
|
95
|
+
return etime;
|
|
96
|
+
}
|
|
97
|
+
// ── Core Functions (exported for CLI use) ──
|
|
98
|
+
export function getServiceStatus() {
|
|
99
|
+
const raw = exec('launchctl list 2>/dev/null');
|
|
100
|
+
if (!raw)
|
|
101
|
+
return [];
|
|
102
|
+
const lines = raw.split('\n');
|
|
103
|
+
const found = new Map();
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
const parsed = parseServiceLine(line);
|
|
106
|
+
if (!parsed)
|
|
107
|
+
continue;
|
|
108
|
+
// Match against known service labels or grep for kbot/kernel/email
|
|
109
|
+
if (!parsed.label.match(/kbot|kernel|email/i))
|
|
110
|
+
continue;
|
|
111
|
+
const shortName = LABEL_TO_SHORT[parsed.label] || parsed.label.replace(/^com\.kernel\./, '');
|
|
112
|
+
const alive = parsed.pid !== null && parsed.pid > 0 && isPidAlive(parsed.pid);
|
|
113
|
+
const stats = alive && parsed.pid ? getProcessStats(parsed.pid) : { cpu: '-', mem: '-', uptime: '-' };
|
|
114
|
+
found.set(parsed.label, {
|
|
115
|
+
label: parsed.label,
|
|
116
|
+
shortName,
|
|
117
|
+
pid: alive ? parsed.pid : null,
|
|
118
|
+
status: alive ? 'running' : 'dead',
|
|
119
|
+
cpu: stats.cpu,
|
|
120
|
+
mem: stats.mem,
|
|
121
|
+
uptime: formatUptime(stats.uptime),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// Add known services that weren't found (not loaded)
|
|
125
|
+
for (const [short, label] of Object.entries(SERVICE_MAP)) {
|
|
126
|
+
if (!found.has(label)) {
|
|
127
|
+
found.set(label, {
|
|
128
|
+
label,
|
|
129
|
+
shortName: short,
|
|
130
|
+
pid: null,
|
|
131
|
+
status: 'not-loaded',
|
|
132
|
+
cpu: '-',
|
|
133
|
+
mem: '-',
|
|
134
|
+
uptime: '-',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Sort: running first, then dead, then not-loaded
|
|
139
|
+
const order = { running: 0, dead: 1, 'not-loaded': 2 };
|
|
140
|
+
return [...found.values()].sort((a, b) => order[a.status] - order[b.status]);
|
|
141
|
+
}
|
|
142
|
+
export function restartService(name) {
|
|
143
|
+
// Resolve short name to label
|
|
144
|
+
const label = SERVICE_MAP[name] || name;
|
|
145
|
+
const uid = getUid();
|
|
146
|
+
if (!uid) {
|
|
147
|
+
return { success: false, message: 'Could not determine user ID' };
|
|
148
|
+
}
|
|
149
|
+
// Try kickstart first (restarts running services)
|
|
150
|
+
const domain = `gui/${uid}`;
|
|
151
|
+
const result = exec(`launchctl kickstart -k ${domain}/${label} 2>&1`, 15_000);
|
|
152
|
+
if (result.includes('No such process') || result.includes('Could not find service')) {
|
|
153
|
+
// Try bootstrap (load the plist)
|
|
154
|
+
const plistPath = join(homedir(), 'Library', 'LaunchAgents', `${label}.plist`);
|
|
155
|
+
if (existsSync(plistPath)) {
|
|
156
|
+
const loadResult = exec(`launchctl bootstrap ${domain} ${plistPath} 2>&1`, 15_000);
|
|
157
|
+
if (loadResult.includes('error') || loadResult.includes('Could not')) {
|
|
158
|
+
return { success: false, message: `Failed to load ${label}: ${loadResult}` };
|
|
159
|
+
}
|
|
160
|
+
return { success: true, message: `Loaded and started ${label}` };
|
|
161
|
+
}
|
|
162
|
+
return { success: false, message: `Service ${label} not found. Plist missing at ${plistPath}` };
|
|
163
|
+
}
|
|
164
|
+
if (result.includes('error') || result.includes('failed')) {
|
|
165
|
+
return { success: false, message: `Restart failed: ${result}` };
|
|
166
|
+
}
|
|
167
|
+
return { success: true, message: `Restarted ${label} via kickstart` };
|
|
168
|
+
}
|
|
169
|
+
export function getSystemHealth() {
|
|
170
|
+
// ── CPU load ──
|
|
171
|
+
const loadRaw = exec('sysctl -n vm.loadavg');
|
|
172
|
+
// Output: "{ 1.23 1.45 1.67 }"
|
|
173
|
+
const loadAvg = loadRaw.replace(/[{}]/g, '').trim() || '?';
|
|
174
|
+
// ── Memory ──
|
|
175
|
+
let memFree = '?';
|
|
176
|
+
let memTotal = '?';
|
|
177
|
+
let memUsed = '?';
|
|
178
|
+
const vmStatRaw = exec('vm_stat');
|
|
179
|
+
const pageSize = parseInt(exec('sysctl -n hw.pagesize') || '4096', 10);
|
|
180
|
+
const totalMemBytes = parseInt(exec('sysctl -n hw.memsize') || '0', 10);
|
|
181
|
+
if (vmStatRaw && totalMemBytes > 0) {
|
|
182
|
+
const pages = (key) => {
|
|
183
|
+
const m = vmStatRaw.match(new RegExp(`${key}:\\s+(\\d+)`));
|
|
184
|
+
return m ? parseInt(m[1], 10) : 0;
|
|
185
|
+
};
|
|
186
|
+
const freePages = pages('Pages free') + pages('Pages speculative');
|
|
187
|
+
const freeBytes = freePages * pageSize;
|
|
188
|
+
const usedBytes = totalMemBytes - freeBytes;
|
|
189
|
+
memTotal = `${(totalMemBytes / (1024 ** 3)).toFixed(1)}GB`;
|
|
190
|
+
memFree = `${(freeBytes / (1024 ** 3)).toFixed(1)}GB`;
|
|
191
|
+
memUsed = `${(usedBytes / (1024 ** 3)).toFixed(1)}GB`;
|
|
192
|
+
}
|
|
193
|
+
// ── Disk ──
|
|
194
|
+
let diskFree = '?';
|
|
195
|
+
let diskUsed = '?';
|
|
196
|
+
let diskTotal = '?';
|
|
197
|
+
const dfRaw = exec('df -h / 2>/dev/null');
|
|
198
|
+
if (dfRaw) {
|
|
199
|
+
const dfLines = dfRaw.split('\n');
|
|
200
|
+
if (dfLines.length >= 2) {
|
|
201
|
+
const parts = dfLines[1].split(/\s+/);
|
|
202
|
+
// Filesystem Size Used Avail Use% Mounted
|
|
203
|
+
if (parts.length >= 4) {
|
|
204
|
+
diskTotal = parts[1];
|
|
205
|
+
diskUsed = parts[2];
|
|
206
|
+
diskFree = parts[3];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// ── Ollama ──
|
|
211
|
+
let ollamaStatus = 'offline';
|
|
212
|
+
let ollamaModels = [];
|
|
213
|
+
try {
|
|
214
|
+
const ollamaRaw = exec('curl -s --connect-timeout 2 http://localhost:11434/api/tags');
|
|
215
|
+
if (ollamaRaw) {
|
|
216
|
+
const data = JSON.parse(ollamaRaw);
|
|
217
|
+
if (data.models) {
|
|
218
|
+
ollamaStatus = 'online';
|
|
219
|
+
ollamaModels = data.models.map(m => m.name);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch { /* offline */ }
|
|
224
|
+
// ── kbot memory ──
|
|
225
|
+
const kbotMemDir = join(homedir(), '.kbot', 'memory');
|
|
226
|
+
let kbotMemorySize = '0B';
|
|
227
|
+
if (existsSync(kbotMemDir)) {
|
|
228
|
+
const duRaw = exec(`du -sh "${kbotMemDir}" 2>/dev/null`);
|
|
229
|
+
if (duRaw) {
|
|
230
|
+
kbotMemorySize = duRaw.split('\t')[0] || '0B';
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// ── Dream state ──
|
|
234
|
+
let dreamCycles = 0;
|
|
235
|
+
let dreamInsights = 0;
|
|
236
|
+
const dreamStateFile = join(homedir(), '.kbot', 'dream', 'state.json');
|
|
237
|
+
if (existsSync(dreamStateFile)) {
|
|
238
|
+
try {
|
|
239
|
+
const state = JSON.parse(readFileSync(dreamStateFile, 'utf-8'));
|
|
240
|
+
dreamCycles = state.cycles || 0;
|
|
241
|
+
dreamInsights = state.activeInsights || 0;
|
|
242
|
+
}
|
|
243
|
+
catch { /* ignore */ }
|
|
244
|
+
}
|
|
245
|
+
// ── Services ──
|
|
246
|
+
const services = getServiceStatus();
|
|
247
|
+
return {
|
|
248
|
+
loadAvg,
|
|
249
|
+
memFree,
|
|
250
|
+
memTotal,
|
|
251
|
+
memUsed,
|
|
252
|
+
diskFree,
|
|
253
|
+
diskUsed,
|
|
254
|
+
diskTotal,
|
|
255
|
+
ollamaStatus,
|
|
256
|
+
ollamaModels,
|
|
257
|
+
kbotMemorySize,
|
|
258
|
+
dreamCycles,
|
|
259
|
+
dreamInsights,
|
|
260
|
+
services,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// ── Tool Registration ──
|
|
264
|
+
export function registerWatchdogTools() {
|
|
265
|
+
// ── service_status ──
|
|
266
|
+
registerTool({
|
|
267
|
+
name: 'service_status',
|
|
268
|
+
description: 'List all kbot background services (launchd) with PID, status, CPU%, MEM%, and uptime. Checks email-agent, discovery daemon, kbot serve, discord bot, MLX server, collective sync, kbot-local MCP, and the main daemon.',
|
|
269
|
+
parameters: {},
|
|
270
|
+
tier: 'free',
|
|
271
|
+
timeout: 15_000,
|
|
272
|
+
execute: async () => {
|
|
273
|
+
const services = getServiceStatus();
|
|
274
|
+
if (services.length === 0) {
|
|
275
|
+
return 'No kbot services found in launchd. Run `kbot daemon start` to enable background services.';
|
|
276
|
+
}
|
|
277
|
+
const running = services.filter(s => s.status === 'running').length;
|
|
278
|
+
const total = services.length;
|
|
279
|
+
const lines = [
|
|
280
|
+
`KBOT SERVICES: ${running}/${total} running`,
|
|
281
|
+
'',
|
|
282
|
+
'SERVICE PID STATUS CPU MEM UPTIME',
|
|
283
|
+
'─'.repeat(72),
|
|
284
|
+
];
|
|
285
|
+
for (const s of services) {
|
|
286
|
+
const icon = s.status === 'running' ? '[OK]' : s.status === 'dead' ? '[!!]' : '[--]';
|
|
287
|
+
const pid = s.pid ? String(s.pid) : '-';
|
|
288
|
+
lines.push(`${icon} ${s.shortName.padEnd(18)} ${pid.padEnd(8)} ${s.status.padEnd(12)} ${s.cpu.padEnd(7)} ${s.mem.padEnd(7)} ${s.uptime}`);
|
|
289
|
+
}
|
|
290
|
+
return lines.join('\n');
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
// ── service_restart ──
|
|
294
|
+
registerTool({
|
|
295
|
+
name: 'service_restart',
|
|
296
|
+
description: 'Restart a kbot background service by name. Use short names: email-agent, discovery, serve, discord, mlx, collective-sync, daemon, kbot-local. Runs launchctl kickstart to restart the service.',
|
|
297
|
+
parameters: {
|
|
298
|
+
service: {
|
|
299
|
+
type: 'string',
|
|
300
|
+
description: 'Service short name: email-agent, discovery, serve, discord, mlx, collective-sync, daemon, kbot-local',
|
|
301
|
+
required: true,
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
tier: 'free',
|
|
305
|
+
timeout: 20_000,
|
|
306
|
+
execute: async (args) => {
|
|
307
|
+
const name = args.service;
|
|
308
|
+
if (!name)
|
|
309
|
+
return 'Error: service name required';
|
|
310
|
+
// Validate
|
|
311
|
+
const known = Object.keys(SERVICE_MAP);
|
|
312
|
+
if (!SERVICE_MAP[name] && !name.startsWith('com.kernel.')) {
|
|
313
|
+
return `Unknown service "${name}". Known services: ${known.join(', ')}`;
|
|
314
|
+
}
|
|
315
|
+
const result = restartService(name);
|
|
316
|
+
return result.success
|
|
317
|
+
? `OK: ${result.message}`
|
|
318
|
+
: `FAIL: ${result.message}`;
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
// ── system_health ──
|
|
322
|
+
registerTool({
|
|
323
|
+
name: 'system_health',
|
|
324
|
+
description: 'Full system health dashboard: CPU load, RAM, disk, Ollama models, kbot memory size, dream journal status, and all service statuses. Compact overview of everything running on this machine.',
|
|
325
|
+
parameters: {},
|
|
326
|
+
tier: 'free',
|
|
327
|
+
timeout: 20_000,
|
|
328
|
+
execute: async () => {
|
|
329
|
+
const h = getSystemHealth();
|
|
330
|
+
const running = h.services.filter(s => s.status === 'running').length;
|
|
331
|
+
const total = h.services.length;
|
|
332
|
+
const lines = [
|
|
333
|
+
'KBOT SYSTEM HEALTH',
|
|
334
|
+
'═══════════════════',
|
|
335
|
+
'',
|
|
336
|
+
`Services: ${running}/${total} running`,
|
|
337
|
+
`CPU Load: ${h.loadAvg}`,
|
|
338
|
+
`RAM: ${h.memUsed} used / ${h.memTotal} total (${h.memFree} free)`,
|
|
339
|
+
`Disk: ${h.diskUsed} used / ${h.diskTotal} total (${h.diskFree} free)`,
|
|
340
|
+
`Ollama: ${h.ollamaStatus}${h.ollamaModels.length > 0 ? ` — ${h.ollamaModels.length} models: ${h.ollamaModels.join(', ')}` : ''}`,
|
|
341
|
+
`Memory: ${h.kbotMemorySize}`,
|
|
342
|
+
`Dreams: ${h.dreamCycles} cycles, ${h.dreamInsights} active insights`,
|
|
343
|
+
'',
|
|
344
|
+
'SERVICES',
|
|
345
|
+
'─'.repeat(72),
|
|
346
|
+
];
|
|
347
|
+
for (const s of h.services) {
|
|
348
|
+
const icon = s.status === 'running' ? '[OK]' : s.status === 'dead' ? '[!!]' : '[--]';
|
|
349
|
+
const pid = s.pid ? String(s.pid) : '-';
|
|
350
|
+
lines.push(`${icon} ${s.shortName.padEnd(18)} PID ${pid.padEnd(8)} CPU ${s.cpu.padEnd(7)} MEM ${s.mem.padEnd(7)} up ${s.uptime}`);
|
|
351
|
+
}
|
|
352
|
+
return lines.join('\n');
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
//# sourceMappingURL=watchdog.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export interface BehaviorSnapshot {
|
|
2
|
+
/** ISO timestamp */
|
|
3
|
+
timestamp: string;
|
|
4
|
+
/** Hour of day (0-23) */
|
|
5
|
+
hour: number;
|
|
6
|
+
/** Day of week (0=Sunday, 6=Saturday) */
|
|
7
|
+
dayOfWeek: number;
|
|
8
|
+
/** Visible app names */
|
|
9
|
+
visibleApps: string[];
|
|
10
|
+
/** Active (frontmost) app name */
|
|
11
|
+
activeApp: string | null;
|
|
12
|
+
/** Active window title */
|
|
13
|
+
activeWindowTitle: string | null;
|
|
14
|
+
/** Number of connected screens */
|
|
15
|
+
screenCount: number;
|
|
16
|
+
/** Whether Ollama daemon is running */
|
|
17
|
+
ollamaRunning: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface BehaviorSummary {
|
|
20
|
+
/** Number of snapshots analyzed */
|
|
21
|
+
snapshotCount: number;
|
|
22
|
+
/** Hours covered */
|
|
23
|
+
hoursCovered: number;
|
|
24
|
+
/** Apps ranked by frequency (name → count) */
|
|
25
|
+
topApps: Array<{
|
|
26
|
+
app: string;
|
|
27
|
+
count: number;
|
|
28
|
+
percent: number;
|
|
29
|
+
}>;
|
|
30
|
+
/** Active hours (hour → snapshot count) */
|
|
31
|
+
activeHours: Record<number, number>;
|
|
32
|
+
/** App combinations seen together (sorted by frequency) */
|
|
33
|
+
appCombinations: Array<{
|
|
34
|
+
apps: string[];
|
|
35
|
+
count: number;
|
|
36
|
+
}>;
|
|
37
|
+
/** Average number of visible apps per snapshot */
|
|
38
|
+
avgVisibleApps: number;
|
|
39
|
+
/** Most common active app */
|
|
40
|
+
mostActiveApp: string | null;
|
|
41
|
+
/** Ollama usage rate (0-1) */
|
|
42
|
+
ollamaUsageRate: number;
|
|
43
|
+
/** Human-readable summary */
|
|
44
|
+
text: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Capture a behavior snapshot right now.
|
|
48
|
+
* Runs osascript to detect visible apps, active window, screen count, etc.
|
|
49
|
+
* Stores the snapshot to ~/.kbot/memory/behavior/ as a timestamped JSON file.
|
|
50
|
+
*
|
|
51
|
+
* Non-blocking-safe: catches all errors so it never crashes the caller.
|
|
52
|
+
* macOS only — returns null on other platforms.
|
|
53
|
+
*/
|
|
54
|
+
export declare function captureUserBehavior(): BehaviorSnapshot | null;
|
|
55
|
+
/**
|
|
56
|
+
* Read recent snapshots and produce a behavior summary.
|
|
57
|
+
* @param hours How many hours of history to analyze (default: 24)
|
|
58
|
+
*/
|
|
59
|
+
export declare function getBehaviorSummary(hours?: number): BehaviorSummary | null;
|
|
60
|
+
/**
|
|
61
|
+
* Get a compact text summary suitable for dream engine injection.
|
|
62
|
+
* Returns null if no data available.
|
|
63
|
+
*/
|
|
64
|
+
export declare function getBehaviorForDream(hours?: number): string | null;
|
|
65
|
+
//# sourceMappingURL=user-behavior.d.ts.map
|