audrey 0.20.0 → 0.21.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/CHANGELOG.md +15 -0
- package/README.md +170 -115
- package/dist/mcp-server/config.d.ts +25 -1
- package/dist/mcp-server/config.d.ts.map +1 -1
- package/dist/mcp-server/config.js +97 -12
- package/dist/mcp-server/config.js.map +1 -1
- package/dist/mcp-server/index.d.ts +83 -2
- package/dist/mcp-server/index.d.ts.map +1 -1
- package/dist/mcp-server/index.js +453 -36
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/src/audrey.d.ts +4 -0
- package/dist/src/audrey.d.ts.map +1 -1
- package/dist/src/audrey.js +12 -0
- package/dist/src/audrey.js.map +1 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/preflight.d.ts +51 -0
- package/dist/src/preflight.d.ts.map +1 -0
- package/dist/src/preflight.js +201 -0
- package/dist/src/preflight.js.map +1 -0
- package/dist/src/reflexes.d.ts +35 -0
- package/dist/src/reflexes.d.ts.map +1 -0
- package/dist/src/reflexes.js +87 -0
- package/dist/src/reflexes.js.map +1 -0
- package/dist/src/routes.d.ts.map +1 -1
- package/dist/src/routes.js +84 -7
- package/dist/src/routes.js.map +1 -1
- package/docs/assets/audrey-feature-grid.jpg +0 -0
- package/docs/assets/audrey-logo.svg +45 -0
- package/docs/assets/audrey-wordmark.png +0 -0
- package/docs/audrey-for-dummies.md +670 -0
- package/docs/future-of-llm-memory.md +452 -0
- package/docs/mcp-hosts.md +206 -0
- package/docs/ollama-local-agents.md +128 -0
- package/docs/production-readiness.md +11 -7
- package/examples/ollama-memory-agent.js +326 -0
- package/package.json +21 -2
package/dist/mcp-server/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
3
|
+
import { homedir, platform, tmpdir } from 'node:os';
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
5
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
6
6
|
import { execFileSync } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { Audrey } from '../src/index.js';
|
|
9
9
|
import { readStoredDimensions } from '../src/db.js';
|
|
10
|
-
import { VERSION, SERVER_NAME, buildAudreyConfig, buildInstallArgs, resolveDataDir, resolveEmbeddingProvider, resolveLLMProvider, } from './config.js';
|
|
10
|
+
import { VERSION, SERVER_NAME, MCP_ENTRYPOINT, buildAudreyConfig, buildInstallArgs, formatMcpHostConfig, resolveDataDir, resolveEmbeddingProvider, resolveLLMProvider, } from './config.js';
|
|
11
11
|
const VALID_SOURCES = {
|
|
12
12
|
'direct-observation': 'direct-observation',
|
|
13
13
|
'told-by-user': 'told-by-user',
|
|
@@ -93,6 +93,27 @@ export const memoryForgetToolSchema = {
|
|
|
93
93
|
min_similarity: z.number().min(0).max(1).optional().describe('Minimum similarity for query-based forget (default 0.9)'),
|
|
94
94
|
purge: z.boolean().optional().describe('Hard-delete the memory permanently (default false, soft-delete)'),
|
|
95
95
|
};
|
|
96
|
+
export const memoryPreflightToolSchema = {
|
|
97
|
+
action: z.string()
|
|
98
|
+
.refine(isNonEmptyText, 'Action must not be empty')
|
|
99
|
+
.describe('Natural-language description of the action the agent is about to take.'),
|
|
100
|
+
tool: z.string().optional().describe('Tool or command family about to be used, e.g. Bash, npm test, Edit, deploy.'),
|
|
101
|
+
session_id: z.string().optional().describe('Session identifier for grouping the optional preflight event.'),
|
|
102
|
+
cwd: z.string().optional().describe('Working directory for the action.'),
|
|
103
|
+
files: z.array(z.string()).optional().describe('File paths to fingerprint if record_event is true.'),
|
|
104
|
+
strict: z.boolean().optional().describe('If true, high-severity memory warnings produce decision=block instead of caution.'),
|
|
105
|
+
limit: z.number().int().min(1).max(50).optional().describe('Max recall results to consider before preflight categorization.'),
|
|
106
|
+
budget_chars: z.number().int().min(200).max(32000).optional().describe('Capsule budget in characters.'),
|
|
107
|
+
mode: z.enum(['balanced', 'conservative', 'aggressive']).optional().describe('Underlying capsule mode. Defaults to conservative.'),
|
|
108
|
+
failure_window_hours: z.number().int().min(1).max(8760).optional().describe('How far back to check failed tool events. Defaults to 168 hours.'),
|
|
109
|
+
include_status: z.boolean().optional().describe('Include memory health in the response and warning calculation. Defaults to true.'),
|
|
110
|
+
record_event: z.boolean().optional().describe('Record a redacted PreToolUse event for this preflight. Defaults to false.'),
|
|
111
|
+
include_capsule: z.boolean().optional().describe('If false, omit the embedded Memory Capsule from the response.'),
|
|
112
|
+
};
|
|
113
|
+
export const memoryReflexesToolSchema = {
|
|
114
|
+
...memoryPreflightToolSchema,
|
|
115
|
+
include_preflight: z.boolean().optional().describe('If true, include the full underlying preflight report.'),
|
|
116
|
+
};
|
|
96
117
|
// ---------------------------------------------------------------------------
|
|
97
118
|
// CLI subcommands
|
|
98
119
|
// ---------------------------------------------------------------------------
|
|
@@ -202,11 +223,14 @@ async function greeting() {
|
|
|
202
223
|
if (result.mood && result.mood.samples > 0) {
|
|
203
224
|
const v = result.mood.valence;
|
|
204
225
|
const moodWord = v > 0.3 ? 'positive' : v < -0.3 ? 'negative' : 'neutral';
|
|
205
|
-
lines.push(`Mood: ${moodWord} (valence=${v.toFixed(2)},
|
|
226
|
+
lines.push(`Mood: ${moodWord} (valence=${v.toFixed(2)}, `
|
|
227
|
+
+ `arousal=${result.mood.arousal.toFixed(2)}, `
|
|
228
|
+
+ `from ${result.mood.samples} recent memories)`);
|
|
206
229
|
}
|
|
207
230
|
// Health
|
|
208
231
|
const stats = audrey.introspect();
|
|
209
|
-
lines.push(`Memory: ${stats.episodic} episodic, ${stats.semantic} semantic,
|
|
232
|
+
lines.push(`Memory: ${stats.episodic} episodic, ${stats.semantic} semantic, `
|
|
233
|
+
+ `${stats.procedural} procedural | ${health.healthy ? 'healthy' : 'needs attention'}`);
|
|
210
234
|
lines.push('');
|
|
211
235
|
// Principles (semantic memories)
|
|
212
236
|
if (result.principles?.length > 0) {
|
|
@@ -323,7 +347,58 @@ async function reflect() {
|
|
|
323
347
|
audrey.close();
|
|
324
348
|
}
|
|
325
349
|
}
|
|
326
|
-
function
|
|
350
|
+
function parseInstallOptions(argv = process.argv) {
|
|
351
|
+
let host = 'claude-code';
|
|
352
|
+
let dryRun = false;
|
|
353
|
+
for (let i = 3; i < argv.length; i += 1) {
|
|
354
|
+
const arg = argv[i] ?? '';
|
|
355
|
+
if (arg === '--dry-run' || arg === '--print') {
|
|
356
|
+
dryRun = true;
|
|
357
|
+
}
|
|
358
|
+
else if (arg === '--host') {
|
|
359
|
+
host = argv[i + 1] || host;
|
|
360
|
+
i += 1;
|
|
361
|
+
}
|
|
362
|
+
else if (arg.startsWith('--host=')) {
|
|
363
|
+
host = arg.slice('--host='.length) || host;
|
|
364
|
+
}
|
|
365
|
+
else if (!arg.startsWith('-')) {
|
|
366
|
+
host = arg;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return { host, dryRun };
|
|
370
|
+
}
|
|
371
|
+
export function formatInstallGuide(host, env = process.env, dryRun = false) {
|
|
372
|
+
const normalizedHost = host || 'claude-code';
|
|
373
|
+
const title = dryRun || normalizedHost === 'claude-code'
|
|
374
|
+
? `Audrey install preview for ${normalizedHost}`
|
|
375
|
+
: `Audrey config-only install for ${normalizedHost}`;
|
|
376
|
+
const lines = [
|
|
377
|
+
title,
|
|
378
|
+
'',
|
|
379
|
+
'No host config files were modified.',
|
|
380
|
+
'',
|
|
381
|
+
'Generated MCP config:',
|
|
382
|
+
formatMcpHostConfig(normalizedHost, env),
|
|
383
|
+
'',
|
|
384
|
+
'Next steps:',
|
|
385
|
+
];
|
|
386
|
+
if (normalizedHost === 'claude-code') {
|
|
387
|
+
lines.push('- Run without --dry-run to register Audrey through Claude Code: npx audrey install --host claude-code');
|
|
388
|
+
lines.push('- Verify with: claude mcp list');
|
|
389
|
+
}
|
|
390
|
+
else if (normalizedHost === 'codex') {
|
|
391
|
+
lines.push('- Paste the TOML block into C:\\Users\\<you>\\.codex\\config.toml under the MCP server section.');
|
|
392
|
+
lines.push('- Restart Codex, then run: codex mcp list');
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
lines.push('- Paste the JSON block into your host MCP configuration.');
|
|
396
|
+
lines.push('- Restart the host and look for the audrey-memory MCP server.');
|
|
397
|
+
}
|
|
398
|
+
lines.push('- Run a local health check any time with: npx audrey doctor');
|
|
399
|
+
return lines.join('\n');
|
|
400
|
+
}
|
|
401
|
+
function installClaudeCode() {
|
|
327
402
|
try {
|
|
328
403
|
execFileSync('claude', ['--version'], { stdio: 'ignore' });
|
|
329
404
|
}
|
|
@@ -372,13 +447,13 @@ function install() {
|
|
|
372
447
|
console.error('Failed to register MCP server. Is Claude Code installed and on your PATH?');
|
|
373
448
|
process.exit(1);
|
|
374
449
|
}
|
|
375
|
-
console.log(`
|
|
376
|
-
Audrey registered as "${SERVER_NAME}" with Claude Code.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
memory_encode - Store observations, facts, preferences
|
|
380
|
-
memory_recall - Search memories by semantic similarity
|
|
381
|
-
memory_consolidate - Extract principles from accumulated episodes
|
|
450
|
+
console.log(`
|
|
451
|
+
Audrey registered as "${SERVER_NAME}" with Claude Code.
|
|
452
|
+
|
|
453
|
+
19 MCP tools available in every session:
|
|
454
|
+
memory_encode - Store observations, facts, preferences
|
|
455
|
+
memory_recall - Search memories by semantic similarity
|
|
456
|
+
memory_consolidate - Extract principles from accumulated episodes
|
|
382
457
|
memory_dream - Full sleep cycle: consolidate + decay + stats
|
|
383
458
|
memory_introspect - Check memory system health
|
|
384
459
|
memory_resolve_truth - Resolve contradictions between claims
|
|
@@ -386,25 +461,51 @@ Audrey registered as "${SERVER_NAME}" with Claude Code.
|
|
|
386
461
|
memory_import - Import a snapshot into a fresh database
|
|
387
462
|
memory_forget - Forget a specific memory by ID or query
|
|
388
463
|
memory_decay - Apply forgetting curves, transition low-confidence to dormant
|
|
389
|
-
memory_status - Check brain health (episode/vec sync, dimensions)
|
|
390
|
-
memory_reflect - Form lasting memories from a conversation
|
|
391
|
-
memory_greeting - Wake up as yourself: load identity, context, mood
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
464
|
+
memory_status - Check brain health (episode/vec sync, dimensions)
|
|
465
|
+
memory_reflect - Form lasting memories from a conversation
|
|
466
|
+
memory_greeting - Wake up as yourself: load identity, context, mood
|
|
467
|
+
memory_observe_tool - Record redacted tool-use events
|
|
468
|
+
memory_recent_failures - Inspect recent failed tool events
|
|
469
|
+
memory_capsule - Return a ranked, evidence-backed memory packet
|
|
470
|
+
memory_preflight - Check memory before an agent acts
|
|
471
|
+
memory_reflexes - Convert preflight evidence into trigger-response reflexes
|
|
472
|
+
memory_promote - Promote repeated lessons into project rules
|
|
473
|
+
|
|
474
|
+
CLI subcommands:
|
|
475
|
+
npx audrey demo - Run a 60-second local proof with no network calls
|
|
476
|
+
npx audrey doctor - Diagnose runtime, store health, and host config readiness
|
|
477
|
+
npx audrey install - Register MCP server with Claude Code
|
|
478
|
+
npx audrey install --host codex --dry-run - Print safe host setup instructions
|
|
479
|
+
npx audrey mcp-config codex - Print Codex MCP TOML
|
|
480
|
+
npx audrey mcp-config generic - Print JSON config for other MCP hosts
|
|
481
|
+
npx audrey uninstall - Remove MCP server registration
|
|
482
|
+
npx audrey status - Show memory store health and stats
|
|
397
483
|
npx audrey status --json - Emit machine-readable health output
|
|
398
484
|
npx audrey status --json --fail-on-unhealthy - Exit non-zero on unhealthy status
|
|
399
485
|
npx audrey greeting - Output session briefing (for hooks)
|
|
400
486
|
npx audrey reflect - Reflect on conversation + dream cycle (for hooks)
|
|
401
487
|
npx audrey dream - Run consolidation + decay cycle
|
|
402
|
-
npx audrey reembed - Re-embed all memories with current provider
|
|
403
|
-
|
|
404
|
-
Data stored in: ${dataDir}
|
|
405
|
-
Verify: claude mcp list
|
|
488
|
+
npx audrey reembed - Re-embed all memories with current provider
|
|
489
|
+
|
|
490
|
+
Data stored in: ${dataDir}
|
|
491
|
+
Verify: claude mcp list
|
|
406
492
|
`);
|
|
407
493
|
}
|
|
494
|
+
function install() {
|
|
495
|
+
const options = parseInstallOptions();
|
|
496
|
+
if (options.dryRun || options.host !== 'claude-code') {
|
|
497
|
+
try {
|
|
498
|
+
console.log(formatInstallGuide(options.host, process.env, options.dryRun));
|
|
499
|
+
}
|
|
500
|
+
catch (err) {
|
|
501
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
502
|
+
console.error(`[audrey] install failed: ${message}`);
|
|
503
|
+
process.exit(2);
|
|
504
|
+
}
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
installClaudeCode();
|
|
508
|
+
}
|
|
408
509
|
function uninstall() {
|
|
409
510
|
try {
|
|
410
511
|
execFileSync('claude', ['--version'], { stdio: 'ignore' });
|
|
@@ -422,6 +523,146 @@ function uninstall() {
|
|
|
422
523
|
process.exit(1);
|
|
423
524
|
}
|
|
424
525
|
}
|
|
526
|
+
function printMcpConfig() {
|
|
527
|
+
const host = process.argv[3] || 'generic';
|
|
528
|
+
try {
|
|
529
|
+
console.log(formatMcpHostConfig(host, process.env));
|
|
530
|
+
}
|
|
531
|
+
catch (err) {
|
|
532
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
533
|
+
console.error(`[audrey] mcp-config failed: ${message}`);
|
|
534
|
+
process.exit(2);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function sectionTitle(section) {
|
|
538
|
+
return section.replace(/_/g, ' ');
|
|
539
|
+
}
|
|
540
|
+
function createDemoDir() {
|
|
541
|
+
const preferredParent = process.env['AUDREY_DEMO_PARENT_DIR'] || tmpdir();
|
|
542
|
+
try {
|
|
543
|
+
return mkdtempSync(join(preferredParent, 'audrey-demo-'));
|
|
544
|
+
}
|
|
545
|
+
catch {
|
|
546
|
+
const fallbackParent = join(process.cwd(), '.audrey-demo-tmp');
|
|
547
|
+
mkdirSync(fallbackParent, { recursive: true });
|
|
548
|
+
return mkdtempSync(join(fallbackParent, 'run-'));
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
export async function runDemoCommand({ out = console.log, keep = process.argv.includes('--keep'), } = {}) {
|
|
552
|
+
const demoDir = createDemoDir();
|
|
553
|
+
const audrey = new Audrey({
|
|
554
|
+
dataDir: demoDir,
|
|
555
|
+
agent: 'audrey-demo',
|
|
556
|
+
embedding: { provider: 'mock', dimensions: 64 },
|
|
557
|
+
llm: { provider: 'mock' },
|
|
558
|
+
});
|
|
559
|
+
try {
|
|
560
|
+
out('Audrey 60-second memory demo');
|
|
561
|
+
out('');
|
|
562
|
+
out(`Memory store: ${demoDir}`);
|
|
563
|
+
out('Writing memories that could have come from Codex, Claude, or an Ollama agent...');
|
|
564
|
+
const ids = [];
|
|
565
|
+
ids.push(await audrey.encode({
|
|
566
|
+
content: 'Audrey should work across Codex, Claude Code, Claude Desktop, Cursor, and Ollama-backed local agents.',
|
|
567
|
+
source: 'direct-observation',
|
|
568
|
+
tags: ['must-follow', 'host-neutral', 'codex', 'ollama'],
|
|
569
|
+
}));
|
|
570
|
+
ids.push(await audrey.encode({
|
|
571
|
+
content: 'Before an agent starts work, ask Audrey for a Memory Capsule and include the capsule in the model context.',
|
|
572
|
+
source: 'direct-observation',
|
|
573
|
+
tags: ['procedure', 'memory-capsule', 'agent-loop'],
|
|
574
|
+
}));
|
|
575
|
+
ids.push(await audrey.encode({
|
|
576
|
+
content: 'If a host cannot auto-install Audrey, run npx audrey mcp-config codex '
|
|
577
|
+
+ 'or npx audrey mcp-config generic and paste the generated config.',
|
|
578
|
+
source: 'direct-observation',
|
|
579
|
+
tags: ['procedure', 'mcp', 'first-contact'],
|
|
580
|
+
}));
|
|
581
|
+
ids.push(await audrey.encode({
|
|
582
|
+
content: 'Repeated tool failures should become procedural warnings before the agent retries the same risky action.',
|
|
583
|
+
source: 'direct-observation',
|
|
584
|
+
tags: ['risk', 'procedure', 'tool-trace'],
|
|
585
|
+
}));
|
|
586
|
+
ids.push(await audrey.encode({
|
|
587
|
+
content: 'Memory Reflexes turn preflight evidence into trigger-response rules an agent can follow before tool use.',
|
|
588
|
+
source: 'direct-observation',
|
|
589
|
+
tags: ['procedure', 'memory-reflexes', 'agent-loop'],
|
|
590
|
+
}));
|
|
591
|
+
const event = audrey.observeTool({
|
|
592
|
+
event: 'PostToolUse',
|
|
593
|
+
tool: 'npm test',
|
|
594
|
+
outcome: 'failed',
|
|
595
|
+
errorSummary: 'Vitest can fail with spawn EPERM on locked-down Windows hosts; '
|
|
596
|
+
+ 'use build, typecheck, benchmarks, and direct dist smokes as the fallback evidence path.',
|
|
597
|
+
cwd: process.cwd(),
|
|
598
|
+
metadata: { demo: true, source: 'audrey demo' },
|
|
599
|
+
});
|
|
600
|
+
out(`Encoded ${ids.length} memories and 1 redacted tool trace (${event.event.id}).`);
|
|
601
|
+
out('');
|
|
602
|
+
const query = 'How should an agent use Audrey with Codex and Ollama?';
|
|
603
|
+
out(`Asking Audrey for a Memory Capsule: "${query}"`);
|
|
604
|
+
const capsule = await audrey.capsule(query, {
|
|
605
|
+
limit: 8,
|
|
606
|
+
budgetChars: 2400,
|
|
607
|
+
includeRisks: true,
|
|
608
|
+
includeContradictions: true,
|
|
609
|
+
});
|
|
610
|
+
out('');
|
|
611
|
+
out('Capsule highlights:');
|
|
612
|
+
let printed = 0;
|
|
613
|
+
for (const [name, entries] of Object.entries(capsule.sections)) {
|
|
614
|
+
if (!Array.isArray(entries) || entries.length === 0)
|
|
615
|
+
continue;
|
|
616
|
+
printed += 1;
|
|
617
|
+
out(`- ${sectionTitle(name)}:`);
|
|
618
|
+
for (const entry of entries.slice(0, 2)) {
|
|
619
|
+
out(` * ${entry.content}`);
|
|
620
|
+
out(` why: ${entry.reason}`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (printed === 0) {
|
|
624
|
+
out('- No capsule sections were populated. That is unexpected for this demo.');
|
|
625
|
+
}
|
|
626
|
+
const reflexReport = await audrey.reflexes('run npm test before release', {
|
|
627
|
+
tool: 'npm test',
|
|
628
|
+
includePreflight: false,
|
|
629
|
+
});
|
|
630
|
+
out('');
|
|
631
|
+
out('Memory Reflex proof:');
|
|
632
|
+
const demoReflexes = [...reflexReport.reflexes].sort((a, b) => {
|
|
633
|
+
if (a.source === 'recent_failure' && b.source !== 'recent_failure')
|
|
634
|
+
return -1;
|
|
635
|
+
if (b.source === 'recent_failure' && a.source !== 'recent_failure')
|
|
636
|
+
return 1;
|
|
637
|
+
return 0;
|
|
638
|
+
});
|
|
639
|
+
for (const reflex of demoReflexes.slice(0, 3)) {
|
|
640
|
+
out(`- ${reflex.trigger}`);
|
|
641
|
+
out(` ${reflex.response_type}: ${reflex.response}`);
|
|
642
|
+
}
|
|
643
|
+
const recall = await audrey.recall('Codex Ollama Memory Capsule host install', { limit: 3 });
|
|
644
|
+
out('');
|
|
645
|
+
out('Recall proof:');
|
|
646
|
+
for (const memory of recall.slice(0, 3)) {
|
|
647
|
+
out(`- [${memory.type}] ${(memory.confidence * 100).toFixed(0)}% ${memory.content}`);
|
|
648
|
+
}
|
|
649
|
+
out('');
|
|
650
|
+
out('Next steps:');
|
|
651
|
+
out('- Diagnose your setup: npx audrey doctor');
|
|
652
|
+
out('- Codex: npx audrey mcp-config codex');
|
|
653
|
+
out('- Any stdio MCP host: npx audrey mcp-config generic');
|
|
654
|
+
out('- Ollama/local agents: npx audrey serve, then call /v1/reflexes, /v1/capsule, and /v1/recall as tools');
|
|
655
|
+
if (keep) {
|
|
656
|
+
out(`- Demo data kept at: ${demoDir}`);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
finally {
|
|
660
|
+
audrey.close();
|
|
661
|
+
if (!keep) {
|
|
662
|
+
rmSync(demoDir, { recursive: true, force: true });
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
425
666
|
function cliHasFlag(flag, argv = process.argv) {
|
|
426
667
|
return Array.isArray(argv) && argv.includes(flag);
|
|
427
668
|
}
|
|
@@ -511,12 +752,128 @@ export function runStatusCommand({ argv = process.argv, dataDir = resolveDataDir
|
|
|
511
752
|
: 0;
|
|
512
753
|
return { report, exitCode };
|
|
513
754
|
}
|
|
755
|
+
function describeEmbedding(env) {
|
|
756
|
+
const embedding = resolveEmbeddingProvider(env, env['AUDREY_EMBEDDING_PROVIDER']);
|
|
757
|
+
if (embedding.provider === 'local') {
|
|
758
|
+
return `local (${embedding.dimensions}d, device=${embedding.device || 'gpu'})`;
|
|
759
|
+
}
|
|
760
|
+
return `${embedding.provider} (${embedding.dimensions}d)`;
|
|
761
|
+
}
|
|
762
|
+
function describeLlm(env) {
|
|
763
|
+
const llm = resolveLLMProvider(env, env['AUDREY_LLM_PROVIDER']);
|
|
764
|
+
return llm ? llm.provider : 'not configured (heuristic mode)';
|
|
765
|
+
}
|
|
766
|
+
function addDoctorCheck(checks, name, ok, severity, message, hint) {
|
|
767
|
+
checks.push({ name, ok, severity, message, ...(hint ? { hint } : {}) });
|
|
768
|
+
}
|
|
769
|
+
export function buildDoctorReport({ dataDir = resolveDataDir(process.env), claudeJsonPath = join(homedir(), '.claude.json'), env = process.env, nodeVersion = process.versions.node, } = {}) {
|
|
770
|
+
const checks = [];
|
|
771
|
+
const statusReport = buildStatusReport({ dataDir, claudeJsonPath });
|
|
772
|
+
const major = Number.parseInt(nodeVersion.split('.')[0] || '0', 10);
|
|
773
|
+
const entrypointExists = existsSync(MCP_ENTRYPOINT);
|
|
774
|
+
addDoctorCheck(checks, 'node-runtime', major >= 20, major >= 20 ? 'info' : 'error', `Node.js ${nodeVersion}`, major >= 20 ? undefined : 'Install Node.js 20 or newer.');
|
|
775
|
+
addDoctorCheck(checks, 'mcp-entrypoint', entrypointExists, entrypointExists ? 'info' : 'error', MCP_ENTRYPOINT, entrypointExists ? undefined : 'Run npm run build before launching Audrey from this checkout.');
|
|
776
|
+
let embedding = 'invalid';
|
|
777
|
+
try {
|
|
778
|
+
embedding = describeEmbedding(env);
|
|
779
|
+
addDoctorCheck(checks, 'embedding-provider', true, 'info', embedding);
|
|
780
|
+
}
|
|
781
|
+
catch (err) {
|
|
782
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
783
|
+
addDoctorCheck(checks, 'embedding-provider', false, 'error', message, 'Check AUDREY_EMBEDDING_PROVIDER.');
|
|
784
|
+
}
|
|
785
|
+
let llm = 'not configured (heuristic mode)';
|
|
786
|
+
try {
|
|
787
|
+
llm = describeLlm(env);
|
|
788
|
+
addDoctorCheck(checks, 'llm-provider', true, 'info', llm);
|
|
789
|
+
}
|
|
790
|
+
catch (err) {
|
|
791
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
792
|
+
addDoctorCheck(checks, 'llm-provider', false, 'error', message, 'Check AUDREY_LLM_PROVIDER.');
|
|
793
|
+
}
|
|
794
|
+
if (!statusReport.exists) {
|
|
795
|
+
addDoctorCheck(checks, 'memory-store', true, 'info', `${dataDir} is not created yet`, 'Run npx audrey demo or connect a host to create the store.');
|
|
796
|
+
}
|
|
797
|
+
else if (statusReport.error) {
|
|
798
|
+
addDoctorCheck(checks, 'memory-store', false, 'error', statusReport.error, 'Run npx audrey status --json for details.');
|
|
799
|
+
}
|
|
800
|
+
else if (!statusReport.health) {
|
|
801
|
+
addDoctorCheck(checks, 'memory-store', false, 'error', 'memory store health could not be read');
|
|
802
|
+
}
|
|
803
|
+
else if (statusReport.health && !statusReport.health.healthy) {
|
|
804
|
+
addDoctorCheck(checks, 'memory-store', false, 'error', 'memory vectors are out of sync', 'Run npx audrey reembed.');
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
addDoctorCheck(checks, 'memory-store', true, 'info', 'healthy');
|
|
808
|
+
}
|
|
809
|
+
try {
|
|
810
|
+
formatMcpHostConfig('codex', env);
|
|
811
|
+
formatMcpHostConfig('generic', env);
|
|
812
|
+
addDoctorCheck(checks, 'host-config-generation', true, 'info', 'codex TOML and generic JSON can be generated');
|
|
813
|
+
}
|
|
814
|
+
catch (err) {
|
|
815
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
816
|
+
addDoctorCheck(checks, 'host-config-generation', false, 'error', message);
|
|
817
|
+
}
|
|
818
|
+
const ok = checks.every(check => check.ok || check.severity !== 'error');
|
|
819
|
+
return {
|
|
820
|
+
generatedAt: new Date().toISOString(),
|
|
821
|
+
version: VERSION,
|
|
822
|
+
node: nodeVersion,
|
|
823
|
+
platform: platform(),
|
|
824
|
+
entrypoint: MCP_ENTRYPOINT,
|
|
825
|
+
dataDir,
|
|
826
|
+
embedding,
|
|
827
|
+
llm,
|
|
828
|
+
status: statusReport,
|
|
829
|
+
checks,
|
|
830
|
+
ok,
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
export function formatDoctorReport(report) {
|
|
834
|
+
const lines = [
|
|
835
|
+
`Audrey Doctor v${report.version}`,
|
|
836
|
+
`Runtime: Node.js ${report.node} on ${report.platform}`,
|
|
837
|
+
`MCP entrypoint: ${report.entrypoint}`,
|
|
838
|
+
`Data directory: ${report.dataDir}`,
|
|
839
|
+
`Embedding: ${report.embedding}`,
|
|
840
|
+
`LLM: ${report.llm}`,
|
|
841
|
+
`Store health: ${report.status.exists ? (report.status.health?.healthy ? 'healthy' : 'needs attention') : 'not initialized'}`,
|
|
842
|
+
'',
|
|
843
|
+
'Checks:',
|
|
844
|
+
];
|
|
845
|
+
for (const check of report.checks) {
|
|
846
|
+
const marker = check.ok ? 'OK' : check.severity.toUpperCase();
|
|
847
|
+
lines.push(`- [${marker}] ${check.name}: ${check.message}`);
|
|
848
|
+
if (check.hint)
|
|
849
|
+
lines.push(` hint: ${check.hint}`);
|
|
850
|
+
}
|
|
851
|
+
lines.push('');
|
|
852
|
+
lines.push(`Verdict: ${report.ok ? 'ready' : 'blocked'}`);
|
|
853
|
+
lines.push('');
|
|
854
|
+
lines.push('Next steps:');
|
|
855
|
+
lines.push('- Prove local behavior: npx audrey demo');
|
|
856
|
+
lines.push('- Preview host setup: npx audrey install --host codex --dry-run');
|
|
857
|
+
lines.push('- Emit automation JSON: npx audrey doctor --json');
|
|
858
|
+
return lines.join('\n');
|
|
859
|
+
}
|
|
860
|
+
export function runDoctorCommand({ argv = process.argv, dataDir = resolveDataDir(process.env), claudeJsonPath = join(homedir(), '.claude.json'), env = process.env, out = console.log, } = {}) {
|
|
861
|
+
const report = buildDoctorReport({ dataDir, claudeJsonPath, env });
|
|
862
|
+
out(cliHasFlag('--json', argv) ? JSON.stringify(report, null, 2) : formatDoctorReport(report));
|
|
863
|
+
return { report, exitCode: report.ok ? 0 : 1 };
|
|
864
|
+
}
|
|
514
865
|
function status() {
|
|
515
866
|
const { exitCode } = runStatusCommand();
|
|
516
867
|
if (exitCode !== 0) {
|
|
517
868
|
process.exitCode = exitCode;
|
|
518
869
|
}
|
|
519
870
|
}
|
|
871
|
+
function doctor() {
|
|
872
|
+
const { exitCode } = runDoctorCommand();
|
|
873
|
+
if (exitCode !== 0) {
|
|
874
|
+
process.exitCode = exitCode;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
520
877
|
function toolResult(data) {
|
|
521
878
|
return { content: [{ type: 'text', text: JSON.stringify(data) }] };
|
|
522
879
|
}
|
|
@@ -599,7 +956,7 @@ async function main() {
|
|
|
599
956
|
return toolError(err);
|
|
600
957
|
}
|
|
601
958
|
});
|
|
602
|
-
server.tool('memory_recall', memoryRecallToolSchema, async ({ query, limit, types, min_confidence, tags, sources, after, before, context, mood }) => {
|
|
959
|
+
server.tool('memory_recall', memoryRecallToolSchema, async ({ query, limit, types, min_confidence, tags, sources, after, before, context, mood, }) => {
|
|
603
960
|
try {
|
|
604
961
|
const results = await audrey.recall(query, {
|
|
605
962
|
limit: limit ?? 10,
|
|
@@ -723,7 +1080,7 @@ async function main() {
|
|
|
723
1080
|
});
|
|
724
1081
|
registerDreamTool(server, audrey);
|
|
725
1082
|
server.tool('memory_greeting', {
|
|
726
|
-
context: z.string().optional().describe('Optional hint about this session
|
|
1083
|
+
context: z.string().optional().describe('Optional hint about this session. When provided, Audrey also returns semantically relevant memories.'),
|
|
727
1084
|
}, async ({ context }) => {
|
|
728
1085
|
try {
|
|
729
1086
|
return toolResult(await audrey.greeting({ context }));
|
|
@@ -736,7 +1093,7 @@ async function main() {
|
|
|
736
1093
|
event: z.string().describe('Hook event name (PreToolUse, PostToolUse, PostToolUseFailure, PreCompact, PostCompact, etc.)'),
|
|
737
1094
|
tool: z.string().describe('Tool name being observed (Bash, Edit, Write, etc.)'),
|
|
738
1095
|
session_id: z.string().optional().describe('Session identifier for grouping related events'),
|
|
739
|
-
input: z.unknown().optional().describe('Tool input. Hashed and never stored raw; redacted
|
|
1096
|
+
input: z.unknown().optional().describe('Tool input. Hashed and never stored raw; redacted metadata is only stored when retain_details is true.'),
|
|
740
1097
|
output: z.unknown().optional().describe('Tool output. Same redaction and storage policy as input.'),
|
|
741
1098
|
outcome: z.enum(['succeeded', 'failed', 'blocked', 'skipped', 'unknown']).optional().describe('Outcome classification'),
|
|
742
1099
|
error_summary: z.string().optional().describe('Short error description if the tool failed. Redacted and truncated to 2 KB.'),
|
|
@@ -744,7 +1101,7 @@ async function main() {
|
|
|
744
1101
|
files: z.array(z.string()).optional().describe('File paths to fingerprint (size + mtime + content hash)'),
|
|
745
1102
|
metadata: z.record(z.string(), z.unknown()).optional().describe('Arbitrary structured metadata (redacted before storage)'),
|
|
746
1103
|
retain_details: z.boolean().optional().describe('If true, redacted input and output payloads are stored alongside hashes. Defaults to false.'),
|
|
747
|
-
}, async ({ event, tool, session_id, input, output, outcome, error_summary, cwd, files, metadata, retain_details }) => {
|
|
1104
|
+
}, async ({ event, tool, session_id, input, output, outcome, error_summary, cwd, files, metadata, retain_details, }) => {
|
|
748
1105
|
try {
|
|
749
1106
|
const result = audrey.observeTool({
|
|
750
1107
|
event,
|
|
@@ -792,7 +1149,7 @@ async function main() {
|
|
|
792
1149
|
recent_change_window_hours: z.number().int().min(1).max(720).optional().describe('How far back "recent_changes" looks (default 24h).'),
|
|
793
1150
|
include_risks: z.boolean().optional().describe('Include recent tool failures as risks (default true).'),
|
|
794
1151
|
include_contradictions: z.boolean().optional().describe('Include open contradictions (default true).'),
|
|
795
|
-
}, async ({ query, limit, budget_chars, mode, recent_change_window_hours, include_risks, include_contradictions }) => {
|
|
1152
|
+
}, async ({ query, limit, budget_chars, mode, recent_change_window_hours, include_risks, include_contradictions, }) => {
|
|
796
1153
|
try {
|
|
797
1154
|
const capsule = await audrey.capsule(query, {
|
|
798
1155
|
limit,
|
|
@@ -808,15 +1165,60 @@ async function main() {
|
|
|
808
1165
|
return toolError(err);
|
|
809
1166
|
}
|
|
810
1167
|
});
|
|
1168
|
+
server.tool('memory_preflight', memoryPreflightToolSchema, async ({ action, tool, session_id, cwd, files, strict, limit, budget_chars, mode, failure_window_hours, include_status, record_event, include_capsule, }) => {
|
|
1169
|
+
try {
|
|
1170
|
+
const preflight = await audrey.preflight(action, {
|
|
1171
|
+
tool,
|
|
1172
|
+
sessionId: session_id,
|
|
1173
|
+
cwd,
|
|
1174
|
+
files,
|
|
1175
|
+
strict,
|
|
1176
|
+
limit,
|
|
1177
|
+
budgetChars: budget_chars,
|
|
1178
|
+
mode,
|
|
1179
|
+
recentFailureWindowHours: failure_window_hours,
|
|
1180
|
+
includeStatus: include_status,
|
|
1181
|
+
recordEvent: record_event,
|
|
1182
|
+
includeCapsule: include_capsule,
|
|
1183
|
+
});
|
|
1184
|
+
return toolResult(preflight);
|
|
1185
|
+
}
|
|
1186
|
+
catch (err) {
|
|
1187
|
+
return toolError(err);
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
server.tool('memory_reflexes', memoryReflexesToolSchema, async ({ action, tool, session_id, cwd, files, strict, limit, budget_chars, mode, failure_window_hours, include_status, record_event, include_capsule, include_preflight, }) => {
|
|
1191
|
+
try {
|
|
1192
|
+
const report = await audrey.reflexes(action, {
|
|
1193
|
+
tool,
|
|
1194
|
+
sessionId: session_id,
|
|
1195
|
+
cwd,
|
|
1196
|
+
files,
|
|
1197
|
+
strict,
|
|
1198
|
+
limit,
|
|
1199
|
+
budgetChars: budget_chars,
|
|
1200
|
+
mode,
|
|
1201
|
+
recentFailureWindowHours: failure_window_hours,
|
|
1202
|
+
includeStatus: include_status,
|
|
1203
|
+
recordEvent: record_event,
|
|
1204
|
+
includeCapsule: include_capsule,
|
|
1205
|
+
includePreflight: include_preflight,
|
|
1206
|
+
});
|
|
1207
|
+
return toolResult(report);
|
|
1208
|
+
}
|
|
1209
|
+
catch (err) {
|
|
1210
|
+
return toolError(err);
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
811
1213
|
server.tool('memory_promote', {
|
|
812
|
-
target: z.enum(['claude-rules']).optional().describe('Promotion target. Only claude-rules is implemented in PR 4 v1.
|
|
1214
|
+
target: z.enum(['claude-rules']).optional().describe('Promotion target. Only claude-rules is implemented in PR 4 v1.'),
|
|
813
1215
|
min_confidence: z.number().min(0).max(1).optional().describe('Minimum memory confidence for promotion (default 0.7 for procedural, 0.8 for semantic).'),
|
|
814
1216
|
min_evidence: z.number().int().min(1).optional().describe('Minimum supporting episode count (default 2).'),
|
|
815
1217
|
limit: z.number().int().min(1).max(50).optional().describe('Max candidates to return/apply (default 20).'),
|
|
816
1218
|
dry_run: z.boolean().optional().describe('If true (default), return candidates without writing. Pair with yes=true to actually write.'),
|
|
817
1219
|
yes: z.boolean().optional().describe('Confirm write. Without this or dry_run=false the command stays in dry-run mode.'),
|
|
818
1220
|
project_dir: z.string().optional().describe('Absolute path to the project root where .claude/rules/ should be created. Defaults to process.cwd().'),
|
|
819
|
-
}, async ({ target, min_confidence, min_evidence, limit, dry_run, yes, project_dir }) => {
|
|
1221
|
+
}, async ({ target, min_confidence, min_evidence, limit, dry_run, yes, project_dir, }) => {
|
|
820
1222
|
try {
|
|
821
1223
|
const result = await audrey.promote({
|
|
822
1224
|
target,
|
|
@@ -1025,9 +1427,11 @@ async function promoteCli() {
|
|
|
1025
1427
|
console.log(JSON.stringify(result, null, 2));
|
|
1026
1428
|
return;
|
|
1027
1429
|
}
|
|
1430
|
+
const candidateLabel = `${result.candidates.length} candidate${result.candidates.length === 1 ? '' : 's'}`;
|
|
1431
|
+
const appliedLabel = `${result.applied.length} rule${result.applied.length === 1 ? '' : 's'}`;
|
|
1028
1432
|
const header = result.dry_run
|
|
1029
|
-
? `[audrey] promote (dry-run)
|
|
1030
|
-
: `[audrey] promote
|
|
1433
|
+
? `[audrey] promote (dry-run) - ${candidateLabel} for target "${result.target}"`
|
|
1434
|
+
: `[audrey] promote - wrote ${appliedLabel} to ${result.project_dir}`;
|
|
1031
1435
|
console.log(header);
|
|
1032
1436
|
if (result.candidates.length === 0) {
|
|
1033
1437
|
console.log(' (no candidates met the confidence/evidence thresholds)');
|
|
@@ -1036,10 +1440,11 @@ async function promoteCli() {
|
|
|
1036
1440
|
for (const c of result.candidates) {
|
|
1037
1441
|
console.log('');
|
|
1038
1442
|
console.log(` ${c.rendered_path} [score ${c.score.toFixed(1)}]`);
|
|
1039
|
-
const snippet = c.content.length > 120 ? c.content.slice(0, 117) + '
|
|
1443
|
+
const snippet = c.content.length > 120 ? c.content.slice(0, 117) + '...' : c.content;
|
|
1040
1444
|
console.log(` memory: ${snippet}`);
|
|
1041
1445
|
console.log(` why: ${c.reason}`);
|
|
1042
|
-
console.log(` confidence=${(c.confidence * 100).toFixed(1)}%
|
|
1446
|
+
console.log(` confidence=${(c.confidence * 100).toFixed(1)}% `
|
|
1447
|
+
+ `evidence=${c.evidence_count} prevented_failures=${c.failure_prevented}`);
|
|
1043
1448
|
}
|
|
1044
1449
|
if (result.dry_run) {
|
|
1045
1450
|
console.log('');
|
|
@@ -1058,6 +1463,15 @@ if (isDirectRun) {
|
|
|
1058
1463
|
else if (subcommand === 'uninstall') {
|
|
1059
1464
|
uninstall();
|
|
1060
1465
|
}
|
|
1466
|
+
else if (subcommand === 'mcp-config') {
|
|
1467
|
+
printMcpConfig();
|
|
1468
|
+
}
|
|
1469
|
+
else if (subcommand === 'demo') {
|
|
1470
|
+
runDemoCommand().catch(err => {
|
|
1471
|
+
console.error('[audrey] demo failed:', err);
|
|
1472
|
+
process.exit(1);
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1061
1475
|
else if (subcommand === 'reembed') {
|
|
1062
1476
|
reembed().catch(err => {
|
|
1063
1477
|
console.error('[audrey] reembed failed:', err);
|
|
@@ -1091,6 +1505,9 @@ if (isDirectRun) {
|
|
|
1091
1505
|
else if (subcommand === 'status') {
|
|
1092
1506
|
status();
|
|
1093
1507
|
}
|
|
1508
|
+
else if (subcommand === 'doctor') {
|
|
1509
|
+
doctor();
|
|
1510
|
+
}
|
|
1094
1511
|
else if (subcommand === 'observe-tool') {
|
|
1095
1512
|
observeToolCli().catch(err => {
|
|
1096
1513
|
console.error('[audrey] observe-tool failed:', err);
|