engram-sdk 0.5.7 → 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');
@@ -497,6 +513,9 @@ Store ANY of these the moment you encounter them — do not batch, do not wait:
497
513
  'mcp__engram__engram_entities',
498
514
  'mcp__engram__engram_stats',
499
515
  'mcp__engram__engram_ingest',
516
+ 'mcp__engram__engram_private_session',
517
+ 'mcp__engram__engram_shared_session',
518
+ 'mcp__engram__engram_invite',
500
519
  ];
501
520
  let added = 0;
502
521
  for (const tool of engramTools) {
@@ -913,6 +932,24 @@ async function main() {
913
932
  await vault.close();
914
933
  }
915
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
+ }
916
953
  else {
917
954
  console.log(`
918
955
  engram import — Migrate memory from other AI tools
@@ -922,6 +959,8 @@ Usage:
922
959
  engram import --claude-code --dry-run Preview what would be imported
923
960
  engram import --claude-code --include-sessions Also parse session transcripts
924
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
925
964
 
926
965
  Options:
927
966
  --include-sessions Parse JSONL session transcripts for high-signal messages
@@ -1046,12 +1085,62 @@ Engram has no limit, semantic search, and cross-project intelligence.
1046
1085
  console.log(green('✓ API key verified'));
1047
1086
  setHostedConfig(url, apiKey);
1048
1087
  console.log(green('\n✓ Saved to ~/.config/engram/config.json'));
1049
- console.log('\nYou can now use:');
1050
- console.log(' engram remember "..." Stores to shared vault (default when connected)');
1051
- console.log(' engram remember "..." --private Keeps memory local-only');
1052
- console.log(' engram recall "..." Searches both local + shared');
1053
- console.log(' engram recall "..." --local Search only local vault');
1054
- 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('');
1055
1144
  }
1056
1145
  catch (err) {
1057
1146
  console.error(`\n✗ Connection failed: ${err.message}`);
@@ -1059,6 +1148,34 @@ Engram has no limit, semantic search, and cross-project intelligence.
1059
1148
  }
1060
1149
  return;
1061
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
+ }
1062
1179
  if (command === 'disconnect') {
1063
1180
  const existing = getHostedConfig();
1064
1181
  if (!existing) {
@@ -1108,14 +1225,50 @@ Engram has no limit, semantic search, and cross-project intelligence.
1108
1225
  input.salience = parseFloat(values.salience);
1109
1226
  if (values.confidence)
1110
1227
  input.confidence = parseFloat(values.confidence);
1111
- const mem = await client.remember(input);
1112
- if (values.json) {
1113
- 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;
1114
1243
  }
1115
- else {
1116
- console.log(green('✓ Remembered (hosted):'));
1117
- 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(() => { });
1118
1270
  }
1271
+ await localVault.close();
1119
1272
  return;
1120
1273
  }
1121
1274
  case 'stats': {
@@ -1365,8 +1518,10 @@ Engram has no limit, semantic search, and cross-project intelligence.
1365
1518
  }
1366
1519
  case 'sleep':
1367
1520
  case 'consolidate': {
1368
- console.log(yellow('⏳ Running consolidation...'));
1369
- 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 });
1370
1525
  if (values.json) {
1371
1526
  console.log(JSON.stringify(report, null, 2));
1372
1527
  }