engram-sdk 0.5.8 → 0.6.1

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/dist/cli.js CHANGED
@@ -4,13 +4,67 @@ import { runEval } from './eval.js';
4
4
  import { checkForUpdates, getVersion } from './update-check.js';
5
5
  import * as telemetry from './telemetry.js';
6
6
  import { renderTree as renderMemoryTree, animateGrowth } from './memory-tree.js';
7
- import { HostedClient, getHostedConfig, setHostedConfig, clearHostedConfig, federatedRecall } from './hosted-client.js';
7
+ import { HostedClient, getHostedConfig, setHostedConfig, clearHostedConfig, federatedRecall, getWriteQueue } from './hosted-client.js';
8
+ import { classifySensitive } from './content-filter.js';
8
9
  import path from 'path';
9
10
  import { homedir } from 'os';
10
11
  import { parseArgs } from 'util';
11
12
  // ============================================================
12
13
  // Engram CLI — Quick interface for testing & exploration
13
14
  // ============================================================
15
+ function getEngramInstructions() {
16
+ return `
17
+ ## Engram — Persistent Memory
18
+
19
+ You have Engram memory tools via MCP. **Use them proactively** — do not wait to be asked.
20
+
21
+ ### Session Start
22
+ - ALWAYS call \`engram_briefing\` at the start of every session to load context from past sessions.
23
+
24
+ ### What to Remember (call \`engram_remember\` immediately)
25
+ Store ANY of these the moment you encounter them — do not batch, do not wait:
26
+
27
+ **From the user:**
28
+ - Preferences, opinions, personal facts, decisions, corrections
29
+ - "I prefer X", "we decided Y", "actually it's Z"
30
+
31
+ **From your own work:**
32
+ - When you read a file and synthesize useful knowledge (style guides, architecture patterns, workflow rules) — store the synthesis
33
+ - When you figure out HOW the user works (communication style, review habits, tool preferences) — store the observation
34
+ - When you discover project conventions, naming patterns, or implicit rules — store them
35
+ - When you build something and learn what works vs what doesn't — store the lesson
36
+
37
+ **The rule:** If this knowledge would be useful in a future session, store it NOW. Don't assume you'll remember — you won't.
38
+
39
+ ### Team Vault
40
+ - When connected to a hosted vault, memories are shared with your team by default
41
+ - Use \`engram_private_session\` if the user wants a confidential session (nothing leaves the local machine)
42
+ - Use \`engram_invite\` to add a teammate when asked ("add Brian to engram", "invite tyler@company.com")
43
+ - Recall always searches both local + shared vaults, so you see everything
44
+
45
+ ### Asking Questions
46
+ - When you need to ANSWER a question from memory: call \`engram_ask\` — it returns a synthesized answer with confidence, not raw memories
47
+ - When you need raw memory objects for processing: call \`engram_recall\`
48
+ - Prefer \`engram_ask\` for user-facing answers, \`engram_recall\` for your own analysis
49
+
50
+ ### Alerts
51
+ - Periodically call \`engram_alerts\` to check for pending commitments, stale follow-ups, and contradictions
52
+ - Alerts are included in \`engram_briefing\` automatically, but call separately if you need a quick check
53
+
54
+ ### Auditing External Memory
55
+ - If you maintain a CLAUDE.md, MEMORY.md, or similar file: periodically call \`engram_audit\` with its contents
56
+ - It will flag any claims that are outdated or contradicted by newer vault data
57
+
58
+ ### Before Compacting / Session End
59
+ - Before context compaction or ending a session, call \`engram_checkpoint\` with a summary of:
60
+ - Decisions made and their reasoning
61
+ - Facts learned or corrected (especially things that changed from what you previously knew)
62
+ - Commitments and next steps
63
+ - Current project state
64
+ - This extracts durable memories from your context window before it is lost
65
+ - Your context window is the most accurate source of truth you have — and the most volatile. Save it.
66
+ `;
67
+ }
14
68
  const HELP = `
15
69
  engram — Universal memory layer for AI agents
16
70
 
@@ -18,6 +72,7 @@ Usage:
18
72
  engram init Set up Engram for Claude Code / Cursor / VS Code / Gemini CLI / Codex
19
73
  engram setup Alias for init
20
74
  engram import --claude-code Import memory from Claude Code (no 200-line limit)
75
+ engram import --obsidian <path> Import an Obsidian vault (wikilinks, tags, frontmatter)
21
76
  engram mcp Start the MCP server (stdio transport)
22
77
  engram mcp --http Start the MCP server (HTTP transport, port 3801)
23
78
  engram shadow start Start shadow mode (server + watcher, background)
@@ -37,6 +92,7 @@ Usage:
37
92
  engram telemetry show View telemetry status and pending events
38
93
  engram connect <url> --api-key <k> Connect to a hosted vault
39
94
  engram disconnect Disconnect from hosted vault
95
+ engram invite <email> Invite a teammate to your shared vault
40
96
  engram config set telemetry false Opt out of anonymous usage data
41
97
  engram config get telemetry Check telemetry status
42
98
 
@@ -70,6 +126,7 @@ function parseCliArgs() {
70
126
  salience: { type: 'string', default: '' },
71
127
  confidence: { type: 'string', default: '' },
72
128
  'claude-code': { type: 'boolean', default: false },
129
+ obsidian: { type: 'string', default: '' },
73
130
  'include-sessions': { type: 'boolean', default: false },
74
131
  'dry-run': { type: 'boolean', default: false },
75
132
  'max-sessions': { type: 'string', default: '10' },
@@ -78,7 +135,10 @@ function parseCliArgs() {
78
135
  private: { type: 'boolean', default: false },
79
136
  local: { type: 'boolean', default: false },
80
137
  'api-key': { type: 'string', default: '' },
138
+ role: { type: 'string', default: 'member' },
81
139
  verbose: { type: 'boolean', default: false },
140
+ all: { type: 'boolean', default: false },
141
+ since: { type: 'string', default: '' },
82
142
  },
83
143
  });
84
144
  return { values, positionals };
@@ -403,51 +463,7 @@ async function runInit(values) {
403
463
  // 5. Add Engram instructions to CLAUDE.md (if Claude dir exists)
404
464
  if (hasClaudeDir) {
405
465
  const claudeMdPath = join(home, '.claude', 'CLAUDE.md');
406
- const engramBlock = `
407
- ## Engram — Persistent Memory
408
-
409
- You have Engram memory tools via MCP. **Use them proactively** — do not wait to be asked.
410
-
411
- ### Session Start
412
- - ALWAYS call \`engram_briefing\` at the start of every session to load context from past sessions.
413
-
414
- ### What to Remember (call \`engram_remember\` immediately)
415
- Store ANY of these the moment you encounter them — do not batch, do not wait:
416
-
417
- **From the user:**
418
- - Preferences, opinions, personal facts, decisions, corrections
419
- - "I prefer X", "we decided Y", "actually it's Z"
420
-
421
- **From your own work:**
422
- - When you read a file and synthesize useful knowledge (style guides, architecture patterns, workflow rules) — store the synthesis
423
- - When you figure out HOW the user works (communication style, review habits, tool preferences) — store the observation
424
- - When you discover project conventions, naming patterns, or implicit rules — store them
425
- - When you build something and learn what works vs what doesn't — store the lesson
426
-
427
- **The rule:** If this knowledge would be useful in a future session, store it NOW. Don't assume you'll remember — you won't.
428
-
429
- ### Asking Questions
430
- - When you need to ANSWER a question from memory: call \`engram_ask\` — it returns a synthesized answer with confidence, not raw memories
431
- - When you need raw memory objects for processing: call \`engram_recall\`
432
- - Prefer \`engram_ask\` for user-facing answers, \`engram_recall\` for your own analysis
433
-
434
- ### Alerts
435
- - Periodically call \`engram_alerts\` to check for pending commitments, stale follow-ups, and contradictions
436
- - Alerts are included in \`engram_briefing\` automatically, but call separately if you need a quick check
437
-
438
- ### Auditing External Memory
439
- - If you maintain a CLAUDE.md, MEMORY.md, or similar file: periodically call \`engram_audit\` with its contents
440
- - It will flag any claims that are outdated or contradicted by newer vault data
441
-
442
- ### Before Compacting / Session End
443
- - Before context compaction or ending a session, call \`engram_checkpoint\` with a summary of:
444
- - Decisions made and their reasoning
445
- - Facts learned or corrected (especially things that changed from what you previously knew)
446
- - Commitments and next steps
447
- - Current project state
448
- - This extracts durable memories from your context window before it is lost
449
- - Your context window is the most accurate source of truth you have — and the most volatile. Save it.
450
- `;
466
+ const engramBlock = getEngramInstructions();
451
467
  let claudeMd = '';
452
468
  if (existsSync(claudeMdPath)) {
453
469
  claudeMd = readFileSync(claudeMdPath, 'utf-8');
@@ -499,6 +515,7 @@ Store ANY of these the moment you encounter them — do not batch, do not wait:
499
515
  'mcp__engram__engram_ingest',
500
516
  'mcp__engram__engram_private_session',
501
517
  'mcp__engram__engram_shared_session',
518
+ 'mcp__engram__engram_invite',
502
519
  ];
503
520
  let added = 0;
504
521
  for (const tool of engramTools) {
@@ -915,6 +932,24 @@ async function main() {
915
932
  await vault.close();
916
933
  }
917
934
  }
935
+ else if (values.obsidian) {
936
+ const { importObsidian } = await import('./import.js');
937
+ const vault = createVault(values);
938
+ try {
939
+ const result = await importObsidian({
940
+ vault,
941
+ vaultPath: values.obsidian,
942
+ dryRun: values['dry-run'],
943
+ verbose: values.verbose,
944
+ });
945
+ if (values.json) {
946
+ console.log(JSON.stringify(result, null, 2));
947
+ }
948
+ }
949
+ finally {
950
+ await vault.close();
951
+ }
952
+ }
918
953
  else {
919
954
  console.log(`
920
955
  engram import — Migrate memory from other AI tools
@@ -924,6 +959,8 @@ Usage:
924
959
  engram import --claude-code --dry-run Preview what would be imported
925
960
  engram import --claude-code --include-sessions Also parse session transcripts
926
961
  engram import --claude-code --verbose Show detailed progress
962
+ engram import --obsidian /path/to/vault Import from an Obsidian vault
963
+ engram import --obsidian ~/vault --dry-run Preview what would be imported
927
964
 
928
965
  Options:
929
966
  --include-sessions Parse JSONL session transcripts for high-signal messages
@@ -1048,12 +1085,62 @@ Engram has no limit, semantic search, and cross-project intelligence.
1048
1085
  console.log(green('✓ API key verified'));
1049
1086
  setHostedConfig(url, apiKey);
1050
1087
  console.log(green('\n✓ Saved to ~/.config/engram/config.json'));
1051
- console.log('\nYou can now use:');
1052
- console.log(' engram remember "..." Stores to shared vault (default when connected)');
1053
- console.log(' engram remember "..." --private Keeps memory local-only');
1054
- console.log(' engram recall "..." Searches both local + shared');
1055
- console.log(' engram recall "..." --local Search only local vault');
1056
- console.log(' engram stats --shared Shared vault stats\n');
1088
+ // Auto-setup agent instructions if not already done
1089
+ const { existsSync, readFileSync, writeFileSync } = await import('fs');
1090
+ const { join } = await import('path');
1091
+ const home = homedir();
1092
+ const claudeDir = join(home, '.claude');
1093
+ const claudeMdPath = join(claudeDir, 'CLAUDE.md');
1094
+ if (existsSync(claudeDir)) {
1095
+ let claudeMd = existsSync(claudeMdPath) ? readFileSync(claudeMdPath, 'utf-8') : '';
1096
+ if (!claudeMd.includes('## Engram')) {
1097
+ console.log('\n📋 Setting up agent instructions...');
1098
+ // Run init-style CLAUDE.md setup
1099
+ const engramBlock = getEngramInstructions();
1100
+ writeFileSync(claudeMdPath, claudeMd + '\n' + engramBlock.trim() + '\n');
1101
+ console.log(green(' ✓ Added Engram instructions to ~/.claude/CLAUDE.md'));
1102
+ console.log(' Your agent will now use Engram proactively in every session.');
1103
+ }
1104
+ // Auto-approve MCP tools in Claude Code settings
1105
+ const settingsPath = join(claudeDir, 'settings.json');
1106
+ if (existsSync(settingsPath)) {
1107
+ try {
1108
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
1109
+ const allow = settings.permissions?.allow ?? [];
1110
+ const engramTools = [
1111
+ 'mcp__engram__engram_remember', 'mcp__engram__engram_recall',
1112
+ 'mcp__engram__engram_surface', 'mcp__engram__engram_briefing',
1113
+ 'mcp__engram__engram_consolidate', 'mcp__engram__engram_connect',
1114
+ 'mcp__engram__engram_forget', 'mcp__engram__engram_entities',
1115
+ 'mcp__engram__engram_stats', 'mcp__engram__engram_ingest',
1116
+ 'mcp__engram__engram_private_session', 'mcp__engram__engram_shared_session',
1117
+ 'mcp__engram__engram_invite',
1118
+ ];
1119
+ let added = 0;
1120
+ for (const tool of engramTools) {
1121
+ if (!allow.includes(tool)) {
1122
+ allow.push(tool);
1123
+ added++;
1124
+ }
1125
+ }
1126
+ if (added > 0) {
1127
+ if (!settings.permissions)
1128
+ settings.permissions = {};
1129
+ settings.permissions.allow = allow;
1130
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
1131
+ console.log(green(` ✓ Auto-approved ${added} Engram tools in Claude Code`));
1132
+ }
1133
+ }
1134
+ catch { /* settings parse error, skip */ }
1135
+ }
1136
+ }
1137
+ console.log('\n✅ You\'re all set! Your agent will now:');
1138
+ console.log(' • Store memories to the shared team vault automatically');
1139
+ console.log(' • Recall context from both local + shared vaults');
1140
+ console.log(' • Use /engram-private to switch a session to local-only mode');
1141
+ console.log('');
1142
+ console.log(` 📊 View your team dashboard at ${green('https://app.engram.fyi')}`);
1143
+ console.log('');
1057
1144
  }
1058
1145
  catch (err) {
1059
1146
  console.error(`\n✗ Connection failed: ${err.message}`);
@@ -1061,6 +1148,34 @@ Engram has no limit, semantic search, and cross-project intelligence.
1061
1148
  }
1062
1149
  return;
1063
1150
  }
1151
+ if (command === 'invite') {
1152
+ const email = positionals[1];
1153
+ if (!email) {
1154
+ console.error('Usage: engram invite <email> [--role member|readonly]');
1155
+ process.exit(1);
1156
+ }
1157
+ const hostedConfig = getHostedConfig();
1158
+ if (!hostedConfig) {
1159
+ console.error('Error: not connected to a hosted vault. Run: engram connect <url> --api-key <key>');
1160
+ process.exit(1);
1161
+ }
1162
+ try {
1163
+ const client = new HostedClient(hostedConfig);
1164
+ const role = values.role ?? 'member';
1165
+ const result = await client.invite(email, role);
1166
+ console.log(green(`✓ ${result.message}`));
1167
+ console.log(`\nSend them this:\n`);
1168
+ console.log(` npm install -g engram-sdk@latest`);
1169
+ console.log(` engram connect https://api.engram.fyi --api-key ${result.account.apiKey}`);
1170
+ console.log(`\nThat's it — their agent will automatically share memories with the team.`);
1171
+ console.log(`Team dashboard: ${green('https://app.engram.fyi')}`);
1172
+ }
1173
+ catch (err) {
1174
+ console.error(`Error: ${err.message}`);
1175
+ process.exit(1);
1176
+ }
1177
+ return;
1178
+ }
1064
1179
  if (command === 'disconnect') {
1065
1180
  const existing = getHostedConfig();
1066
1181
  if (!existing) {
@@ -1110,14 +1225,50 @@ Engram has no limit, semantic search, and cross-project intelligence.
1110
1225
  input.salience = parseFloat(values.salience);
1111
1226
  if (values.confidence)
1112
1227
  input.confidence = parseFloat(values.confidence);
1113
- const mem = await client.remember(input);
1114
- if (values.json) {
1115
- console.log(JSON.stringify(mem, null, 2));
1228
+ // Always write locally first (write-through)
1229
+ const localVault = createVault(values);
1230
+ const localMem = localVault.remember(input);
1231
+ // Check for sensitive content before hosted write
1232
+ const sensitivity = classifySensitive(text);
1233
+ if (sensitivity.sensitive) {
1234
+ if (values.json) {
1235
+ console.log(JSON.stringify({ ...localMem, warning: `Stored locally only — sensitive content detected (${sensitivity.reason})` }, null, 2));
1236
+ }
1237
+ else {
1238
+ console.log(yellow(`⚠️ Stored locally only — sensitive content detected (${sensitivity.reason})`));
1239
+ printMemory(localMem, false);
1240
+ }
1241
+ await localVault.close();
1242
+ return;
1116
1243
  }
1117
- else {
1118
- console.log(green('✓ Remembered (hosted):'));
1119
- printMemory(mem, false);
1244
+ // Write-through to hosted (async with retry queue)
1245
+ try {
1246
+ const mem = await client.remember(input);
1247
+ if (values.json) {
1248
+ console.log(JSON.stringify(mem, null, 2));
1249
+ }
1250
+ else {
1251
+ console.log(green('✓ Remembered (local + shared):'));
1252
+ printMemory(mem, false);
1253
+ }
1254
+ }
1255
+ catch (err) {
1256
+ getWriteQueue().enqueue(input);
1257
+ if (values.json) {
1258
+ console.log(JSON.stringify(localMem, null, 2));
1259
+ }
1260
+ else {
1261
+ console.log(yellow(`⚠️ Hosted write failed (queued for retry): ${err.message}`));
1262
+ console.log(green('✓ Remembered locally:'));
1263
+ printMemory(localMem, false);
1264
+ }
1265
+ }
1266
+ // Flush any queued writes
1267
+ const wq = getWriteQueue();
1268
+ if (wq.length > 0) {
1269
+ wq.flush(client).catch(() => { });
1120
1270
  }
1271
+ await localVault.close();
1121
1272
  return;
1122
1273
  }
1123
1274
  case 'stats': {
@@ -1367,8 +1518,10 @@ Engram has no limit, semantic search, and cross-project intelligence.
1367
1518
  }
1368
1519
  case 'sleep':
1369
1520
  case 'consolidate': {
1370
- console.log(yellow('⏳ Running consolidation...'));
1371
- const report = await vault.consolidate();
1521
+ const allFlag = values.all ?? process.argv.includes('--all');
1522
+ const sinceFlag = (values.since || undefined);
1523
+ console.log(yellow(`⏳ Running consolidation${allFlag ? ' (all episodes)' : sinceFlag ? ` (since ${sinceFlag})` : ''}...`));
1524
+ const report = await vault.consolidate({ all: !!allFlag, since: sinceFlag });
1372
1525
  if (values.json) {
1373
1526
  console.log(JSON.stringify(report, null, 2));
1374
1527
  }