memex-mvp 0.10.9 → 0.10.11
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/ingest.js +15 -0
- package/lib/cli/index.js +43 -10
- package/lib/telegram-notify.js +50 -0
- package/package.json +2 -2
- package/server.js +26 -8
- package/skills/install-memex/SKILL.md +8 -4
package/ingest.js
CHANGED
|
@@ -293,6 +293,21 @@ async function cmdInstall() {
|
|
|
293
293
|
console.log('');
|
|
294
294
|
console.log(`config: ${CONFIG_PATH} (auto-created on first edit)`);
|
|
295
295
|
console.log(`status: npx memex-sync status`);
|
|
296
|
+
|
|
297
|
+
// v0.10.10: surface the new web dashboard so manual installers actually
|
|
298
|
+
// discover it. (curl-bash flow has its own [Y/n] prompt in install.sh —
|
|
299
|
+
// it suppresses this output via `>/dev/null 2>&1`, so this callout is
|
|
300
|
+
// for the `memex-sync install` direct-call path only.)
|
|
301
|
+
console.log('');
|
|
302
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
303
|
+
console.log('🌐 NEW in v0.10.8: open your memory in a browser');
|
|
304
|
+
console.log('');
|
|
305
|
+
console.log(' memex web --open');
|
|
306
|
+
console.log('');
|
|
307
|
+
console.log('5 pages, read-only, localhost-only. Every captured');
|
|
308
|
+
console.log('conversation, verbatim — not summarized. Ctrl+C to stop.');
|
|
309
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
310
|
+
|
|
296
311
|
process.exit(0);
|
|
297
312
|
}
|
|
298
313
|
|
package/lib/cli/index.js
CHANGED
|
@@ -150,20 +150,41 @@ function maybePrintTelegramTip(opts = {}, currentSubcommand = '') {
|
|
|
150
150
|
async function afterCommand(opts = {}, currentSubcommand = '') {
|
|
151
151
|
if (opts.json) return;
|
|
152
152
|
if (currentSubcommand === 'telegram') return;
|
|
153
|
+
if (currentSubcommand === 'web') return; // web command holds the loop; tip would never print anyway
|
|
154
|
+
// `context` output IS the SessionStart hook payload — it gets injected
|
|
155
|
+
// verbatim into Claude Code's session header. Tips would corrupt that
|
|
156
|
+
// (and bust the --budget-tokens cap that the hook contract relies on).
|
|
157
|
+
if (currentSubcommand === 'context') return;
|
|
153
158
|
if (process.env.MEMEX_TIP_SUPPRESS === '1') return;
|
|
154
159
|
try {
|
|
155
|
-
const { listPending } = await import('../telegram-pending.js');
|
|
156
|
-
const list = listPending();
|
|
157
|
-
if (!list || list.length === 0) return;
|
|
158
160
|
const notify = await import('../telegram-notify.js');
|
|
159
161
|
const state = notify.loadNotifyState();
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
162
|
+
|
|
163
|
+
// Channel B priority: TG-pending tip first (it's actionable — there's
|
|
164
|
+
// a real export waiting). Dashboard tip is "nice to know", so only
|
|
165
|
+
// show it when the TG channel has nothing to surface.
|
|
166
|
+
const { listPending } = await import('../telegram-pending.js');
|
|
167
|
+
const list = listPending();
|
|
168
|
+
const hasPending = list && list.length > 0;
|
|
169
|
+
|
|
170
|
+
if (hasPending && notify.cliTipDue(state)) {
|
|
171
|
+
const showTitles = state.notifications.show_titles !== false;
|
|
172
|
+
const tip = notify.formatTelegramTip(list, { showTitles });
|
|
173
|
+
if (tip) {
|
|
174
|
+
console.log(tip);
|
|
175
|
+
notify.markCliTipShown(state);
|
|
176
|
+
notify.saveNotifyState(state);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!hasPending && notify.dashboardTipDue(state)) {
|
|
182
|
+
const tip = notify.formatDashboardTip();
|
|
183
|
+
if (tip) {
|
|
184
|
+
console.log(c.dim(tip));
|
|
185
|
+
notify.markDashboardTipShown(state);
|
|
186
|
+
notify.saveNotifyState(state);
|
|
187
|
+
}
|
|
167
188
|
}
|
|
168
189
|
} catch (_) {
|
|
169
190
|
/* never break a command because of a tip */
|
|
@@ -1737,6 +1758,18 @@ Examples:
|
|
|
1737
1758
|
|
|
1738
1759
|
const { startServer } = await import('../web/index.js');
|
|
1739
1760
|
await startServer(opts);
|
|
1761
|
+
|
|
1762
|
+
// First successful start ever → silence the discovery tip forever.
|
|
1763
|
+
// User has clearly found the feature; no need to keep nagging.
|
|
1764
|
+
try {
|
|
1765
|
+
const notify = await import('../telegram-notify.js');
|
|
1766
|
+
const state = notify.loadNotifyState();
|
|
1767
|
+
if (!state.dashboard_ever_opened) {
|
|
1768
|
+
notify.markDashboardEverOpened(state);
|
|
1769
|
+
notify.saveNotifyState(state);
|
|
1770
|
+
}
|
|
1771
|
+
} catch (_) { /* never break server start on notify-state write */ }
|
|
1772
|
+
|
|
1740
1773
|
// The HTTP server now holds the event loop open. Park on an unsettled
|
|
1741
1774
|
// promise so cmdWeb never returns to the dispatcher — that way server.js
|
|
1742
1775
|
// doesn't reach process.exit(0) below or fall through to MCP-mode init.
|
package/lib/telegram-notify.js
CHANGED
|
@@ -47,6 +47,13 @@ const DEFAULT_STATE = () => ({
|
|
|
47
47
|
version: 1,
|
|
48
48
|
cli_tip_last_shown_at: null,
|
|
49
49
|
notif_shown_for_ids: [],
|
|
50
|
+
// v0.10.10: dashboard discovery throttle. Three-strike pattern so the tip
|
|
51
|
+
// appears on a few different terminal sessions in the first days after
|
|
52
|
+
// install, then quiets down. Becomes permanently silent once the user
|
|
53
|
+
// actually opens the dashboard at least once.
|
|
54
|
+
dashboard_tip_shown_count: 0,
|
|
55
|
+
dashboard_tip_last_shown_at: null,
|
|
56
|
+
dashboard_ever_opened: false,
|
|
50
57
|
notifications: {
|
|
51
58
|
enabled: false, // privacy-first: opt-in for macOS notification
|
|
52
59
|
show_titles: false, // even when on, don't leak chat names by default
|
|
@@ -105,6 +112,49 @@ export function markCliTipShown(state, now = new Date()) {
|
|
|
105
112
|
return state;
|
|
106
113
|
}
|
|
107
114
|
|
|
115
|
+
// ---------------------- Dashboard discovery tip (v0.10.10) ----------------------
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Whether the "try memex web" tip should fire on the next CLI command.
|
|
119
|
+
*
|
|
120
|
+
* - Hard-stop once the user has actually run `memex web` (any duration)
|
|
121
|
+
* - Cap at maxShows (default 3) total reveals
|
|
122
|
+
* - Cooldown cooldownHours (default 12) between reveals so it doesn't
|
|
123
|
+
* stack with the TG-pending tip on the same minute
|
|
124
|
+
*/
|
|
125
|
+
export function dashboardTipDue(state, opts = {}) {
|
|
126
|
+
const { maxShows = 3, cooldownHours = 12 } = opts;
|
|
127
|
+
if (state.dashboard_ever_opened) return false;
|
|
128
|
+
if ((state.dashboard_tip_shown_count || 0) >= maxShows) return false;
|
|
129
|
+
if (!state.dashboard_tip_last_shown_at) return true;
|
|
130
|
+
const last = Date.parse(state.dashboard_tip_last_shown_at);
|
|
131
|
+
if (isNaN(last)) return true;
|
|
132
|
+
return Date.now() - last >= cooldownHours * ONE_HOUR_MS;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function markDashboardTipShown(state, now = new Date()) {
|
|
136
|
+
state.dashboard_tip_shown_count = (state.dashboard_tip_shown_count || 0) + 1;
|
|
137
|
+
state.dashboard_tip_last_shown_at = now.toISOString();
|
|
138
|
+
return state;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Permanently silence the dashboard tip — call this the first time the user
|
|
143
|
+
* actually runs `memex web`. They've discovered it; no need to keep nagging.
|
|
144
|
+
*/
|
|
145
|
+
export function markDashboardEverOpened(state) {
|
|
146
|
+
state.dashboard_ever_opened = true;
|
|
147
|
+
return state;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The tip text itself. Plain string (no ANSI) — the caller decides whether
|
|
152
|
+
* to dim it. Returns null if there is genuinely nothing to say (defensive).
|
|
153
|
+
*/
|
|
154
|
+
export function formatDashboardTip() {
|
|
155
|
+
return '💡 New: try `memex web --open` — browse your memory in a browser (read-only, localhost only).';
|
|
156
|
+
}
|
|
157
|
+
|
|
108
158
|
// ---------------------- Notification dedup ----------------------
|
|
109
159
|
|
|
110
160
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memex-mvp",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.11",
|
|
4
4
|
"description": "Local-first MCP server for cross-agent AI memory. One SQLite + FTS5 corpus across Claude Code, Cowork, Cursor, Continue, Zed, Obsidian, and Telegram — passively captured, verbatim, searchable from any MCP-compatible client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"sync": "node ingest.js",
|
|
27
27
|
"ingest": "node ingest.js",
|
|
28
28
|
"bot": "node bot/index.js",
|
|
29
|
-
"test": "node test/parser.test.js && node test/bot-inbox.test.js && node test/search-sort.test.js && node test/store-document.test.js && node test/cli.test.js && node test/hook.test.js && node test/telegram-html.test.js && node test/telegram-decisions.test.js && node test/telegram-pending.test.js && node test/telegram-notify.test.js && node test/notify-click-action.test.js",
|
|
29
|
+
"test": "node test/parser.test.js && node test/bot-inbox.test.js && node test/search-sort.test.js && node test/store-document.test.js && node test/cli.test.js && node test/hook.test.js && node test/telegram-html.test.js && node test/telegram-decisions.test.js && node test/telegram-pending.test.js && node test/telegram-notify.test.js && node test/notify-click-action.test.js && node test/inbox-watcher.test.js && node test/e2e-inbox.test.js",
|
|
30
30
|
"prepublishOnly": "npm test"
|
|
31
31
|
},
|
|
32
32
|
"engines": {
|
package/server.js
CHANGED
|
@@ -810,18 +810,23 @@ function importFile(filePath) {
|
|
|
810
810
|
Math.floor(Date.now() / 1000),
|
|
811
811
|
imported
|
|
812
812
|
);
|
|
813
|
+
log(`imported ${imported} messages from ${basename(filePath)} (${source})`);
|
|
814
|
+
} else {
|
|
815
|
+
log(`no NEW messages from ${basename(filePath)} (all dupes)`);
|
|
816
|
+
}
|
|
813
817
|
|
|
814
|
-
|
|
818
|
+
// Move processed file to archive regardless of imported count. If we only
|
|
819
|
+
// archive when imported>0, a fully-deduplicated snapshot stays in inbox.
|
|
820
|
+
// Daemon then overwrites that file periodically — and on filesystems where
|
|
821
|
+
// rename-over-existing only fires chokidar 'change' (not 'add'), the
|
|
822
|
+
// 'change' listener above re-imports, fine; but it also means a wasted
|
|
823
|
+
// file accumulates in inbox if for any reason the listener didn't catch.
|
|
824
|
+
// Archiving always keeps inbox a clean "what's new" queue.
|
|
825
|
+
if (source !== 'unknown') {
|
|
815
826
|
const targetDir = join(ARCHIVE, source);
|
|
816
827
|
mkdirSync(targetDir, { recursive: true });
|
|
817
828
|
const target = join(targetDir, basename(filePath));
|
|
818
|
-
try {
|
|
819
|
-
renameSync(filePath, target);
|
|
820
|
-
} catch (_) {}
|
|
821
|
-
|
|
822
|
-
log(`imported ${imported} messages from ${basename(filePath)} (${source})`);
|
|
823
|
-
} else {
|
|
824
|
-
log(`no messages imported from ${basename(filePath)}`);
|
|
829
|
+
try { renameSync(filePath, target); } catch (_) {}
|
|
825
830
|
}
|
|
826
831
|
return imported;
|
|
827
832
|
}
|
|
@@ -852,6 +857,19 @@ chokidar
|
|
|
852
857
|
log('inbox detected (file):', basename(filePath));
|
|
853
858
|
importFile(filePath);
|
|
854
859
|
})
|
|
860
|
+
// 'change' is critical: the ingest daemon overwrites the inbox snapshot
|
|
861
|
+
// file every few seconds as the underlying Claude Code / Cowork JSONL
|
|
862
|
+
// grows (the snapshot is a full re-serialisation, not a delta append).
|
|
863
|
+
// Without a 'change' listener, chokidar only fires 'add' once when the
|
|
864
|
+
// file first appears — every subsequent overwrite is silent, the inbox
|
|
865
|
+
// file accumulates new content on disk but server.js never re-imports
|
|
866
|
+
// it. UNIQUE(source, conv, msg_id) + INSERT OR IGNORE keep repeated
|
|
867
|
+
// imports idempotent, so re-processing the whole file on every change
|
|
868
|
+
// is correct (and cheap: SQLite handles ~10k rows in tens of ms).
|
|
869
|
+
.on('change', (filePath) => {
|
|
870
|
+
log('inbox changed (file):', basename(filePath));
|
|
871
|
+
importFile(filePath);
|
|
872
|
+
})
|
|
855
873
|
.on('addDir', (dirPath) => {
|
|
856
874
|
// Skip the inbox itself
|
|
857
875
|
if (dirPath === INBOX) return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: install-memex
|
|
3
3
|
description: Make Claude, Cursor, Cline, Continue, and Zed remember every AI conversation forever — one local SQLite corpus shared across all of them. Installs memex (local-first MCP server) in ~60 seconds via curl one-liner. Includes auto-capture daemon for Claude Code / Cowork / Cursor / Obsidian; v0.10 Telegram auto-detect (export from Desktop → memex stages it → AI proactively asks which to import, privacy-first per-chat consent); v0.8 SessionStart hook for the Brian Chesky moment ("Claude already knows what you were doing"); URL / Perplexity / AI chat share capture via memex_store_document. 18 MCP tools, no cloud, no account, verbatim storage. Use when the user says "install memex", "set up memex", "add memory to my AI", "make my agent remember across sessions", "сохрани мои чаты", or similar.
|
|
4
|
-
version: 1.4.
|
|
4
|
+
version: 1.4.1
|
|
5
5
|
metadata:
|
|
6
6
|
openclaw:
|
|
7
7
|
emoji: "📚"
|
|
@@ -290,15 +290,19 @@ If the user declines Telegram setup ("not now" / "skip"): say "OK, I'll skip Tel
|
|
|
290
290
|
|
|
291
291
|
Tell the user to fully quit and reopen the MCP client (Cmd+Q on macOS) so it picks up the new memex tools.
|
|
292
292
|
|
|
293
|
-
|
|
293
|
+
**🌟 FIRST, STRONGLY RECOMMEND THIS (v0.10.8+):** offer to run `memex web --open` for them right now. It's the single most magical post-install moment — a local browser tab opens at `http://127.0.0.1:8765` with 5 pages showing every conversation memex has captured, dialogue rendered as chat-bubbles, full-text searchable. Verbatim — not AI-summarized. Read-only, localhost-only, Ctrl+C to stop. Phrase it naturally, e.g.:
|
|
294
|
+
|
|
295
|
+
> "I can open a local web dashboard that lets you actually *see* what memex captured — every message, in chat-bubble form, searchable. It's just a browser tab on `localhost:8765`. Run it now? It's a way better demo than asking me search queries blind."
|
|
296
|
+
|
|
297
|
+
Then run `memex web --open` (in a backgrounded shell if your shell wrapper supports it — otherwise tell the user to run it themselves in another terminal). Wait for them to react before continuing.
|
|
298
|
+
|
|
299
|
+
**After that, suggest other ways to confirm end-to-end:**
|
|
294
300
|
- "show me what memex has in memory" → triggers `memex_overview`
|
|
295
301
|
- "what projects has memex captured" → triggers `memex_list_projects`
|
|
296
302
|
- "search memex for [recent topic]" → triggers `memex_search`
|
|
297
303
|
- "save https://en.wikipedia.org/wiki/As_We_May_Think to memex" → triggers `memex_store_document` and teaches the user that URL-saving exists (v0.6+)
|
|
298
304
|
- **Open Claude Code in any project the user worked on recently** — the SessionStart auto-context (v0.8+) should kick in and Claude will mention prior work _before_ the user types anything. This is the "Brian Chesky moment" — the magical-first-impression of memex.
|
|
299
|
-
|
|
300
305
|
- **(if Telegram was set up)** `memex telegram check` — confirms daemon's Telegram-Downloads watcher is active and shows the user's full capture pipeline state.
|
|
301
|
-
- **(v0.10.8+)** `memex web --open` — opens the local read-only web dashboard at `http://127.0.0.1:8765`. 5 pages: corpus stats, FTS5-searchable conversations list, verbatim chat-bubble transcripts, pending Telegram review with checkboxes, settings/daemon status. Useful for the user to *see* their memory with their own eyes — and a strong demo moment ("look at the actual messages verbatim, not an AI summary"). Localhost-only by default; Ctrl+C to stop.
|
|
302
306
|
|
|
303
307
|
These confirm everything works end-to-end.
|
|
304
308
|
|