memex-mvp 0.10.8 → 0.10.10
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 +53 -12
- package/lib/telegram-notify.js +50 -0
- package/package.json +1 -1
- package/server.js +7 -15
- 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 */
|
|
@@ -1707,7 +1728,10 @@ Examples:
|
|
|
1707
1728
|
memex web --port 9000 custom port
|
|
1708
1729
|
memex web --public --token s3cret bind on 0.0.0.0 with bearer auth
|
|
1709
1730
|
`);
|
|
1710
|
-
|
|
1731
|
+
// Help path: exit cleanly so the caller (server.js) doesn't fall through
|
|
1732
|
+
// to MCP-mode init, and Node doesn't warn about an unsettled top-level
|
|
1733
|
+
// await waiting on an HTTP server that was never started.
|
|
1734
|
+
process.exit(0);
|
|
1711
1735
|
}
|
|
1712
1736
|
else if (a.startsWith('--')) {
|
|
1713
1737
|
console.error(`Unknown flag: ${a}`);
|
|
@@ -1734,7 +1758,24 @@ Examples:
|
|
|
1734
1758
|
|
|
1735
1759
|
const { startServer } = await import('../web/index.js');
|
|
1736
1760
|
await startServer(opts);
|
|
1737
|
-
|
|
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
|
+
|
|
1773
|
+
// The HTTP server now holds the event loop open. Park on an unsettled
|
|
1774
|
+
// promise so cmdWeb never returns to the dispatcher — that way server.js
|
|
1775
|
+
// doesn't reach process.exit(0) below or fall through to MCP-mode init.
|
|
1776
|
+
// (Required because `await runCli(...)` would otherwise resolve as soon
|
|
1777
|
+
// as startServer's listen-callback fires.)
|
|
1778
|
+
await new Promise(() => {});
|
|
1738
1779
|
}
|
|
1739
1780
|
|
|
1740
1781
|
// =============================================================
|
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.10",
|
|
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",
|
package/server.js
CHANGED
|
@@ -64,31 +64,23 @@ import { runCli, CLI_SUBCOMMAND_NAMES } from './lib/cli/index.js';
|
|
|
64
64
|
//
|
|
65
65
|
// This runs BEFORE any DB/watcher side-effects so the CLI doesn't open
|
|
66
66
|
// the DB in write mode unnecessarily.
|
|
67
|
-
// We use a labelled async IIFE so we can `break` (or just fall through)
|
|
68
|
-
// without abusing top-level `return`, which is illegal in ESM modules.
|
|
69
67
|
{
|
|
70
68
|
const sub = process.argv[2];
|
|
71
69
|
if (sub && CLI_SUBCOMMAND_NAMES.includes(sub)) {
|
|
70
|
+
// For long-running subs (currently only `web`), cmdWeb parks on an
|
|
71
|
+
// unsettled promise after starting the HTTP server, so this `await`
|
|
72
|
+
// never resolves and process.exit below is never reached. For one-shot
|
|
73
|
+
// subs (search, recent, telegram, …), runCli returns and we exit here.
|
|
72
74
|
await runCli(sub, process.argv.slice(3));
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (sub !== 'web') process.exit(0);
|
|
77
|
-
} else if (sub && !sub.startsWith('-')) {
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
if (sub && !sub.startsWith('-')) {
|
|
78
78
|
// Unknown positional subcommand — fail fast with help, don't drift
|
|
79
79
|
// into MCP mode (which would just hang waiting for stdin).
|
|
80
80
|
console.error(`Unknown subcommand: ${sub}`);
|
|
81
81
|
console.error(`Run 'memex --help' for usage.`);
|
|
82
82
|
process.exit(2);
|
|
83
83
|
}
|
|
84
|
-
// For 'web': fall through to the rest of server.js so it keeps running.
|
|
85
|
-
// Actually no — the rest of server.js initializes MCP mode (stdin handler,
|
|
86
|
-
// DB watchers). For 'web' we DON'T want that. Sentinel-exit instead:
|
|
87
|
-
if (sub === 'web') {
|
|
88
|
-
// Server is listening — nothing more for this process to do.
|
|
89
|
-
// Just sit on the event loop until the HTTP server is closed.
|
|
90
|
-
await new Promise(() => {});
|
|
91
|
-
}
|
|
92
84
|
// No args (or only flags we don't recognize) → MCP mode
|
|
93
85
|
}
|
|
94
86
|
|
|
@@ -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
|
|