audrey 0.16.1 → 0.17.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 +21 -21
- package/README.md +310 -643
- package/benchmarks/baselines.js +169 -0
- package/benchmarks/cases.js +421 -0
- package/benchmarks/reference-results.js +70 -0
- package/benchmarks/report.js +255 -0
- package/benchmarks/run.js +514 -0
- package/docs/assets/benchmarks/local-benchmark.svg +45 -0
- package/docs/assets/benchmarks/operations-benchmark.svg +45 -0
- package/docs/assets/benchmarks/published-memory-standards.svg +50 -0
- package/docs/benchmarking.md +151 -0
- package/docs/production-readiness.md +96 -0
- package/examples/fintech-ops-demo.js +67 -0
- package/examples/healthcare-ops-demo.js +67 -0
- package/examples/stripe-demo.js +105 -0
- package/mcp-server/config.js +80 -27
- package/mcp-server/index.js +611 -75
- package/mcp-server/serve.js +482 -0
- package/package.json +24 -5
- package/src/audrey.js +51 -13
- package/src/consolidate.js +70 -54
- package/src/db.js +22 -1
- package/src/embedding.js +16 -12
- package/src/encode.js +8 -2
- package/src/fts.js +134 -0
- package/src/import.js +28 -0
- package/src/llm.js +6 -3
- package/src/migrate.js +2 -2
- package/src/recall.js +253 -32
- package/src/utils.js +25 -0
- package/types/index.d.ts +434 -0
package/mcp-server/index.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
2
|
import { z } from 'zod';
|
|
5
3
|
import { homedir } from 'node:os';
|
|
6
4
|
import { join, resolve } from 'node:path';
|
|
7
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
8
6
|
import { execFileSync } from 'node:child_process';
|
|
9
7
|
import { fileURLToPath } from 'node:url';
|
|
10
8
|
import { Audrey } from '../src/index.js';
|
|
@@ -12,10 +10,11 @@ import { readStoredDimensions } from '../src/db.js';
|
|
|
12
10
|
import {
|
|
13
11
|
VERSION,
|
|
14
12
|
SERVER_NAME,
|
|
15
|
-
DEFAULT_DATA_DIR,
|
|
16
13
|
buildAudreyConfig,
|
|
17
14
|
buildInstallArgs,
|
|
15
|
+
resolveDataDir,
|
|
18
16
|
resolveEmbeddingProvider,
|
|
17
|
+
resolveLLMProvider,
|
|
19
18
|
} from './config.js';
|
|
20
19
|
|
|
21
20
|
const VALID_SOURCES = ['direct-observation', 'told-by-user', 'tool-result', 'inference', 'model-generated'];
|
|
@@ -50,6 +49,13 @@ export async function initializeEmbeddingProvider(provider) {
|
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
|
|
52
|
+
async function closeAudreyGracefully(audrey) {
|
|
53
|
+
if (audrey && typeof audrey.waitForIdle === 'function') {
|
|
54
|
+
await audrey.waitForIdle();
|
|
55
|
+
}
|
|
56
|
+
audrey?.close();
|
|
57
|
+
}
|
|
58
|
+
|
|
53
59
|
export const memoryEncodeToolSchema = {
|
|
54
60
|
content: z.string()
|
|
55
61
|
.max(MAX_MEMORY_CONTENT_LENGTH)
|
|
@@ -105,7 +111,7 @@ export const memoryForgetToolSchema = {
|
|
|
105
111
|
};
|
|
106
112
|
|
|
107
113
|
async function reembed() {
|
|
108
|
-
const dataDir = process.env
|
|
114
|
+
const dataDir = resolveDataDir(process.env);
|
|
109
115
|
const explicit = process.env.AUDREY_EMBEDDING_PROVIDER;
|
|
110
116
|
const embedding = resolveEmbeddingProvider(process.env, explicit);
|
|
111
117
|
const storedDims = readStoredDimensions(dataDir);
|
|
@@ -123,33 +129,12 @@ async function reembed() {
|
|
|
123
129
|
const counts = await reembedAll(audrey.db, audrey.embeddingProvider, { dropAndRecreate: dimensionsChanged });
|
|
124
130
|
console.log(`Done. Re-embedded: ${counts.episodes} episodes, ${counts.semantics} semantics, ${counts.procedures} procedures`);
|
|
125
131
|
} finally {
|
|
126
|
-
audrey
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function resolveLLMConfig() {
|
|
131
|
-
const explicit = process.env.AUDREY_LLM_PROVIDER;
|
|
132
|
-
if (explicit === 'anthropic') {
|
|
133
|
-
return { provider: 'anthropic', apiKey: process.env.ANTHROPIC_API_KEY };
|
|
132
|
+
await closeAudreyGracefully(audrey);
|
|
134
133
|
}
|
|
135
|
-
if (explicit === 'openai') {
|
|
136
|
-
return { provider: 'openai', apiKey: process.env.OPENAI_API_KEY };
|
|
137
|
-
}
|
|
138
|
-
if (explicit === 'mock') {
|
|
139
|
-
return { provider: 'mock' };
|
|
140
|
-
}
|
|
141
|
-
// Auto-detect: prefer anthropic, then openai
|
|
142
|
-
if (process.env.ANTHROPIC_API_KEY) {
|
|
143
|
-
return { provider: 'anthropic', apiKey: process.env.ANTHROPIC_API_KEY };
|
|
144
|
-
}
|
|
145
|
-
if (process.env.OPENAI_API_KEY) {
|
|
146
|
-
return { provider: 'openai', apiKey: process.env.OPENAI_API_KEY };
|
|
147
|
-
}
|
|
148
|
-
return null;
|
|
149
134
|
}
|
|
150
135
|
|
|
151
136
|
async function dream() {
|
|
152
|
-
const dataDir = process.env
|
|
137
|
+
const dataDir = resolveDataDir(process.env);
|
|
153
138
|
const explicit = process.env.AUDREY_EMBEDDING_PROVIDER;
|
|
154
139
|
const embedding = resolveEmbeddingProvider(process.env, explicit);
|
|
155
140
|
const storedDims = readStoredDimensions(dataDir);
|
|
@@ -160,7 +145,7 @@ async function dream() {
|
|
|
160
145
|
embedding,
|
|
161
146
|
};
|
|
162
147
|
|
|
163
|
-
const llm =
|
|
148
|
+
const llm = resolveLLMProvider(process.env, process.env.AUDREY_LLM_PROVIDER);
|
|
164
149
|
if (llm) config.llm = llm;
|
|
165
150
|
|
|
166
151
|
const audrey = new Audrey(config);
|
|
@@ -192,34 +177,52 @@ async function dream() {
|
|
|
192
177
|
);
|
|
193
178
|
console.log('[audrey] Dream complete.');
|
|
194
179
|
} finally {
|
|
195
|
-
audrey
|
|
180
|
+
await closeAudreyGracefully(audrey);
|
|
196
181
|
}
|
|
197
182
|
}
|
|
198
183
|
|
|
199
184
|
async function greeting() {
|
|
200
|
-
const dataDir = process.env
|
|
185
|
+
const dataDir = resolveDataDir(process.env);
|
|
186
|
+
const contextArg = process.argv[3] || undefined;
|
|
201
187
|
|
|
202
188
|
if (!existsSync(dataDir)) {
|
|
203
|
-
console.log('[audrey] No data yet
|
|
189
|
+
console.log('[audrey] No data yet - fresh start.');
|
|
204
190
|
return;
|
|
205
191
|
}
|
|
206
192
|
|
|
207
|
-
const
|
|
193
|
+
const storedDimensions = readStoredDimensions(dataDir);
|
|
194
|
+
const resolvedEmbedding = resolveEmbeddingProvider(process.env, process.env.AUDREY_EMBEDDING_PROVIDER);
|
|
195
|
+
const canUseResolvedEmbedding = Boolean(contextArg)
|
|
196
|
+
&& storedDimensions !== null
|
|
197
|
+
&& storedDimensions === resolvedEmbedding.dimensions;
|
|
198
|
+
const dimensions = storedDimensions || resolvedEmbedding.dimensions || 8;
|
|
208
199
|
const audrey = new Audrey({
|
|
209
200
|
dataDir,
|
|
210
201
|
agent: 'greeting',
|
|
211
|
-
embedding:
|
|
202
|
+
embedding: canUseResolvedEmbedding
|
|
203
|
+
? resolvedEmbedding
|
|
204
|
+
: { provider: 'mock', dimensions },
|
|
212
205
|
});
|
|
213
206
|
|
|
214
207
|
try {
|
|
215
|
-
|
|
216
|
-
|
|
208
|
+
if (canUseResolvedEmbedding) {
|
|
209
|
+
await initializeEmbeddingProvider(audrey.embeddingProvider);
|
|
210
|
+
}
|
|
211
|
+
const result = await audrey.greeting({ context: canUseResolvedEmbedding ? contextArg : undefined });
|
|
217
212
|
const health = audrey.memoryStatus();
|
|
218
213
|
|
|
219
214
|
const lines = [];
|
|
220
215
|
lines.push(`[Audrey v${VERSION}] Memory briefing`);
|
|
221
216
|
lines.push('');
|
|
222
217
|
|
|
218
|
+
if (contextArg && !canUseResolvedEmbedding) {
|
|
219
|
+
lines.push(
|
|
220
|
+
`Context recall skipped: stored index is ${storedDimensions ?? 'unknown'}d `
|
|
221
|
+
+ `but current embedding config resolves to ${resolvedEmbedding.dimensions}d.`
|
|
222
|
+
);
|
|
223
|
+
lines.push('');
|
|
224
|
+
}
|
|
225
|
+
|
|
223
226
|
// Mood
|
|
224
227
|
if (result.mood && result.mood.samples > 0) {
|
|
225
228
|
const v = result.mood.valence;
|
|
@@ -280,7 +283,7 @@ async function greeting() {
|
|
|
280
283
|
|
|
281
284
|
console.log(lines.join('\n'));
|
|
282
285
|
} finally {
|
|
283
|
-
audrey
|
|
286
|
+
await closeAudreyGracefully(audrey);
|
|
284
287
|
}
|
|
285
288
|
}
|
|
286
289
|
|
|
@@ -295,7 +298,7 @@ function timeSince(isoDate) {
|
|
|
295
298
|
}
|
|
296
299
|
|
|
297
300
|
async function reflect() {
|
|
298
|
-
const dataDir = process.env
|
|
301
|
+
const dataDir = resolveDataDir(process.env);
|
|
299
302
|
const explicit = process.env.AUDREY_EMBEDDING_PROVIDER;
|
|
300
303
|
const embedding = resolveEmbeddingProvider(process.env, explicit);
|
|
301
304
|
|
|
@@ -305,7 +308,7 @@ async function reflect() {
|
|
|
305
308
|
embedding,
|
|
306
309
|
};
|
|
307
310
|
|
|
308
|
-
const llm =
|
|
311
|
+
const llm = resolveLLMProvider(process.env, process.env.AUDREY_LLM_PROVIDER);
|
|
309
312
|
if (llm) config.llm = llm;
|
|
310
313
|
|
|
311
314
|
const audrey = new Audrey(config);
|
|
@@ -356,7 +359,340 @@ async function reflect() {
|
|
|
356
359
|
);
|
|
357
360
|
console.log('[audrey] Dream complete.');
|
|
358
361
|
} finally {
|
|
359
|
-
audrey
|
|
362
|
+
await closeAudreyGracefully(audrey);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function recall() {
|
|
367
|
+
const dataDir = resolveDataDir(process.env);
|
|
368
|
+
|
|
369
|
+
if (!existsSync(dataDir)) {
|
|
370
|
+
// No data yet — nothing to recall
|
|
371
|
+
process.exit(0);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Read hook JSON from stdin
|
|
375
|
+
let hookInput = null;
|
|
376
|
+
if (!process.stdin.isTTY) {
|
|
377
|
+
const chunks = [];
|
|
378
|
+
for await (const chunk of process.stdin) {
|
|
379
|
+
chunks.push(chunk);
|
|
380
|
+
}
|
|
381
|
+
const raw = Buffer.concat(chunks).toString('utf-8').trim();
|
|
382
|
+
if (raw) {
|
|
383
|
+
try {
|
|
384
|
+
hookInput = JSON.parse(raw);
|
|
385
|
+
} catch {
|
|
386
|
+
console.error('[audrey] Could not parse stdin as JSON');
|
|
387
|
+
process.exit(0);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Extract query from hook input or CLI arg
|
|
393
|
+
const query = hookInput?.prompt // UserPromptSubmit hook
|
|
394
|
+
|| hookInput?.query // direct query field
|
|
395
|
+
|| process.argv[3]; // CLI argument
|
|
396
|
+
|
|
397
|
+
if (!query || typeof query !== 'string' || !query.trim()) {
|
|
398
|
+
process.exit(0);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const storedDimensions = readStoredDimensions(dataDir);
|
|
402
|
+
const resolvedEmbedding = resolveEmbeddingProvider(process.env, process.env.AUDREY_EMBEDDING_PROVIDER);
|
|
403
|
+
const canEmbed = storedDimensions !== null && storedDimensions === resolvedEmbedding.dimensions;
|
|
404
|
+
|
|
405
|
+
if (!canEmbed) {
|
|
406
|
+
// Dimension mismatch — skip recall silently
|
|
407
|
+
process.exit(0);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const audrey = new Audrey({
|
|
411
|
+
dataDir,
|
|
412
|
+
agent: 'recall-hook',
|
|
413
|
+
embedding: resolvedEmbedding,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
try {
|
|
417
|
+
await initializeEmbeddingProvider(audrey.embeddingProvider);
|
|
418
|
+
|
|
419
|
+
const limit = parseInt(process.argv[4], 10) || 5;
|
|
420
|
+
const results = await audrey.recall(query.trim(), {
|
|
421
|
+
limit,
|
|
422
|
+
includePrivate: false,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
if (!results || results.length === 0) {
|
|
426
|
+
process.exit(0);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const lines = results.map(r => {
|
|
430
|
+
const type = r.type === 'semantic' ? 'principle' : r.type === 'procedural' ? 'procedure' : 'memory';
|
|
431
|
+
return `[${type}] ${r.content}`;
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
const output = {
|
|
435
|
+
additionalContext: `Relevant memories from Audrey:\n\n${lines.join('\n\n')}`,
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
console.log(JSON.stringify(output));
|
|
439
|
+
} finally {
|
|
440
|
+
await closeAudreyGracefully(audrey);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function buildHooksConfig({ scope = 'user' } = {}) {
|
|
445
|
+
const audreyBin = 'npx audrey';
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
SessionStart: [
|
|
449
|
+
{
|
|
450
|
+
matcher: 'startup|resume',
|
|
451
|
+
hooks: [
|
|
452
|
+
{
|
|
453
|
+
type: 'command',
|
|
454
|
+
command: `${audreyBin} greeting`,
|
|
455
|
+
timeout: 30,
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
},
|
|
459
|
+
],
|
|
460
|
+
UserPromptSubmit: [
|
|
461
|
+
{
|
|
462
|
+
matcher: '',
|
|
463
|
+
hooks: [
|
|
464
|
+
{
|
|
465
|
+
type: 'command',
|
|
466
|
+
command: `${audreyBin} recall`,
|
|
467
|
+
timeout: 15,
|
|
468
|
+
},
|
|
469
|
+
],
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
Stop: [
|
|
473
|
+
{
|
|
474
|
+
matcher: '',
|
|
475
|
+
hooks: [
|
|
476
|
+
{
|
|
477
|
+
type: 'command',
|
|
478
|
+
command: `${audreyBin} reflect`,
|
|
479
|
+
timeout: 120,
|
|
480
|
+
},
|
|
481
|
+
],
|
|
482
|
+
},
|
|
483
|
+
],
|
|
484
|
+
PostCompact: [
|
|
485
|
+
{
|
|
486
|
+
matcher: '',
|
|
487
|
+
hooks: [
|
|
488
|
+
{
|
|
489
|
+
type: 'command',
|
|
490
|
+
command: `${audreyBin} greeting`,
|
|
491
|
+
timeout: 30,
|
|
492
|
+
},
|
|
493
|
+
],
|
|
494
|
+
},
|
|
495
|
+
],
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function hooksInstall() {
|
|
500
|
+
const settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
501
|
+
const settingsDir = join(homedir(), '.claude');
|
|
502
|
+
|
|
503
|
+
let settings = {};
|
|
504
|
+
if (existsSync(settingsPath)) {
|
|
505
|
+
try {
|
|
506
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
507
|
+
} catch {
|
|
508
|
+
console.error(`[audrey] Could not parse ${settingsPath}. Please fix it manually.`);
|
|
509
|
+
process.exit(1);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const audreyHooks = buildHooksConfig();
|
|
514
|
+
|
|
515
|
+
if (!settings.hooks) {
|
|
516
|
+
settings.hooks = {};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Merge Audrey hooks with existing hooks, preserving user's existing hooks
|
|
520
|
+
for (const [event, audreyEntries] of Object.entries(audreyHooks)) {
|
|
521
|
+
if (!settings.hooks[event]) {
|
|
522
|
+
settings.hooks[event] = [];
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Remove any previously-installed Audrey hooks (by command match)
|
|
526
|
+
settings.hooks[event] = settings.hooks[event].filter(entry => {
|
|
527
|
+
if (!entry.hooks) return true;
|
|
528
|
+
return !entry.hooks.some(h => h.command && h.command.includes('npx audrey'));
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// Add Audrey hooks
|
|
532
|
+
settings.hooks[event].push(...audreyEntries);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
mkdirSync(settingsDir, { recursive: true });
|
|
536
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
537
|
+
|
|
538
|
+
console.log(`[audrey] Hooks installed in ${settingsPath}
|
|
539
|
+
|
|
540
|
+
Hooks configured:
|
|
541
|
+
SessionStart → npx audrey greeting (load identity, principles, mood)
|
|
542
|
+
UserPromptSubmit → npx audrey recall (semantic memory search per prompt)
|
|
543
|
+
Stop → npx audrey reflect (consolidate learnings + dream cycle)
|
|
544
|
+
PostCompact → npx audrey greeting (re-inject memories after compaction)
|
|
545
|
+
|
|
546
|
+
Verify: Open ${settingsPath} or run claude /hooks
|
|
547
|
+
`);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function hooksUninstall() {
|
|
551
|
+
const settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
552
|
+
|
|
553
|
+
if (!existsSync(settingsPath)) {
|
|
554
|
+
console.log('[audrey] No settings.json found. Nothing to remove.');
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
let settings;
|
|
559
|
+
try {
|
|
560
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
561
|
+
} catch {
|
|
562
|
+
console.error(`[audrey] Could not parse ${settingsPath}.`);
|
|
563
|
+
process.exit(1);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (!settings.hooks) {
|
|
567
|
+
console.log('[audrey] No hooks configured. Nothing to remove.');
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
let removed = 0;
|
|
572
|
+
for (const event of Object.keys(settings.hooks)) {
|
|
573
|
+
const before = settings.hooks[event].length;
|
|
574
|
+
settings.hooks[event] = settings.hooks[event].filter(entry => {
|
|
575
|
+
if (!entry.hooks) return true;
|
|
576
|
+
return !entry.hooks.some(h => h.command && h.command.includes('npx audrey'));
|
|
577
|
+
});
|
|
578
|
+
removed += before - settings.hooks[event].length;
|
|
579
|
+
|
|
580
|
+
// Clean up empty arrays
|
|
581
|
+
if (settings.hooks[event].length === 0) {
|
|
582
|
+
delete settings.hooks[event];
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Clean up empty hooks object
|
|
587
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
588
|
+
delete settings.hooks;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
592
|
+
console.log(`[audrey] Removed ${removed} hook(s) from ${settingsPath}`);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export function resolveSnapshotPath(outputArg, dataDir) {
|
|
596
|
+
if (outputArg) return resolve(outputArg);
|
|
597
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').slice(0, 19);
|
|
598
|
+
return resolve(dataDir, '..', `audrey-snapshot-${timestamp}.json`);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async function snapshot() {
|
|
602
|
+
const dataDir = resolveDataDir(process.env);
|
|
603
|
+
|
|
604
|
+
if (!existsSync(dataDir)) {
|
|
605
|
+
console.error('[audrey] No data directory found. Nothing to snapshot.');
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const storedDimensions = readStoredDimensions(dataDir);
|
|
610
|
+
const dimensions = storedDimensions || 8;
|
|
611
|
+
const audrey = new Audrey({
|
|
612
|
+
dataDir,
|
|
613
|
+
agent: 'snapshot',
|
|
614
|
+
embedding: { provider: 'mock', dimensions },
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
try {
|
|
618
|
+
const data = audrey.export();
|
|
619
|
+
const stats = audrey.introspect();
|
|
620
|
+
|
|
621
|
+
const outputPath = resolveSnapshotPath(process.argv[3], dataDir);
|
|
622
|
+
|
|
623
|
+
writeFileSync(outputPath, JSON.stringify(data, null, 2) + '\n');
|
|
624
|
+
|
|
625
|
+
console.log(`[audrey] Snapshot saved to ${outputPath}`);
|
|
626
|
+
console.log(` ${stats.episodic} episodes, ${stats.semantic} semantics, ${stats.procedural} procedures`);
|
|
627
|
+
console.log(` ${data.contradictions?.length || 0} contradictions, ${data.causalLinks?.length || 0} causal links`);
|
|
628
|
+
console.log(` Version: ${data.version}, exported at: ${data.exportedAt}`);
|
|
629
|
+
console.log('');
|
|
630
|
+
console.log('To restore: npx audrey restore ' + outputPath);
|
|
631
|
+
} finally {
|
|
632
|
+
await closeAudreyGracefully(audrey);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async function restore() {
|
|
637
|
+
const snapshotPath = process.argv[3];
|
|
638
|
+
if (!snapshotPath) {
|
|
639
|
+
console.error('Usage: npx audrey restore <snapshot-file>');
|
|
640
|
+
console.error(' e.g.: npx audrey restore audrey-snapshot-2026-03-24.json');
|
|
641
|
+
process.exit(1);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const resolvedPath = resolve(snapshotPath);
|
|
645
|
+
if (!existsSync(resolvedPath)) {
|
|
646
|
+
console.error(`[audrey] Snapshot file not found: ${resolvedPath}`);
|
|
647
|
+
process.exit(1);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
let data;
|
|
651
|
+
try {
|
|
652
|
+
data = JSON.parse(readFileSync(resolvedPath, 'utf-8'));
|
|
653
|
+
} catch {
|
|
654
|
+
console.error(`[audrey] Could not parse snapshot file: ${resolvedPath}`);
|
|
655
|
+
process.exit(1);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (!data.version || !data.episodes) {
|
|
659
|
+
console.error('[audrey] Invalid snapshot: missing version or episodes field.');
|
|
660
|
+
process.exit(1);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const dataDir = resolveDataDir(process.env);
|
|
664
|
+
const explicit = process.env.AUDREY_EMBEDDING_PROVIDER;
|
|
665
|
+
const embedding = resolveEmbeddingProvider(process.env, explicit);
|
|
666
|
+
|
|
667
|
+
const audrey = new Audrey({ dataDir, agent: 'restore', embedding });
|
|
668
|
+
|
|
669
|
+
try {
|
|
670
|
+
await initializeEmbeddingProvider(audrey.embeddingProvider);
|
|
671
|
+
|
|
672
|
+
const stats = audrey.introspect();
|
|
673
|
+
const isEmpty = stats.episodic === 0 && stats.semantic === 0 && stats.procedural === 0;
|
|
674
|
+
|
|
675
|
+
if (!isEmpty) {
|
|
676
|
+
const force = process.argv.includes('--force');
|
|
677
|
+
if (!force) {
|
|
678
|
+
console.error('[audrey] Database is not empty. Use --force to purge and restore.');
|
|
679
|
+
console.error(` Current: ${stats.episodic} episodes, ${stats.semantic} semantics, ${stats.procedural} procedures`);
|
|
680
|
+
process.exit(1);
|
|
681
|
+
}
|
|
682
|
+
console.log('[audrey] --force: purging existing memories before restore...');
|
|
683
|
+
audrey.purge();
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
console.log(`[audrey] Restoring from snapshot v${data.version} (${data.exportedAt || 'unknown date'})...`);
|
|
687
|
+
console.log(`[audrey] Re-embedding with ${embedding.provider} (${embedding.dimensions}d)...`);
|
|
688
|
+
|
|
689
|
+
await audrey.import(data);
|
|
690
|
+
|
|
691
|
+
const restored = audrey.introspect();
|
|
692
|
+
console.log(`[audrey] Restored: ${restored.episodic} episodes, ${restored.semantic} semantics, ${restored.procedural} procedures`);
|
|
693
|
+
console.log('[audrey] Restore complete.');
|
|
694
|
+
} finally {
|
|
695
|
+
await closeAudreyGracefully(audrey);
|
|
360
696
|
}
|
|
361
697
|
}
|
|
362
698
|
|
|
@@ -368,17 +704,27 @@ function install() {
|
|
|
368
704
|
process.exit(1);
|
|
369
705
|
}
|
|
370
706
|
|
|
371
|
-
const
|
|
707
|
+
const dataDir = resolveDataDir(process.env);
|
|
708
|
+
const resolvedEmbedding = resolveEmbeddingProvider(process.env, process.env.AUDREY_EMBEDDING_PROVIDER);
|
|
709
|
+
const resolvedLlm = resolveLLMProvider(process.env, process.env.AUDREY_LLM_PROVIDER);
|
|
372
710
|
if (resolvedEmbedding.provider === 'gemini') {
|
|
373
|
-
console.log('
|
|
711
|
+
console.log('Using Gemini embeddings (3072d)');
|
|
374
712
|
} else if (resolvedEmbedding.provider === 'local') {
|
|
375
|
-
console.log(
|
|
713
|
+
console.log(`Using local embeddings (384d, device=${resolvedEmbedding.device || 'gpu'})`);
|
|
376
714
|
} else if (resolvedEmbedding.provider === 'openai') {
|
|
377
|
-
console.log('Using
|
|
715
|
+
console.log('Using OpenAI embeddings (1536d)');
|
|
716
|
+
} else if (resolvedEmbedding.provider === 'mock') {
|
|
717
|
+
console.log('Using mock embeddings');
|
|
378
718
|
}
|
|
379
719
|
|
|
380
|
-
if (
|
|
381
|
-
console.log('
|
|
720
|
+
if (resolvedLlm?.provider === 'anthropic') {
|
|
721
|
+
console.log('Using Anthropic for LLM-powered consolidation, contradiction detection, and reflection');
|
|
722
|
+
} else if (resolvedLlm?.provider === 'openai') {
|
|
723
|
+
console.log('Using OpenAI for LLM-powered consolidation, contradiction detection, and reflection');
|
|
724
|
+
} else if (resolvedLlm?.provider === 'mock') {
|
|
725
|
+
console.log('Using mock LLM provider');
|
|
726
|
+
} else {
|
|
727
|
+
console.log('No LLM provider configured - consolidation and contradiction detection will use heuristics');
|
|
382
728
|
}
|
|
383
729
|
|
|
384
730
|
try {
|
|
@@ -417,12 +763,28 @@ CLI subcommands:
|
|
|
417
763
|
npx audrey install - Register MCP server with Claude Code
|
|
418
764
|
npx audrey uninstall - Remove MCP server registration
|
|
419
765
|
npx audrey status - Show memory store health and stats
|
|
766
|
+
npx audrey status --json - Emit machine-readable health output
|
|
767
|
+
npx audrey status --json --fail-on-unhealthy - Exit non-zero on unhealthy status
|
|
420
768
|
npx audrey greeting - Output session briefing (for hooks)
|
|
769
|
+
npx audrey recall - Semantic recall for hook context injection
|
|
421
770
|
npx audrey reflect - Reflect on conversation + dream cycle (for hooks)
|
|
422
771
|
npx audrey dream - Run consolidation + decay cycle
|
|
423
772
|
npx audrey reembed - Re-embed all memories with current provider
|
|
424
773
|
|
|
425
|
-
|
|
774
|
+
Versioning (git-friendly memory snapshots):
|
|
775
|
+
npx audrey snapshot [file] - Export memories to a JSON snapshot file
|
|
776
|
+
npx audrey restore <file> - Restore memories from a snapshot (--force to overwrite)
|
|
777
|
+
|
|
778
|
+
Hooks integration (automatic memory in every session):
|
|
779
|
+
npx audrey hooks install - Add Audrey hooks to ~/.claude/settings.json
|
|
780
|
+
npx audrey hooks uninstall - Remove Audrey hooks from settings
|
|
781
|
+
|
|
782
|
+
REST API server (any language, any framework):
|
|
783
|
+
npx audrey serve [port] - Start HTTP server (default: 3487)
|
|
784
|
+
AUDREY_API_KEY=secret npx audrey serve - Start with Bearer token auth
|
|
785
|
+
npx audrey dashboard - Start server and open memory dashboard
|
|
786
|
+
|
|
787
|
+
Data stored in: ${dataDir}
|
|
426
788
|
Verify: claude mcp list
|
|
427
789
|
`);
|
|
428
790
|
}
|
|
@@ -444,9 +806,15 @@ function uninstall() {
|
|
|
444
806
|
}
|
|
445
807
|
}
|
|
446
808
|
|
|
447
|
-
function
|
|
809
|
+
function cliHasFlag(flag, argv = process.argv) {
|
|
810
|
+
return Array.isArray(argv) && argv.includes(flag);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
export function buildStatusReport({
|
|
814
|
+
dataDir = resolveDataDir(process.env),
|
|
815
|
+
claudeJsonPath = join(homedir(), '.claude.json'),
|
|
816
|
+
} = {}) {
|
|
448
817
|
let registered = false;
|
|
449
|
-
const claudeJsonPath = join(homedir(), '.claude.json');
|
|
450
818
|
try {
|
|
451
819
|
const claudeConfig = JSON.parse(readFileSync(claudeJsonPath, 'utf-8'));
|
|
452
820
|
registered = SERVER_NAME in (claudeConfig.mcpServers || {});
|
|
@@ -454,41 +822,108 @@ function status() {
|
|
|
454
822
|
// Ignore unreadable config.
|
|
455
823
|
}
|
|
456
824
|
|
|
457
|
-
|
|
825
|
+
const report = {
|
|
826
|
+
generatedAt: new Date().toISOString(),
|
|
827
|
+
registered,
|
|
828
|
+
dataDir,
|
|
829
|
+
exists: existsSync(dataDir),
|
|
830
|
+
storedDimensions: null,
|
|
831
|
+
stats: null,
|
|
832
|
+
health: null,
|
|
833
|
+
lastConsolidation: null,
|
|
834
|
+
error: null,
|
|
835
|
+
};
|
|
458
836
|
|
|
459
|
-
if (!
|
|
460
|
-
|
|
461
|
-
return;
|
|
837
|
+
if (!report.exists) {
|
|
838
|
+
return report;
|
|
462
839
|
}
|
|
463
840
|
|
|
464
841
|
try {
|
|
465
|
-
|
|
842
|
+
report.storedDimensions = readStoredDimensions(dataDir);
|
|
843
|
+
const dimensions = report.storedDimensions || 8;
|
|
466
844
|
const audrey = new Audrey({
|
|
467
|
-
dataDir
|
|
845
|
+
dataDir,
|
|
468
846
|
agent: 'status-check',
|
|
469
847
|
embedding: { provider: 'mock', dimensions },
|
|
470
848
|
});
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
849
|
+
report.stats = audrey.introspect();
|
|
850
|
+
report.health = audrey.memoryStatus();
|
|
851
|
+
report.lastConsolidation = audrey.db.prepare(`
|
|
474
852
|
SELECT completed_at FROM consolidation_runs
|
|
475
853
|
WHERE status = 'completed'
|
|
476
854
|
ORDER BY completed_at DESC
|
|
477
855
|
LIMIT 1
|
|
478
856
|
`).get()?.completed_at || 'never';
|
|
479
857
|
audrey.close();
|
|
480
|
-
|
|
481
|
-
console.log(`Data directory: ${DEFAULT_DATA_DIR}`);
|
|
482
|
-
console.log(`Memories: ${stats.episodic} episodic, ${stats.semantic} semantic, ${stats.procedural} procedural`);
|
|
483
|
-
console.log(`Index sync: ${health.vec_episodes}/${health.searchable_episodes} episodic, ${health.vec_semantics}/${health.searchable_semantics} semantic, ${health.vec_procedures}/${health.searchable_procedures} procedural`);
|
|
484
|
-
console.log(`Health: ${health.healthy ? 'healthy' : 'unhealthy'}${health.reembed_recommended ? ' (re-embed recommended)' : ''}`);
|
|
485
|
-
console.log(`Dormant: ${stats.dormant}`);
|
|
486
|
-
console.log(`Causal links: ${stats.causalLinks}`);
|
|
487
|
-
console.log(`Contradictions: ${stats.contradictions.open} open, ${stats.contradictions.resolved} resolved`);
|
|
488
|
-
console.log(`Consolidation runs: ${stats.totalConsolidationRuns}`);
|
|
489
|
-
console.log(`Last consolidation: ${lastConsolidation}`);
|
|
490
858
|
} catch (err) {
|
|
491
|
-
|
|
859
|
+
report.error = err.message || String(err);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
return report;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
export function formatStatusReport(report) {
|
|
866
|
+
const lines = [];
|
|
867
|
+
lines.push(`Registration: ${report.registered ? 'active' : 'not registered'}`);
|
|
868
|
+
|
|
869
|
+
if (!report.exists) {
|
|
870
|
+
lines.push(`Data directory: ${report.dataDir} (not yet created - will be created on first use)`);
|
|
871
|
+
return lines.join('\n');
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (report.error) {
|
|
875
|
+
lines.push(`Data directory: ${report.dataDir} (exists but could not read: ${report.error})`);
|
|
876
|
+
return lines.join('\n');
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
lines.push(`Data directory: ${report.dataDir}`);
|
|
880
|
+
lines.push(`Stored dimensions: ${report.storedDimensions ?? 'unknown'}`);
|
|
881
|
+
lines.push(
|
|
882
|
+
`Memories: ${report.stats.episodic} episodic, ${report.stats.semantic} semantic, ${report.stats.procedural} procedural`
|
|
883
|
+
);
|
|
884
|
+
lines.push(
|
|
885
|
+
`Index sync: ${report.health.vec_episodes}/${report.health.searchable_episodes} episodic, `
|
|
886
|
+
+ `${report.health.vec_semantics}/${report.health.searchable_semantics} semantic, `
|
|
887
|
+
+ `${report.health.vec_procedures}/${report.health.searchable_procedures} procedural`
|
|
888
|
+
);
|
|
889
|
+
lines.push(
|
|
890
|
+
`Health: ${report.health.healthy ? 'healthy' : 'unhealthy'}`
|
|
891
|
+
+ `${report.health.reembed_recommended ? ' (re-embed recommended)' : ''}`
|
|
892
|
+
);
|
|
893
|
+
lines.push(`Dormant: ${report.stats.dormant}`);
|
|
894
|
+
lines.push(`Causal links: ${report.stats.causalLinks}`);
|
|
895
|
+
lines.push(`Contradictions: ${report.stats.contradictions.open} open, ${report.stats.contradictions.resolved} resolved`);
|
|
896
|
+
lines.push(`Consolidation runs: ${report.stats.totalConsolidationRuns}`);
|
|
897
|
+
lines.push(`Last consolidation: ${report.lastConsolidation}`);
|
|
898
|
+
|
|
899
|
+
return lines.join('\n');
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
export function runStatusCommand({
|
|
903
|
+
argv = process.argv,
|
|
904
|
+
dataDir = resolveDataDir(process.env),
|
|
905
|
+
claudeJsonPath = join(homedir(), '.claude.json'),
|
|
906
|
+
out = console.log,
|
|
907
|
+
} = {}) {
|
|
908
|
+
const report = buildStatusReport({ dataDir, claudeJsonPath });
|
|
909
|
+
if (cliHasFlag('--json', argv)) {
|
|
910
|
+
out(JSON.stringify(report, null, 2));
|
|
911
|
+
} else {
|
|
912
|
+
out(formatStatusReport(report));
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
const exitCode = report.error
|
|
916
|
+
|| (cliHasFlag('--fail-on-unhealthy', argv) && report.exists && report.health && !report.health.healthy)
|
|
917
|
+
? 1
|
|
918
|
+
: 0;
|
|
919
|
+
|
|
920
|
+
return { report, exitCode };
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function status() {
|
|
924
|
+
const { exitCode } = runStatusCommand();
|
|
925
|
+
if (exitCode !== 0) {
|
|
926
|
+
process.exitCode = exitCode;
|
|
492
927
|
}
|
|
493
928
|
}
|
|
494
929
|
|
|
@@ -500,6 +935,61 @@ function toolError(err) {
|
|
|
500
935
|
return { isError: true, content: [{ type: 'text', text: `Error: ${err.message || String(err)}` }] };
|
|
501
936
|
}
|
|
502
937
|
|
|
938
|
+
export function registerShutdownHandlers(processRef, audrey, logger = console.error) {
|
|
939
|
+
let closed = false;
|
|
940
|
+
|
|
941
|
+
const shutdown = (message, exitCode = 0) => {
|
|
942
|
+
if (message) {
|
|
943
|
+
logger(message);
|
|
944
|
+
}
|
|
945
|
+
if (!closed) {
|
|
946
|
+
closed = true;
|
|
947
|
+
if (typeof audrey?.waitForIdle === 'function') {
|
|
948
|
+
Promise.resolve(audrey.waitForIdle())
|
|
949
|
+
.catch(err => {
|
|
950
|
+
logger(`[audrey-mcp] shutdown wait error: ${err.message || String(err)}`);
|
|
951
|
+
exitCode = exitCode === 0 ? 1 : exitCode;
|
|
952
|
+
})
|
|
953
|
+
.finally(() => {
|
|
954
|
+
try {
|
|
955
|
+
audrey.close();
|
|
956
|
+
} catch (err) {
|
|
957
|
+
logger(`[audrey-mcp] shutdown error: ${err.message || String(err)}`);
|
|
958
|
+
exitCode = exitCode === 0 ? 1 : exitCode;
|
|
959
|
+
}
|
|
960
|
+
if (typeof processRef.exit === 'function') {
|
|
961
|
+
processRef.exit(exitCode);
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
try {
|
|
967
|
+
audrey.close();
|
|
968
|
+
} catch (err) {
|
|
969
|
+
logger(`[audrey-mcp] shutdown error: ${err.message || String(err)}`);
|
|
970
|
+
exitCode = exitCode === 0 ? 1 : exitCode;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (typeof processRef.exit === 'function') {
|
|
974
|
+
processRef.exit(exitCode);
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
processRef.once('SIGINT', () => shutdown('[audrey-mcp] received SIGINT, shutting down'));
|
|
979
|
+
processRef.once('SIGTERM', () => shutdown('[audrey-mcp] received SIGTERM, shutting down'));
|
|
980
|
+
processRef.once('SIGHUP', () => shutdown('[audrey-mcp] received SIGHUP, shutting down'));
|
|
981
|
+
processRef.once('uncaughtException', err => {
|
|
982
|
+
logger('[audrey-mcp] uncaught exception:', err);
|
|
983
|
+
shutdown(null, 1);
|
|
984
|
+
});
|
|
985
|
+
processRef.once('unhandledRejection', reason => {
|
|
986
|
+
logger('[audrey-mcp] unhandled rejection:', reason);
|
|
987
|
+
shutdown(null, 1);
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
return shutdown;
|
|
991
|
+
}
|
|
992
|
+
|
|
503
993
|
export function registerDreamTool(server, audrey) {
|
|
504
994
|
server.tool(
|
|
505
995
|
'memory_dream',
|
|
@@ -524,6 +1014,8 @@ export function registerDreamTool(server, audrey) {
|
|
|
524
1014
|
}
|
|
525
1015
|
|
|
526
1016
|
async function main() {
|
|
1017
|
+
const { McpServer } = await import('@modelcontextprotocol/sdk/server/mcp.js');
|
|
1018
|
+
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
|
527
1019
|
const config = buildAudreyConfig();
|
|
528
1020
|
const audrey = new Audrey(config);
|
|
529
1021
|
|
|
@@ -683,12 +1175,7 @@ async function main() {
|
|
|
683
1175
|
const transport = new StdioServerTransport();
|
|
684
1176
|
await server.connect(transport);
|
|
685
1177
|
console.error('[audrey-mcp] connected via stdio');
|
|
686
|
-
|
|
687
|
-
process.on('SIGINT', () => {
|
|
688
|
-
console.error('[audrey-mcp] shutting down');
|
|
689
|
-
audrey.close();
|
|
690
|
-
process.exit(0);
|
|
691
|
-
});
|
|
1178
|
+
registerShutdownHandlers(process, audrey);
|
|
692
1179
|
}
|
|
693
1180
|
|
|
694
1181
|
const isDirectRun = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
@@ -718,6 +1205,55 @@ if (isDirectRun) {
|
|
|
718
1205
|
console.error('[audrey] reflect failed:', err);
|
|
719
1206
|
process.exit(1);
|
|
720
1207
|
});
|
|
1208
|
+
} else if (subcommand === 'recall') {
|
|
1209
|
+
recall().catch(err => {
|
|
1210
|
+
console.error('[audrey] recall failed:', err);
|
|
1211
|
+
process.exit(1);
|
|
1212
|
+
});
|
|
1213
|
+
} else if (subcommand === 'hooks') {
|
|
1214
|
+
const hooksAction = process.argv[3];
|
|
1215
|
+
if (hooksAction === 'install') {
|
|
1216
|
+
hooksInstall();
|
|
1217
|
+
} else if (hooksAction === 'uninstall') {
|
|
1218
|
+
hooksUninstall();
|
|
1219
|
+
} else {
|
|
1220
|
+
console.error('Usage: npx audrey hooks [install|uninstall]');
|
|
1221
|
+
process.exit(1);
|
|
1222
|
+
}
|
|
1223
|
+
} else if (subcommand === 'snapshot') {
|
|
1224
|
+
snapshot().catch(err => {
|
|
1225
|
+
console.error('[audrey] snapshot failed:', err);
|
|
1226
|
+
process.exit(1);
|
|
1227
|
+
});
|
|
1228
|
+
} else if (subcommand === 'restore') {
|
|
1229
|
+
restore().catch(err => {
|
|
1230
|
+
console.error('[audrey] restore failed:', err);
|
|
1231
|
+
process.exit(1);
|
|
1232
|
+
});
|
|
1233
|
+
} else if (subcommand === 'serve') {
|
|
1234
|
+
import('./serve.js').then(({ startServer }) => {
|
|
1235
|
+
const port = process.argv[3] ? parseInt(process.argv[3], 10) : undefined;
|
|
1236
|
+
return startServer({ port });
|
|
1237
|
+
}).catch(err => {
|
|
1238
|
+
console.error('[audrey] serve failed:', err);
|
|
1239
|
+
process.exit(1);
|
|
1240
|
+
});
|
|
1241
|
+
} else if (subcommand === 'dashboard') {
|
|
1242
|
+
import('./serve.js').then(({ startServer }) => {
|
|
1243
|
+
const port = process.argv[3] ? parseInt(process.argv[3], 10) : undefined;
|
|
1244
|
+
return startServer({ port }).then(({ server }) => {
|
|
1245
|
+
const addr = server.address();
|
|
1246
|
+
const url = `http://localhost:${addr.port}/dashboard`;
|
|
1247
|
+
console.log(`[audrey] Opening dashboard: ${url}`);
|
|
1248
|
+
import('node:child_process').then(({ exec: execCmd }) => {
|
|
1249
|
+
const cmd = process.platform === 'win32' ? `start ${url}` : process.platform === 'darwin' ? `open ${url}` : `xdg-open ${url}`;
|
|
1250
|
+
execCmd(cmd);
|
|
1251
|
+
});
|
|
1252
|
+
});
|
|
1253
|
+
}).catch(err => {
|
|
1254
|
+
console.error('[audrey] dashboard failed:', err);
|
|
1255
|
+
process.exit(1);
|
|
1256
|
+
});
|
|
721
1257
|
} else if (subcommand === 'status') {
|
|
722
1258
|
status();
|
|
723
1259
|
} else {
|