parallelclaw 1.0.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 (62) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/HELP.md +600 -0
  3. package/LICENSE +21 -0
  4. package/MULTI_MACHINE.md +152 -0
  5. package/README.md +417 -0
  6. package/README.ru.md +740 -0
  7. package/SYNC.md +844 -0
  8. package/bot/README.md +173 -0
  9. package/bot/config.js +66 -0
  10. package/bot/inbox.js +153 -0
  11. package/bot/index.js +294 -0
  12. package/bot/nexara.js +61 -0
  13. package/bot/poll.js +304 -0
  14. package/bot/search.js +155 -0
  15. package/bot/telegram.js +96 -0
  16. package/ingest.js +2712 -0
  17. package/lib/cli/index.js +1987 -0
  18. package/lib/config.js +220 -0
  19. package/lib/db-init.js +158 -0
  20. package/lib/hook/install.js +268 -0
  21. package/lib/import-telegram.js +158 -0
  22. package/lib/ingest-file.js +779 -0
  23. package/lib/notify-click-action.js +281 -0
  24. package/lib/openclaw-channel.js +643 -0
  25. package/lib/parse-cursor.js +172 -0
  26. package/lib/parse-obsidian.js +256 -0
  27. package/lib/parse-telegram-html.js +384 -0
  28. package/lib/parse.js +175 -0
  29. package/lib/render-markdown.js +0 -0
  30. package/lib/store-doc/canonicalize.js +116 -0
  31. package/lib/store-doc/detect.js +209 -0
  32. package/lib/store-doc/extract-title.js +162 -0
  33. package/lib/sync/auth.js +80 -0
  34. package/lib/sync/cert.js +144 -0
  35. package/lib/sync/cli.js +906 -0
  36. package/lib/sync/client.js +138 -0
  37. package/lib/sync/config.js +130 -0
  38. package/lib/sync/pair.js +145 -0
  39. package/lib/sync/pull.js +158 -0
  40. package/lib/sync/push.js +305 -0
  41. package/lib/sync/replicate.js +335 -0
  42. package/lib/sync/server.js +224 -0
  43. package/lib/sync/service.js +726 -0
  44. package/lib/tasks.js +215 -0
  45. package/lib/telegram-decisions.js +165 -0
  46. package/lib/telegram-discovery.js +373 -0
  47. package/lib/telegram-notify.js +272 -0
  48. package/lib/telegram-pending.js +200 -0
  49. package/lib/web/index.js +265 -0
  50. package/lib/web/routes/conversation.js +193 -0
  51. package/lib/web/routes/conversations.js +180 -0
  52. package/lib/web/routes/dashboard.js +175 -0
  53. package/lib/web/routes/pending.js +277 -0
  54. package/lib/web/routes/settings.js +226 -0
  55. package/lib/web/static/style.css +393 -0
  56. package/lib/web/templates.js +234 -0
  57. package/package.json +84 -0
  58. package/server.js +3816 -0
  59. package/skills/install-memex/README.md +109 -0
  60. package/skills/install-memex/SKILL.md +342 -0
  61. package/skills/install-memex/examples.md +294 -0
  62. package/skills/install-memex-claw/SKILL.md +423 -0
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Shared HTML templates for the memex web dashboard.
3
+ *
4
+ * Design: tagged template literals with auto-escaping. No template engine,
5
+ * no JSX, no build step. Match the brand of memex.parallelclaw.ai exactly
6
+ * (ParallelClaw mint palette, Inter font, glass cards, mascot SVG).
7
+ *
8
+ * Public API:
9
+ * • esc(str) — HTML-escape a string. Use everywhere user data
10
+ * is interpolated (chat content, titles, paths).
11
+ * • html(strings, ...) — tagged template. Auto-escapes any
12
+ * ${interpolation} that comes from user input.
13
+ * Wrap pre-trusted HTML in `raw()` to bypass.
14
+ * • raw(s) — opt-out of escaping (use for icons, server-built
15
+ * subtrees, etc).
16
+ * • renderPage(opts) — wraps body in <!DOCTYPE html> + head + nav.
17
+ * opts: {title, active, body, status}
18
+ */
19
+
20
+ const HTML_ENTITIES = {
21
+ '&': '&amp;',
22
+ '<': '&lt;',
23
+ '>': '&gt;',
24
+ '"': '&quot;',
25
+ "'": '&#39;',
26
+ };
27
+
28
+ export function esc(s) {
29
+ if (s == null) return '';
30
+ return String(s).replace(/[&<>"']/g, (c) => HTML_ENTITIES[c]);
31
+ }
32
+
33
+ const RAW = Symbol('raw');
34
+ export function raw(s) { return { [RAW]: true, value: String(s ?? '') }; }
35
+
36
+ export function html(strings, ...values) {
37
+ let out = '';
38
+ for (let i = 0; i < strings.length; i++) {
39
+ out += strings[i];
40
+ if (i < values.length) {
41
+ const v = values[i];
42
+ if (v == null) continue;
43
+ if (Array.isArray(v)) {
44
+ // Arrays of html(...) results — flatten
45
+ for (const item of v) {
46
+ if (item && typeof item === 'object' && item[RAW]) out += item.value;
47
+ else out += esc(item);
48
+ }
49
+ } else if (typeof v === 'object' && v[RAW]) {
50
+ out += v.value;
51
+ } else {
52
+ out += esc(v);
53
+ }
54
+ }
55
+ }
56
+ return raw(out);
57
+ }
58
+
59
+ // ----- Mascot SVG (same as landing nav) -----
60
+ const MASCOT_SVG = `
61
+ <svg class="brand-svg" viewBox="0 0 180 120" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
62
+ <defs>
63
+ <linearGradient id="memex-claw-grad" x1="0%" y1="0%" x2="100%" y2="100%">
64
+ <stop offset="0%" stop-color="#6ee7b7"/>
65
+ <stop offset="100%" stop-color="#60a5fa"/>
66
+ </linearGradient>
67
+ <linearGradient id="memex-bolt-grad" x1="0%" y1="0%" x2="100%" y2="100%">
68
+ <stop offset="0%" stop-color="#fef08a"/>
69
+ <stop offset="50%" stop-color="#fbbf24"/>
70
+ <stop offset="100%" stop-color="#f59e0b"/>
71
+ </linearGradient>
72
+ <filter id="memex-bolt-glow" x="-30%" y="-30%" width="160%" height="160%">
73
+ <feGaussianBlur stdDeviation="3" result="blur"/>
74
+ <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
75
+ </filter>
76
+ </defs>
77
+ <g transform="translate(10,5)">
78
+ <path d="M60 10 C30 10 15 35 15 55 C15 75 30 95 45 100 L45 110 L55 110 L55 100 C55 100 60 102 65 100 L65 110 L75 110 L75 100 C90 95 105 75 105 55 C105 35 90 10 60 10Z" fill="url(#memex-claw-grad)"/>
79
+ <path d="M20 45 C5 40 0 50 5 60 C10 70 20 65 25 55 C28 48 25 45 20 45Z" fill="url(#memex-claw-grad)"/>
80
+ <path d="M100 45 C115 40 120 50 115 60 C110 70 100 65 95 55 C92 48 95 45 100 45Z" fill="url(#memex-claw-grad)"/>
81
+ <path d="M45 15 Q35 5 30 8" stroke="url(#memex-claw-grad)" stroke-width="2" stroke-linecap="round" fill="none"/>
82
+ <path d="M75 15 Q85 5 90 8" stroke="url(#memex-claw-grad)" stroke-width="2" stroke-linecap="round" fill="none"/>
83
+ <circle cx="45" cy="35" r="6" fill="#0d1016"/>
84
+ <circle cx="75" cy="35" r="6" fill="#0d1016"/>
85
+ <circle cx="46" cy="34" r="2" fill="#6ee7b7"/>
86
+ <circle cx="76" cy="34" r="2" fill="#6ee7b7"/>
87
+ </g>
88
+ <path d="M130 5 L108 55 L128 55 L100 115 L152 55 L130 55 L158 5 L130 5Z" fill="url(#memex-bolt-grad)" filter="url(#memex-bolt-glow)" transform="rotate(-8 130 60)"/>
89
+ </svg>`;
90
+
91
+ // ----- Top nav with active-page hint -----
92
+ function renderNav(active) {
93
+ const items = [
94
+ { id: 'dashboard', label: 'Dashboard', href: '/' },
95
+ { id: 'conversations', label: 'Conversations', href: '/conversations' },
96
+ { id: 'pending', label: 'Pending', href: '/pending' },
97
+ { id: 'settings', label: 'Settings', href: '/settings' },
98
+ ];
99
+ const links = items.map((i) => {
100
+ const cls = active === i.id ? 'nav-link active' : 'nav-link';
101
+ return `<a href="${i.href}" class="${cls}">${esc(i.label)}</a>`;
102
+ }).join('\n');
103
+
104
+ return `
105
+ <nav class="topbar">
106
+ <div class="topbar-inner">
107
+ <a href="/" class="brand">
108
+ ${MASCOT_SVG}
109
+ <span class="brand-wordmark">me<span class="accent">mex</span></span>
110
+ </a>
111
+ <div class="nav-links">
112
+ ${links}
113
+ </div>
114
+ </div>
115
+ </nav>`;
116
+ }
117
+
118
+ // ----- Sync status pill (for header) -----
119
+ function renderStatusPill(status) {
120
+ if (!status) return '';
121
+ const { running, lastCaptureMs } = status;
122
+ let icon, label, cls;
123
+ if (running) {
124
+ icon = '🟢';
125
+ cls = 'status-pill ok';
126
+ const ago = formatAgo(lastCaptureMs);
127
+ label = `daemon · ${ago}`;
128
+ } else {
129
+ icon = '🔴';
130
+ cls = 'status-pill stale';
131
+ label = 'daemon stopped';
132
+ }
133
+ return `<span class="${cls}">${icon} ${esc(label)}</span>`;
134
+ }
135
+
136
+ function formatAgo(ms) {
137
+ if (!ms || ms < 0) return 'unknown';
138
+ const sec = Math.floor(ms / 1000);
139
+ if (sec < 60) return 'just now';
140
+ const min = Math.floor(sec / 60);
141
+ if (min < 60) return `${min}m ago`;
142
+ const h = Math.floor(min / 60);
143
+ if (h < 24) return `${h}h ago`;
144
+ const d = Math.floor(h / 24);
145
+ return `${d}d ago`;
146
+ }
147
+
148
+ // ----- Full page wrapper -----
149
+ export function renderPage(opts) {
150
+ const { title = 'memex', active = '', body, status } = opts;
151
+ const bodyHtml = body && typeof body === 'object' && body[RAW] ? body.value : esc(body || '');
152
+ const statusHtml = renderStatusPill(status);
153
+
154
+ return `<!DOCTYPE html>
155
+ <html lang="en">
156
+ <head>
157
+ <meta charset="UTF-8">
158
+ <meta name="viewport" content="width=device-width, initial-scale=1">
159
+ <title>${esc(title)} · memex</title>
160
+ <link rel="preconnect" href="https://fonts.googleapis.com">
161
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
162
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
163
+ <link rel="stylesheet" href="/static/style.css">
164
+ <script src="https://unpkg.com/htmx.org@2.0.3" defer></script>
165
+ </head>
166
+ <body>
167
+ ${renderNav(active)}
168
+ <header class="page-header">
169
+ <div class="page-header-inner">
170
+ <h1 class="page-title">${esc(title)}</h1>
171
+ ${statusHtml}
172
+ </div>
173
+ </header>
174
+ <main class="main">
175
+ ${bodyHtml}
176
+ </main>
177
+ <footer class="footer">
178
+ <span>memex · local-first AI memory</span>
179
+ <span>· <a href="https://memex.parallelclaw.ai" target="_blank" rel="noopener">site</a></span>
180
+ <span>· <a href="https://github.com/parallelclaw/memex-mvp" target="_blank" rel="noopener">github</a></span>
181
+ </footer>
182
+ </body>
183
+ </html>`;
184
+ }
185
+
186
+ // ----- Small reusable bits -----
187
+
188
+ export function renderCard({ label, body, className = '' }) {
189
+ return html`
190
+ <section class="card ${raw(className)}">
191
+ ${label ? html`<div class="card-label">${label}</div>` : null}
192
+ <div class="card-body">${body}</div>
193
+ </section>`;
194
+ }
195
+
196
+ export function renderStat({ value, label }) {
197
+ return html`
198
+ <div class="stat">
199
+ <div class="stat-value">${value}</div>
200
+ <div class="stat-label">${label}</div>
201
+ </div>`;
202
+ }
203
+
204
+ export function renderBubble({ who, when, text, side = 'left' }) {
205
+ const cls = side === 'right' ? 'chat-bubble user' : 'chat-bubble ai';
206
+ return html`
207
+ <div class="${raw(cls)}">
208
+ <span class="chat-who">${who}${when ? raw(' · ' + esc(when)) : null}</span>
209
+ <p>${text}</p>
210
+ </div>`;
211
+ }
212
+
213
+ export function fmtDate(ts) {
214
+ if (!ts || ts === 0) return '?';
215
+ return new Date(ts * 1000).toISOString().slice(0, 10);
216
+ }
217
+
218
+ export function fmtDateTime(ts) {
219
+ if (!ts || ts === 0) return '?';
220
+ return new Date(ts * 1000).toISOString().slice(0, 16).replace('T', ' ');
221
+ }
222
+
223
+ export function fmtNum(n) {
224
+ if (n == null) return '?';
225
+ return Number(n).toLocaleString('en-US');
226
+ }
227
+
228
+ export function fmtBytes(n) {
229
+ if (n == null || n < 0) return '?';
230
+ if (n < 1024) return `${n} B`;
231
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
232
+ if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
233
+ return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
234
+ }
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "parallelclaw",
3
+ "version": "1.0.0",
4
+ "description": "Local-first personal AI ops layer. Shared verbatim memory across your agents (Claude Code, Cursor, OpenClaw, Hermes, Obsidian, Telegram) in one SQLite + FTS5 corpus — plus a coordination layer where any of your agents can delegate tasks to any other. Searchable from any MCP-compatible client.",
5
+ "type": "module",
6
+ "main": "server.js",
7
+ "bin": {
8
+ "parallelclaw": "server.js",
9
+ "pclaw": "server.js",
10
+ "memex": "server.js",
11
+ "memex-mvp": "ingest.js",
12
+ "memex-sync": "ingest.js",
13
+ "memex-ingest": "ingest.js",
14
+ "memex-bot": "bot/index.js"
15
+ },
16
+ "files": [
17
+ "server.js",
18
+ "ingest.js",
19
+ "lib/",
20
+ "bot/",
21
+ "skills/",
22
+ "HELP.md",
23
+ "README.md",
24
+ "SYNC.md",
25
+ "MULTI_MACHINE.md",
26
+ "CHANGELOG.md",
27
+ "LICENSE"
28
+ ],
29
+ "scripts": {
30
+ "start": "node server.js",
31
+ "sync": "node ingest.js",
32
+ "ingest": "node ingest.js",
33
+ "bot": "node bot/index.js",
34
+ "test": "node test/parser.test.js && node test/bot-inbox.test.js && node test/search-sort.test.js && node test/get-conversation-paging.test.js && node test/search-filters.test.js && node test/origin.test.js && node test/tasks.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 && node test/ingest-file.test.js && node test/openclaw-channel.test.js && node test/openclaw-channel-e2e.test.js && node test/wire-openclaw.test.js && node test/sync/server-bootstrap.test.js && node test/sync/push-pull-roundtrip.test.js && node test/sync/cli-end-to-end.test.js && node test/sync/adaptive-batch.test.js && node test/sync/service.test.js && node test/sync/skip-accounting.test.js && node test/sync/pair.test.js && node test/sync/join-token.test.js && node test/sync/tunnel-service.test.js && node test/sync/mcp-invite.test.js && node test/sync/skip-accounting.test.js",
35
+ "prepublishOnly": "npm test"
36
+ },
37
+ "engines": {
38
+ "node": ">=20.0.0 <25.0.0"
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.0.0",
42
+ "better-sqlite3": "^11.0.0",
43
+ "chokidar": "^3.6.0",
44
+ "selfsigned": "^5.5.0"
45
+ },
46
+ "keywords": [
47
+ "mcp",
48
+ "mcp-server",
49
+ "claude",
50
+ "claude-code",
51
+ "claude-cowork",
52
+ "cursor",
53
+ "parallelclaw",
54
+ "memex",
55
+ "memory",
56
+ "ai-memory",
57
+ "agent-coordination",
58
+ "agent-orchestration",
59
+ "personal-ai-ops",
60
+ "agent-delegation",
61
+ "local-first",
62
+ "verbatim",
63
+ "chat-archive",
64
+ "fts5",
65
+ "sqlite",
66
+ "telegram",
67
+ "openclaw",
68
+ "hermes"
69
+ ],
70
+ "author": {
71
+ "name": "parallelclaw",
72
+ "email": "sedelev@gmail.com",
73
+ "url": "https://memex.parallelclaw.ai"
74
+ },
75
+ "license": "MIT",
76
+ "homepage": "https://memex.parallelclaw.ai",
77
+ "repository": {
78
+ "type": "git",
79
+ "url": "git+https://github.com/parallelclaw/memex-mvp.git"
80
+ },
81
+ "bugs": {
82
+ "url": "https://github.com/parallelclaw/memex-mvp/issues"
83
+ }
84
+ }