linksee-memory 0.11.4 → 0.11.5

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/README.md CHANGED
@@ -618,7 +618,7 @@ Use both.
618
618
  linksee-memory runs locally and is built to read — and send — as little as possible.
619
619
 
620
620
  - **Local-first.** Memory is one SQLite file at `~/.linksee-memory/memory.db`. No account, no cloud, no API key.
621
- - **Telemetry is opt-in and OFF by default.** Nothing is sent unless you set `LINKSEE_TELEMETRY=basic`. Even then it never sends your source code, file contents, prompts, conversation, entity/project names, or the memory DB — only anonymous counters ([details](#telemetry-opt-in-off-by-default)).
621
+ - **Telemetry is opt-in and OFF by default.** `setup` asks once; nothing is sent unless you agree there (or set `LINKSEE_TELEMETRY=basic`). Even then it never sends your source code, file contents, prompts, conversation, entity/project names, or the memory DB — only anonymous counters ([details](#telemetry-opt-in-off-by-default)).
622
622
  - **No automatic repo crawling.** linksee reads: memory you explicitly save, your `map.yaml`, the specific files a map reality-check points at, the local SQLite DB, and — when the Stop hook fires — your Claude Code session transcript (locally, to capture what happened). It does **not** crawl your repo, read `.env`/secrets/`node_modules`, or touch your home directory on its own.
623
623
  - **Clean MCP transport.** The server writes only JSON-RPC to stdout; all logs go to stderr.
624
624
  - **Hooks are documented and removable.** `setup` adds a Stop hook (session capture) and an optional guard hook. They make no network calls by default, are time-bounded, fail-open (a hook error never breaks your session), and are listed under [Uninstall](#uninstall).
@@ -636,6 +636,8 @@ linksee-memory ships with **opt-in** anonymous telemetry that helps us understan
636
636
  ```bash
637
637
  export LINKSEE_TELEMETRY=basic # opt in
638
638
  export LINKSEE_TELEMETRY=off # opt out (or just unset the variable)
639
+ # `linksee-memory setup` also asks once and records your choice in
640
+ # ~/.linksee-memory/telemetry-consent (delete that file to be asked again).
639
641
  ```
640
642
 
641
643
  ### Exactly what gets sent (Level 1 contract)
package/dist/bin/setup.js CHANGED
@@ -70,6 +70,9 @@ const GUARD_COMMAND = 'npx -y linksee-memory guard';
70
70
  const PROJECT_DIR = process.cwd();
71
71
  const PROJECT_CLAUDE_DIR = join(PROJECT_DIR, '.claude');
72
72
  const PROJECT_SETTINGS_PATH = join(PROJECT_CLAUDE_DIR, 'settings.json');
73
+ // Opt-in telemetry consent recorded at setup time (read by telemetry.ts getTelemetryMode).
74
+ const TELEMETRY_DIR = process.env.LINKSEE_MEMORY_DIR ?? join(HOME, '.linksee-memory');
75
+ const TELEMETRY_CONSENT_FILE = join(TELEMETRY_DIR, 'telemetry-consent');
73
76
  const CHECK = '\x1b[32m✓\x1b[0m';
74
77
  const SKIP = '\x1b[33m○\x1b[0m';
75
78
  const FAIL = '\x1b[31m✗\x1b[0m';
@@ -225,13 +228,15 @@ const GUARD_HOOKS = {
225
228
  function guardWiredFor(s, ev) {
226
229
  return (s.hooks?.[ev] ?? []).some((entry) => entry?.hooks?.some((h) => typeof h?.command === 'string' && h.command.includes('linksee-memory-guard')));
227
230
  }
228
- function askYesNo(question) {
231
+ function askYesNo(question, defaultYes = true) {
229
232
  return new Promise((resolve) => {
230
233
  const rl = createInterface({ input: process.stdin, output: process.stdout });
231
- rl.question(`${question} [Y/n] `, (ans) => {
234
+ rl.question(`${question} ${defaultYes ? '[Y/n]' : '[y/N]'} `, (ans) => {
232
235
  rl.close();
233
236
  const a = ans.trim().toLowerCase();
234
- resolve(a === '' || a === 'y' || a === 'yes'); // default = yes
237
+ if (a === '')
238
+ return resolve(defaultYes);
239
+ resolve(a === 'y' || a === 'yes');
235
240
  });
236
241
  });
237
242
  }
@@ -288,13 +293,58 @@ async function configureGuard() {
288
293
  }
289
294
  const guardConfigured = await configureGuard();
290
295
  console.log('');
296
+ // ── Telemetry consent (opt-in, anonymous, off unless you agree) ──────────
297
+ async function configureTelemetryConsent() {
298
+ console.log(`${BOLD}Anonymous usage stats${RESET} ${DIM}(optional)${RESET}`);
299
+ const env = (process.env.LINKSEE_TELEMETRY || '').toLowerCase().trim();
300
+ if (env) {
301
+ console.log(` ${SKIP} Controlled by LINKSEE_TELEMETRY=${env} ${DIM}(env overrides)${RESET}`);
302
+ return;
303
+ }
304
+ if (existsSync(TELEMETRY_CONSENT_FILE)) {
305
+ let cur = 'off';
306
+ try {
307
+ cur = (readFileSync(TELEMETRY_CONSENT_FILE, 'utf8').trim().toLowerCase()) || 'off';
308
+ }
309
+ catch { /* ignore */ }
310
+ console.log(` ${SKIP} Already chosen: ${cur === 'basic' ? 'on' : 'off'} ${DIM}(edit ${TELEMETRY_CONSENT_FILE} to change)${RESET}`);
311
+ return;
312
+ }
313
+ if (dryRun) {
314
+ console.log(` ${DIM}[dry-run] Would ask whether to share anonymous usage stats${RESET}`);
315
+ return;
316
+ }
317
+ const dnt = process.env.DO_NOT_TRACK === '1' || process.env.DO_NOT_TRACK === 'true';
318
+ if (dnt || autoYes || !process.stdin.isTTY) {
319
+ try {
320
+ mkdirSync(TELEMETRY_DIR, { recursive: true });
321
+ writeFileSync(TELEMETRY_CONSENT_FILE, 'off');
322
+ }
323
+ catch { /* best-effort */ }
324
+ const why = dnt ? 'DO_NOT_TRACK' : autoYes ? '--yes → off' : 'non-interactive';
325
+ console.log(` ${SKIP} Left off ${DIM}(${why})${RESET}`);
326
+ return;
327
+ }
328
+ console.log(` ${DIM}Helps us see which workflows actually work. Anonymous counts only —`);
329
+ console.log(` never your memory, code, prompts, file contents, entity names, or paths.`);
330
+ console.log(` Off unless you say yes; change anytime with LINKSEE_TELEMETRY=off.${RESET}`);
331
+ const ok = await askYesNo(' Share anonymous usage stats?', false);
332
+ try {
333
+ mkdirSync(TELEMETRY_DIR, { recursive: true });
334
+ writeFileSync(TELEMETRY_CONSENT_FILE, ok ? 'basic' : 'off');
335
+ }
336
+ catch { /* best-effort */ }
337
+ console.log(` ${ok ? CHECK : SKIP} Telemetry ${ok ? 'enabled — thank you!' : 'left off'}`);
338
+ }
339
+ await configureTelemetryConsent();
340
+ console.log('');
291
341
  // ── Summary ──────────────────────────────────────────────
292
342
  console.log(`${BOLD}Setup complete!${RESET}`);
293
343
  console.log('');
294
344
  console.log('How it works:');
295
345
  console.log(` ${DIM}• Every session is auto-captured (decisions, caveats, learnings)${RESET}`);
296
346
  console.log(` ${DIM}• Agent auto-recalls past context when starting a task${RESET}`);
297
- console.log(` ${DIM}• Memory is local-first (nothing leaves your machine)${RESET}`);
347
+ console.log(` ${DIM}• Memory is local-first (your memory never leaves your machine)${RESET}`);
298
348
  console.log(` ${DIM}• Works across Claude Code, Cursor, Windsurf, Codex, Gemini (cross-agent)${RESET}`);
299
349
  if (guardConfigured) {
300
350
  console.log(` ${DIM}• Re-injection guard re-surfaces this project's accepted decisions before edits${RESET}`);
@@ -29,6 +29,19 @@ export function getTelemetryMode() {
29
29
  const v = (process.env.LINKSEE_TELEMETRY || '').toLowerCase().trim();
30
30
  if (v === 'basic' || v === 'on' || v === '1' || v === 'true')
31
31
  return 'basic';
32
+ if (v)
33
+ return 'off'; // any explicit env value (off/0/false/no/…) always wins
34
+ // No env override → use the consent recorded at setup time. Stays off if absent,
35
+ // so "off by default" holds: nothing is sent unless the user agreed at setup.
36
+ try {
37
+ const consentFile = join(TELEMETRY_DIR, 'telemetry-consent');
38
+ if (existsSync(consentFile)) {
39
+ const c = readFileSync(consentFile, 'utf8').trim().toLowerCase();
40
+ if (c === 'basic' || c === 'on')
41
+ return 'basic';
42
+ }
43
+ }
44
+ catch { /* ignore */ }
32
45
  return 'off';
33
46
  }
34
47
  export function getOrCreateAnonId() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linksee-memory",
3
- "version": "0.11.4",
3
+ "version": "0.11.5",
4
4
  "mcpName": "io.github.michielinksee/linksee-memory",
5
5
  "description": "Local-first agent memory MCP — cross-agent brain with drift detection, 6-layer structured memory + token-saving file diff cache",
6
6
  "type": "module",