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 +3 -1
- package/dist/bin/setup.js +54 -4
- package/dist/lib/telemetry.js +13 -0
- package/package.json +1 -1
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.**
|
|
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
|
-
|
|
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 (
|
|
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}`);
|
package/dist/lib/telemetry.js
CHANGED
|
@@ -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.
|
|
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",
|