@pcircle/memesh 4.3.0 → 4.5.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.
Files changed (100) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/README.de.md +35 -12
  4. package/README.es.md +13 -9
  5. package/README.fr.md +33 -10
  6. package/README.ja.md +13 -9
  7. package/README.ko.md +14 -10
  8. package/README.md +18 -11
  9. package/README.pt.md +32 -9
  10. package/README.th.md +18 -8
  11. package/README.vi.md +34 -11
  12. package/README.zh-CN.md +14 -10
  13. package/README.zh-TW.md +14 -10
  14. package/dashboard/dist/index.html +10 -10
  15. package/dist/cli/view-live.js +2 -2
  16. package/dist/cli/view.js +1 -1
  17. package/dist/core/analytics.d.ts +4 -0
  18. package/dist/core/analytics.d.ts.map +1 -1
  19. package/dist/core/analytics.js +8 -8
  20. package/dist/core/analytics.js.map +1 -1
  21. package/dist/core/auto-tagger.d.ts.map +1 -1
  22. package/dist/core/auto-tagger.js.map +1 -1
  23. package/dist/core/config.d.ts +5 -2
  24. package/dist/core/config.d.ts.map +1 -1
  25. package/dist/core/config.js +28 -13
  26. package/dist/core/config.js.map +1 -1
  27. package/dist/core/demo.d.ts +1 -0
  28. package/dist/core/demo.d.ts.map +1 -1
  29. package/dist/core/demo.js +24 -0
  30. package/dist/core/demo.js.map +1 -1
  31. package/dist/core/digest-validator.d.ts.map +1 -1
  32. package/dist/core/digest-validator.js +3 -1
  33. package/dist/core/digest-validator.js.map +1 -1
  34. package/dist/core/doctor.d.ts +2 -0
  35. package/dist/core/doctor.d.ts.map +1 -1
  36. package/dist/core/doctor.js +70 -60
  37. package/dist/core/doctor.js.map +1 -1
  38. package/dist/core/dreamer.d.ts +21 -1
  39. package/dist/core/dreamer.d.ts.map +1 -1
  40. package/dist/core/dreamer.js +86 -8
  41. package/dist/core/dreamer.js.map +1 -1
  42. package/dist/core/embedder.d.ts +1 -4
  43. package/dist/core/embedder.d.ts.map +1 -1
  44. package/dist/core/embedder.js +5 -92
  45. package/dist/core/embedder.js.map +1 -1
  46. package/dist/core/failure-analyzer.d.ts.map +1 -1
  47. package/dist/core/failure-analyzer.js +2 -1
  48. package/dist/core/failure-analyzer.js.map +1 -1
  49. package/dist/core/llm-client.d.ts.map +1 -1
  50. package/dist/core/llm-client.js.map +1 -1
  51. package/dist/core/llm-validator.d.ts +1 -0
  52. package/dist/core/llm-validator.d.ts.map +1 -1
  53. package/dist/core/llm-validator.js +33 -10
  54. package/dist/core/llm-validator.js.map +1 -1
  55. package/dist/core/operations.d.ts.map +1 -1
  56. package/dist/core/operations.js +10 -3
  57. package/dist/core/operations.js.map +1 -1
  58. package/dist/core/output-language.d.ts +6 -0
  59. package/dist/core/output-language.d.ts.map +1 -0
  60. package/dist/core/output-language.js +25 -0
  61. package/dist/core/output-language.js.map +1 -0
  62. package/dist/core/patterns.d.ts +0 -1
  63. package/dist/core/patterns.d.ts.map +1 -1
  64. package/dist/core/patterns.js +1 -5
  65. package/dist/core/patterns.js.map +1 -1
  66. package/dist/core/transcript-extractor.d.ts +89 -0
  67. package/dist/core/transcript-extractor.d.ts.map +1 -0
  68. package/dist/core/transcript-extractor.js +437 -0
  69. package/dist/core/transcript-extractor.js.map +1 -0
  70. package/dist/core/transcript-source.d.ts +21 -0
  71. package/dist/core/transcript-source.d.ts.map +1 -0
  72. package/dist/core/transcript-source.js +142 -0
  73. package/dist/core/transcript-source.js.map +1 -0
  74. package/dist/core/types.d.ts +4 -0
  75. package/dist/core/types.d.ts.map +1 -1
  76. package/dist/core/verifier.js +1 -1
  77. package/dist/core/verifier.js.map +1 -1
  78. package/dist/db.d.ts.map +1 -1
  79. package/dist/db.js +10 -0
  80. package/dist/db.js.map +1 -1
  81. package/dist/skills-manifest.json +11 -11
  82. package/dist/transports/cli/cli.d.ts.map +1 -1
  83. package/dist/transports/cli/cli.js +229 -65
  84. package/dist/transports/cli/cli.js.map +1 -1
  85. package/dist/transports/http/server.d.ts +7 -0
  86. package/dist/transports/http/server.d.ts.map +1 -1
  87. package/dist/transports/http/server.js +145 -50
  88. package/dist/transports/http/server.js.map +1 -1
  89. package/dist/transports/mcp/handlers.d.ts +1 -1
  90. package/dist/transports/mcp/handlers.d.ts.map +1 -1
  91. package/dist/transports/mcp/handlers.js +3 -2
  92. package/dist/transports/mcp/handlers.js.map +1 -1
  93. package/dist/transports/schemas.d.ts.map +1 -1
  94. package/dist/transports/schemas.js.map +1 -1
  95. package/package.json +2 -11
  96. package/scripts/hooks/post-commit.js +8 -2
  97. package/scripts/hooks/pre-compact.js +9 -0
  98. package/scripts/hooks/session-start.js +1 -1
  99. package/skills/agentic-orchestration/SKILL.md +1 -1
  100. package/skills/memesh/SKILL.md +2 -0
@@ -8,6 +8,7 @@ import { openDatabase, closeDatabase, getDatabase, reindexFts, allowVectorIndexR
8
8
  import { remember, recallWithConflicts, forget, exportMemories, importMemories, learn, reindex, setPinned } from '../../core/operations.js';
9
9
  import { verifyAgentWork } from '../../core/verifier.js';
10
10
  import { readConfig, writeConfig, maskApiKey, detectCapabilities } from '../../core/config.js';
11
+ import { MAX_LANGUAGE_LENGTH, languageValueError } from '../../core/output-language.js';
11
12
  import { getDbPath } from '../../core/paths.js';
12
13
  import { flushPendingEmbeddings, canRefillVectorIndex } from '../../core/embedder.js';
13
14
  async function withDatabase(fn) {
@@ -52,6 +53,14 @@ program
52
53
  opts.type = 'note';
53
54
  if (!opts.obs || opts.obs.length === 0)
54
55
  opts.obs = [String(text)];
56
+ else
57
+ opts.obs = [...opts.obs, String(text)];
58
+ }
59
+ else if (text) {
60
+ if (!opts.obs || opts.obs.length === 0)
61
+ opts.obs = [String(text)];
62
+ else
63
+ opts.obs = [...opts.obs, String(text)];
55
64
  }
56
65
  if (!opts.name || !opts.type) {
57
66
  console.error('Error: provide --name and --type, OR pass quick-capture text as a positional arg.\n' +
@@ -108,11 +117,25 @@ program
108
117
  console.log('No results found.');
109
118
  }
110
119
  else {
120
+ const allSemantic = query && entities.every((e) => e.match?.source === 'semantic');
121
+ if (allSemantic) {
122
+ console.log('No keyword matches. Closest memories by meaning — may be unrelated:');
123
+ }
111
124
  for (const e of entities) {
112
125
  const badge = e.archived ? ' [archived]' : '';
113
- console.log(` ${e.name}${badge} (${e.type})`);
126
+ const semantic = e.match?.source === 'semantic'
127
+ ? ` (~${Math.round((e.match.relevance ?? 0) * 100)}% semantic)`
128
+ : '';
129
+ console.log(` ${e.name}${badge} (${e.type})${semantic}`);
114
130
  for (const obs of e.observations.slice(0, 3)) {
115
- console.log(` - ${obs}`);
131
+ let shown = obs;
132
+ if (obs.length > 500) {
133
+ let head = obs.slice(0, 500);
134
+ if (/[\uD800-\uDBFF]$/.test(head))
135
+ head = head.slice(0, -1);
136
+ shown = `${head} … (+${obs.length - head.length} more chars)`;
137
+ }
138
+ console.log(` - ${shown}`);
116
139
  }
117
140
  if (e.observations.length > 3) {
118
141
  console.log(` ... +${e.observations.length - 3} more`);
@@ -168,6 +191,8 @@ function registerPinCommand(name, description, pinned, onFound) {
168
191
  console.log(JSON.stringify(result));
169
192
  else
170
193
  console.log(result.found ? onFound(opts.name) : `Entity "${opts.name}" not found`);
194
+ if (!result.found)
195
+ process.exitCode = 1;
171
196
  });
172
197
  });
173
198
  }
@@ -306,17 +331,25 @@ program
306
331
  .action(async (workdir, opts) => {
307
332
  await withDatabase(() => {
308
333
  let externalReport;
309
- if (opts.report) {
310
- const raw = fs.readFileSync(opts.report, 'utf8');
311
- externalReport = JSON.parse(raw);
312
- }
313
- const result = verifyAgentWork({
314
- agent_id: opts.agentId,
315
- workdir: path.resolve(workdir),
316
- base: opts.base,
317
- claim: opts.expectedFiles != null ? { expected_files: opts.expectedFiles } : undefined,
318
- report: externalReport,
319
- });
334
+ let result;
335
+ try {
336
+ if (opts.report) {
337
+ const raw = fs.readFileSync(opts.report, 'utf8');
338
+ externalReport = JSON.parse(raw);
339
+ }
340
+ result = verifyAgentWork({
341
+ agent_id: opts.agentId,
342
+ workdir: path.resolve(workdir),
343
+ base: opts.base,
344
+ claim: opts.expectedFiles != null ? { expected_files: opts.expectedFiles } : undefined,
345
+ report: externalReport,
346
+ });
347
+ }
348
+ catch (err) {
349
+ console.error(err instanceof Error ? err.message : String(err));
350
+ console.error('Nothing was checked (exit 2 = unverified).');
351
+ process.exit(2);
352
+ }
320
353
  if (opts.json) {
321
354
  console.log(JSON.stringify(result, null, 2));
322
355
  }
@@ -362,10 +395,18 @@ const ALLOWED_KEYS = new Set([
362
395
  'enableAgenticOrchestration',
363
396
  'autoCapture',
364
397
  'llmFallbacks',
398
+ 'language',
365
399
  ]);
366
400
  const KEY_VALIDATORS = {
367
401
  'llm.provider': (v) => ['anthropic', 'openai', 'ollama'].includes(v) ? null : `must be one of: anthropic, openai, ollama`,
368
- 'embedder.provider': (v) => ['onnx', 'openai', 'ollama'].includes(v) ? null : `must be one of: onnx, openai, ollama`,
402
+ 'language': (v) => {
403
+ if (v.trim().length === 0)
404
+ return 'must not be blank — use `memesh config unset language` to clear it';
405
+ if (v.length > MAX_LANGUAGE_LENGTH)
406
+ return `must be ${MAX_LANGUAGE_LENGTH} characters or fewer (a language name or locale code)`;
407
+ return languageValueError(v);
408
+ },
409
+ 'embedder.provider': (v) => ['openai', 'ollama'].includes(v) ? null : `must be one of: openai, ollama`,
369
410
  'autoUpdate': (v) => ['off', 'patch', 'minor', 'major'].includes(v) ? null : `must be one of: off, patch, minor, major`,
370
411
  'llmFallbacks': (v) => {
371
412
  let parsed;
@@ -548,13 +589,13 @@ program
548
589
  });
549
590
  program
550
591
  .command('serve')
551
- .description('Start HTTP API server')
592
+ .description('Start the HTTP API server and web dashboard')
552
593
  .option('--port <port>', 'Port number', '3737')
553
594
  .option('--host <host>', 'Host to bind', '127.0.0.1')
554
- .option('--allow-remote', 'Allow binding to non-loopback hosts (no auth layer is added)')
595
+ .option('--allow-remote', 'Allow binding to non-loopback hosts. A bearer token is generated and REQUIRED for every /v1 request — the startup output shows where it lives and how to rotate it.')
555
596
  .action(async (opts) => {
556
597
  const { startServer } = await import('../http/server.js');
557
- startServer(opts.host, parseInt(opts.port, 10), { allowRemote: opts.allowRemote });
598
+ startServer(opts.host, parseInt(opts.port, 10), { allowRemote: opts.allowRemote, autoUpdateCheck: true });
558
599
  });
559
600
  program
560
601
  .command('update')
@@ -849,18 +890,118 @@ dreamCmd
849
890
  .option('--max-llm-calls <n>', 'Hard cap on LLM calls (default 100)', (v) => parseInt(v, 10))
850
891
  .option('--window-days <n>', 'Look-back window in days (default 56 = 8 weeks)', (v) => parseInt(v, 10))
851
892
  .option('--validate', 'Run a second LLM pass to cross-check each digest against its sources (doubles LLM calls per proposal; surfaces under flow=digest_validator in `memesh telemetry`)')
893
+ .option('--from-transcripts', 'EXPERIMENTAL: mine Claude Code session transcripts for this project (decisions/lessons/facts hidden in the conversation) and STAGE them as proposals for `dream accept`, instead of clustering existing entities. Scoped to the current project only — --project does not apply here. With --dry-run, lists sessions and conversation-turn counts without calling an LLM.')
894
+ .option('--if-due', 'For a scheduler (cron/launchd): only mine if `transcriptMining` is enabled in config AND at least --min-interval-hours have passed since this project was last mined; otherwise exit 0 doing nothing. Lets one frequently-firing entry self-throttle. Only meaningful with --from-transcripts.')
895
+ .option('--min-interval-hours <n>', 'With --if-due: minimum hours between mined runs for this project (default 24).', (v) => parseInt(v, 10))
852
896
  .action(async (opts) => {
897
+ if (opts.fromTranscripts) {
898
+ const windowDays = typeof opts.windowDays === 'number' && !Number.isNaN(opts.windowDays) ? opts.windowDays : 3;
899
+ if (opts.dryRun) {
900
+ const { scanTranscripts } = await import('../../core/transcript-source.js');
901
+ const { countConversationTurns } = await import('../../core/transcript-extractor.js');
902
+ const sessions = scanTranscripts({ windowDays });
903
+ console.log(`[dry-run] transcript sessions for this project in the last ${windowDays} day(s): ${sessions.length}`);
904
+ let totalTurns = 0;
905
+ for (const s of sessions) {
906
+ const turns = countConversationTurns(s.path);
907
+ totalTurns += turns;
908
+ console.log(` ${s.sessionId} ${turns} conversation turns ${s.lineCount} lines ${(s.sizeBytes / 1024).toFixed(0)}KB ${s.modifiedAt}`);
909
+ }
910
+ console.log(` total: ${totalTurns} conversation turns across ${sessions.length} session(s)`);
911
+ console.log('');
912
+ console.log('Run without --dry-run to extract high-value memories and stage them as proposals.');
913
+ return;
914
+ }
915
+ if (opts.ifDue) {
916
+ const { isTranscriptMiningEnabled } = await import('../../core/config.js');
917
+ if (!isTranscriptMiningEnabled(readConfig())) {
918
+ console.log('Scheduled transcript mining is off (opt-in). Enable it with `memesh config set transcriptMining true`; this scheduled run will then start mining when due.');
919
+ return;
920
+ }
921
+ const { getProjectName } = await import('../../core/paths.js');
922
+ const { lastTranscriptMineAt, transcriptMiningDue } = await import('../../core/transcript-source.js');
923
+ const projectKey = getProjectName(process.cwd());
924
+ const intervalH = typeof opts.minIntervalHours === 'number' && !Number.isNaN(opts.minIntervalHours) ? opts.minIntervalHours : 24;
925
+ const last = lastTranscriptMineAt(projectKey);
926
+ if (!transcriptMiningDue(Date.now(), last, intervalH)) {
927
+ const agoH = last === null ? null : (Date.now() - last) / 3600_000;
928
+ console.log(`Not due yet: this project was mined ${agoH === null ? 'recently' : `${agoH.toFixed(1)}h ago`}; interval is ${intervalH}h. Nothing to do.`);
929
+ return;
930
+ }
931
+ }
932
+ if (opts.project) {
933
+ console.log('note: --project does not scope --from-transcripts — the transcript source is always the current project. Ignoring --project.');
934
+ }
935
+ await withDatabase(async () => {
936
+ const { runTranscriptSource } = await import('../../core/transcript-extractor.js');
937
+ const { getDatabase } = await import('../../db.js');
938
+ const cfg = readConfig();
939
+ const llm = detectCapabilities().llm;
940
+ if (!llm) {
941
+ console.error('No LLM configured. Run `memesh config set llm.provider <anthropic|openai|ollama>` first (or set ANTHROPIC_API_KEY / OPENAI_API_KEY).');
942
+ console.error('LLM is required for `--from-transcripts` because extracting durable memory from prose is a semantic decision, not a rule.');
943
+ process.exit(1);
944
+ }
945
+ const result = await runTranscriptSource(getDatabase(), llm, {
946
+ windowDays,
947
+ maxLlmCalls: opts.maxLlmCalls,
948
+ fallbacks: cfg.llmFallbacks,
949
+ });
950
+ const { getProjectName } = await import('../../core/paths.js');
951
+ const { recordTranscriptMine } = await import('../../core/transcript-source.js');
952
+ recordTranscriptMine(getProjectName(process.cwd()), Date.now());
953
+ console.log(`Transcript mining complete in ${result.durationMs}ms`);
954
+ console.log(` sessions scanned: ${result.sessionsScanned}`);
955
+ console.log(` LLM calls: ${result.llmCalls}`);
956
+ console.log(` candidates extracted: ${result.candidatesExtracted}`);
957
+ console.log(` proposals created: ${result.proposalsCreated}`);
958
+ if (result.duplicatesSkipped > 0)
959
+ console.log(` duplicates skipped: ${result.duplicatesSkipped} (already a pending proposal)`);
960
+ if (result.nearDuplicatesSkipped > 0) {
961
+ console.log(` near-duplicates skipped: ${result.nearDuplicatesSkipped} candidate(s) skipped as near-duplicates of existing memories`);
962
+ for (const d of result.nearDuplicates) {
963
+ console.log(` - "${d.candidateName}" ~= existing "${d.matchedEntityName}" (distance ${d.distance.toFixed(3)})`);
964
+ }
965
+ }
966
+ if (result.secretsDropped > 0)
967
+ console.log(` secret-bearing candidates dropped: ${result.secretsDropped}`);
968
+ if (result.llmFailures > 0)
969
+ console.log(` LLM call failures: ${result.llmFailures} (sessions not mined — retry when the provider is reachable)`);
970
+ if (result.parseFailures > 0)
971
+ console.log(` unparsable replies: ${result.parseFailures} (chunk reply not valid JSON — likely truncated; those candidates were lost, retry)`);
972
+ if (result.truncatedTurns > 0) {
973
+ console.log(` size-cap truncation: ${result.truncatedTurns} conversation turn(s) beyond the cap were NOT analysed`);
974
+ for (const t of result.truncatedSessions) {
975
+ console.log(` - session ${t.sessionId}: ${t.truncatedTurns} tail turn(s) not analysed`);
976
+ }
977
+ }
978
+ if (result.skipped.length > 0) {
979
+ const reasonCounts = new Map();
980
+ for (const s of result.skipped)
981
+ reasonCounts.set(s.reason, (reasonCounts.get(s.reason) ?? 0) + 1);
982
+ console.log(` skipped: ${result.skipped.length}`);
983
+ for (const [reason, n] of reasonCounts)
984
+ console.log(` - ${reason}${n > 1 ? ` (×${n})` : ''}`);
985
+ }
986
+ if (result.proposalsCreated > 0) {
987
+ console.log('');
988
+ console.log('Review with: memesh dream list');
989
+ console.log('Accept: memesh dream accept <id>');
990
+ }
991
+ });
992
+ return;
993
+ }
853
994
  await withDatabase(async () => {
854
995
  const { runDreamer } = await import('../../core/dreamer.js');
855
- const { readConfig } = await import('../../core/config.js');
856
996
  const { getDatabase } = await import('../../db.js');
857
997
  const cfg = readConfig();
858
- if (!cfg.llm) {
859
- console.error('No LLM configured. Run `memesh config set llm.provider <anthropic|openai|ollama>` first.');
998
+ const llm = detectCapabilities().llm;
999
+ if (!llm) {
1000
+ console.error('No LLM configured. Run `memesh config set llm.provider <anthropic|openai|ollama>` first (or set ANTHROPIC_API_KEY / OPENAI_API_KEY).');
860
1001
  console.error('LLM is required for `memesh dream` because consolidation is a semantic decision, not a rule.');
861
1002
  process.exit(1);
862
1003
  }
863
- const result = await runDreamer(getDatabase(), cfg.llm, {
1004
+ const result = await runDreamer(getDatabase(), llm, {
864
1005
  project: opts.project,
865
1006
  dryRun: !!opts.dryRun,
866
1007
  maxLlmCalls: opts.maxLlmCalls,
@@ -900,14 +1041,15 @@ dreamCmd
900
1041
  .action(async (opts) => {
901
1042
  await withDatabase(async () => {
902
1043
  const { runPatternDetector } = await import('../../core/dreamer.js');
903
- const { readConfig } = await import('../../core/config.js');
904
1044
  const { getDatabase } = await import('../../db.js');
905
1045
  const cfg = readConfig();
906
- if (!cfg.llm) {
1046
+ const llm = detectCapabilities().llm;
1047
+ if (!llm) {
907
1048
  console.error('No LLM configured. Pattern detection requires an LLM.');
1049
+ console.error('Run `memesh config set llm.provider <anthropic|openai|ollama>` first (or set ANTHROPIC_API_KEY / OPENAI_API_KEY).');
908
1050
  process.exit(1);
909
1051
  }
910
- const result = await runPatternDetector(getDatabase(), cfg.llm, {
1052
+ const result = await runPatternDetector(getDatabase(), llm, {
911
1053
  project: opts.project,
912
1054
  dryRun: !!opts.dryRun,
913
1055
  maxLlmCalls: opts.maxLlmCalls,
@@ -952,12 +1094,48 @@ dreamCmd
952
1094
  console.log(`${proposals.length} ${opts.status} proposal(s):`);
953
1095
  console.log('');
954
1096
  for (const p of proposals) {
955
- console.log(` #${p.id} [${p.project}/${p.cluster_key}] ${p.source_count} sources "${p.digest_name}"`);
956
- console.log(` ${p.digest_observations_preview}`);
1097
+ const srcLabel = p.source_kind === 'transcript' ? ' (transcript)' : '';
1098
+ console.log(` #${p.id} [${p.project}/${p.cluster_key}]${srcLabel} ${p.source_count} source(s) → "${p.digest_name}"`);
1099
+ if (p.digest_observations_preview !== null) {
1100
+ console.log(` ${p.digest_observations_preview}`);
1101
+ }
957
1102
  console.log(` created: ${p.created_at}`);
958
1103
  console.log('');
959
1104
  }
960
- console.log(`Apply: memesh dream accept <id> | Reject: memesh dream reject <id>`);
1105
+ console.log(`Inspect: memesh dream show <id> | Apply: memesh dream accept <id> | Reject: memesh dream reject <id>`);
1106
+ });
1107
+ });
1108
+ dreamCmd
1109
+ .command('show <id>')
1110
+ .description('Show a proposal in full — name, type, ALL observations, tags, source — so you can review the whole thing before accepting')
1111
+ .option('--json', 'Output JSON')
1112
+ .action(async (id, opts) => {
1113
+ await withDatabase(async () => {
1114
+ const { getProposalDetail } = await import('../../core/dreamer.js');
1115
+ const { getDatabase } = await import('../../db.js');
1116
+ const detail = getProposalDetail(getDatabase(), parseInt(id, 10));
1117
+ if (!detail) {
1118
+ console.error(`proposal #${id} not found`);
1119
+ console.error('See ids with: memesh dream list');
1120
+ process.exit(1);
1121
+ }
1122
+ if (opts.json) {
1123
+ console.log(JSON.stringify(detail, null, 2));
1124
+ return;
1125
+ }
1126
+ console.log(`Proposal #${detail.id} [${detail.project}/${detail.cluster_key}] source: ${detail.source_kind} status: ${detail.status}`);
1127
+ console.log(`created: ${detail.created_at}`);
1128
+ console.log('');
1129
+ console.log(`name: ${detail.digest.name}`);
1130
+ console.log(`type: ${detail.digest.type}`);
1131
+ console.log(`observations (${detail.digest.observations.length}):`);
1132
+ for (const o of detail.digest.observations)
1133
+ console.log(` - ${o}`);
1134
+ if (detail.digest.tags.length > 0)
1135
+ console.log(`tags: ${detail.digest.tags.join(', ')}`);
1136
+ console.log(`source: ${JSON.stringify(detail.source)}`);
1137
+ console.log('');
1138
+ console.log(`Accept: memesh dream accept ${detail.id} | Reject: memesh dream reject ${detail.id}`);
961
1139
  });
962
1140
  });
963
1141
  dreamCmd
@@ -969,7 +1147,16 @@ dreamCmd
969
1147
  const { getDatabase } = await import('../../db.js');
970
1148
  const { KnowledgeGraph } = await import('../../knowledge-graph.js');
971
1149
  const kg = new KnowledgeGraph(getDatabase());
972
- const result = applyProposal(getDatabase(), parseInt(id, 10), kg);
1150
+ let result;
1151
+ try {
1152
+ result = applyProposal(getDatabase(), parseInt(id, 10), kg);
1153
+ }
1154
+ catch (err) {
1155
+ console.error(err instanceof Error ? err.message : String(err));
1156
+ console.error('See pending ids with: memesh dream list');
1157
+ process.exit(1);
1158
+ }
1159
+ await flushPendingEmbeddings();
973
1160
  console.log(`Applied proposal #${result.proposalId}`);
974
1161
  console.log(` digest entity: ${result.digestEntityName}`);
975
1162
  console.log(` sources archived: ${result.sourcesArchived}`);
@@ -983,7 +1170,14 @@ dreamCmd
983
1170
  await withDatabase(async () => {
984
1171
  const { rejectProposal } = await import('../../core/dreamer.js');
985
1172
  const { getDatabase } = await import('../../db.js');
986
- rejectProposal(getDatabase(), parseInt(id, 10), opts.reason);
1173
+ try {
1174
+ rejectProposal(getDatabase(), parseInt(id, 10), opts.reason);
1175
+ }
1176
+ catch (err) {
1177
+ console.error(err instanceof Error ? err.message : String(err));
1178
+ console.error('See pending ids with: memesh dream list');
1179
+ process.exit(1);
1180
+ }
987
1181
  console.log(`Rejected proposal #${id}`);
988
1182
  });
989
1183
  });
@@ -1155,9 +1349,8 @@ program
1155
1349
  console.error('❌ Could not produce a test embedding at this database\'s vector width, so\n' +
1156
1350
  ' the index was left untouched. Rebuilding it deletes every stored\n' +
1157
1351
  ' embedding, and nothing here could regenerate them.\n' +
1158
- ' Check that your OpenAI API key is valid, or that Ollama is running, or\n' +
1159
- ' install @huggingface/transformers for local embeddings then run this\n' +
1160
- ' again. `memesh doctor` reports which provider is configured.');
1352
+ ' Check that Ollama is running (or that your OpenAI API key is valid) —\n' +
1353
+ ' then run this again. `memesh doctor` reports which provider is configured.');
1161
1354
  process.exit(1);
1162
1355
  }
1163
1356
  }
@@ -1274,40 +1467,11 @@ program.action(async () => {
1274
1467
  if (stray.length > 0) {
1275
1468
  console.error(`Error: unknown command '${stray[0]}'.`);
1276
1469
  console.error(` Run 'memesh --help' to see available commands.`);
1277
- process.exit(1);
1278
- }
1279
- const { startServer } = await import('../http/server.js');
1280
- const server = startServer('127.0.0.1', 0);
1281
- await new Promise((resolve, reject) => {
1282
- server.once('listening', resolve);
1283
- server.once('error', reject);
1284
- });
1285
- const addr = server.address();
1286
- if (!addr) {
1287
- console.error('Failed to start dashboard server');
1288
- process.exit(1);
1289
- }
1290
- const url = `http://127.0.0.1:${addr.port}/dashboard`;
1291
- console.log(`MeMesh dashboard: ${url}`);
1292
- console.log('Press Ctrl+C to stop.');
1293
- const { execFile } = await import('child_process');
1294
- if (process.platform === 'darwin') {
1295
- execFile('open', [url]);
1296
- }
1297
- else if (process.platform === 'win32') {
1298
- execFile('cmd.exe', ['/c', 'start', '', url]);
1299
- }
1300
- else {
1301
- execFile('xdg-open', [url]);
1470
+ process.exitCode = 1;
1471
+ return;
1302
1472
  }
1303
- process.on('SIGINT', () => {
1304
- server.close();
1305
- try {
1306
- closeDatabase();
1307
- }
1308
- catch { }
1309
- process.exit(0);
1310
- });
1473
+ program.outputHelp();
1474
+ process.exitCode = 0;
1311
1475
  });
1312
1476
  program.parse();
1313
1477
  //# sourceMappingURL=cli.js.map