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,224 @@
1
+ /**
2
+ * HTTPS server for memex sync (v0.11.11 experimental).
3
+ *
4
+ * Spawned in-process by `memex sync server enable` (long-lived) or by
5
+ * integration tests (one-shot, bound to ephemeral port). NOT part of the
6
+ * MCP server — sync runs on its own port so MCP stdio clients aren't
7
+ * affected and the sync surface can be deployed independently.
8
+ *
9
+ * Routes implemented in Day 2 (Day 3+ adds /sync/push, /sync/pull):
10
+ * GET /sync/health — version + schema_version + row counts (auth required)
11
+ * * / — 404
12
+ *
13
+ * Why no Express/Fastify: zero new deps; Node's built-in https is sufficient
14
+ * for our 3-endpoint surface. We add a tiny route table inline.
15
+ *
16
+ * Cert handling: ensureCert() is idempotent — first launch generates a
17
+ * self-signed cert; subsequent launches reuse it so paired clients don't
18
+ * silently break. Use `memex sync rotate-cert` for an explicit rotation.
19
+ */
20
+
21
+ import { createServer } from 'node:https';
22
+ import { existsSync, readFileSync } from 'node:fs';
23
+ import { homedir } from 'node:os';
24
+ import { join } from 'node:path';
25
+ import Database from 'better-sqlite3';
26
+
27
+ import { ensureCert } from './cert.js';
28
+ import { requireBearer, generateBearerToken } from './auth.js';
29
+ import { updateSyncServer, loadSyncConfig } from './config.js';
30
+ import { makePushHandler } from './push.js';
31
+ import { makePullHandler } from './pull.js';
32
+
33
+ const HOME = homedir();
34
+ const MEMEX_DIR = process.env.MEMEX_DIR || join(HOME, '.memex');
35
+ const DEFAULT_DB_PATH = join(MEMEX_DIR, 'data', 'memex.db');
36
+ const DEFAULT_CERT_PATH = join(MEMEX_DIR, 'sync-cert.pem');
37
+ const DEFAULT_KEY_PATH = join(MEMEX_DIR, 'sync-key.pem');
38
+
39
+ /** Wire-protocol schema version. Must match SYNC.md. */
40
+ export const SYNC_SCHEMA_VERSION = 12;
41
+
42
+ /** Default port, overridable via opts.port or sync.server.port config. */
43
+ export const DEFAULT_SYNC_PORT = 8765;
44
+
45
+ /**
46
+ * Start an HTTPS sync server. Returns a Promise<{server, port, fingerprint, bearer}>.
47
+ *
48
+ * opts:
49
+ * port — int; defaults to config.sync.server.port or DEFAULT_SYNC_PORT
50
+ * bind — string; defaults to config.sync.server.bind or '0.0.0.0'
51
+ * dbPath — string; defaults to ~/.memex/data/memex.db
52
+ * certPath — string; defaults to ~/.memex/sync-cert.pem
53
+ * keyPath — string; defaults to ~/.memex/sync-key.pem
54
+ * bearer — string; if omitted, reuses config.sync.server.bearer
55
+ * or generates a new one (persisted)
56
+ * ephemeral — bool; if true, port=0 (OS-assigned), nothing persisted
57
+ * to config. Used by tests.
58
+ * onListen — optional callback fired once listening, args: {port, fingerprint}
59
+ *
60
+ * Idempotency: calling twice with the same opts is fine — cert is reused
61
+ * (ensureCert), bearer reused (if already in config), server just rebinds.
62
+ *
63
+ * Shutdown: call .close() on the returned server.
64
+ */
65
+ export async function startSyncServer(opts = {}) {
66
+ const cfg = loadSyncConfig();
67
+ const port = opts.port ?? (opts.ephemeral ? 0 : (cfg.server.port || DEFAULT_SYNC_PORT));
68
+ const bind = opts.bind ?? (cfg.server.bind || '0.0.0.0');
69
+ const dbPath = opts.dbPath ?? DEFAULT_DB_PATH;
70
+ const certPath = opts.certPath ?? (cfg.server.cert_path || DEFAULT_CERT_PATH);
71
+ const keyPath = opts.keyPath ?? (cfg.server.key_path || DEFAULT_KEY_PATH);
72
+
73
+ // 1. Cert — generate if missing, otherwise reuse.
74
+ const certInfo = await ensureCert({ certPath, keyPath });
75
+
76
+ // 2. Bearer — caller can override; otherwise reuse persisted or mint new.
77
+ let bearer = opts.bearer ?? cfg.server.bearer;
78
+ let bearerMinted = false;
79
+ if (!bearer) {
80
+ bearer = generateBearerToken();
81
+ bearerMinted = true;
82
+ }
83
+
84
+ // 3. DB handle — read-write since Day 3 (push writes). WAL mode (set by
85
+ // the install path) lets the daemon's writer and this server coexist
86
+ // without locking each other.
87
+ if (!existsSync(dbPath)) {
88
+ throw new Error(`Sync server: DB not found at ${dbPath}. Run \`memex-sync install\` first.`);
89
+ }
90
+ // Run schema migrations BEFORE preparing any statements. The pull handler
91
+ // prepares a SELECT over newer columns (channel, origin) at server start —
92
+ // on a node where only the npm package was upgraded (no daemon/MCP boot
93
+ // had migrated yet), that prepare would throw against the old schema and
94
+ // the service would crash-loop. Found live during the v0.14.0 rollout.
95
+ const { initializeDb } = await import('../db-init.js');
96
+ initializeDb(dbPath).close();
97
+ const db = new Database(dbPath);
98
+ db.pragma('journal_mode = WAL');
99
+ db.pragma('synchronous = NORMAL');
100
+
101
+ // 4. Persist config (unless ephemeral test mode).
102
+ if (!opts.ephemeral) {
103
+ updateSyncServer({
104
+ enabled: true,
105
+ port,
106
+ bind,
107
+ bearer, // store back what we have, including freshly minted
108
+ cert_path: certPath,
109
+ key_path: keyPath,
110
+ cert_fp: certInfo.fingerprint,
111
+ });
112
+ }
113
+
114
+ // 5. Construct HTTPS server. Cert + key passed as PEM strings via TLS opts.
115
+ const tlsOpts = {
116
+ cert: readFileSync(certPath),
117
+ key: readFileSync(keyPath),
118
+ };
119
+
120
+ // Build handlers once at server-start — they each pre-compile their
121
+ // prepared statements and reuse them per request.
122
+ const pushHandler = makePushHandler({ db });
123
+ const pullHandler = makePullHandler({ db });
124
+
125
+ const server = createServer(tlsOpts, (req, res) => {
126
+ handleRequest(req, res, { db, bearer, pushHandler, pullHandler });
127
+ });
128
+
129
+ // 6. Bind + return when listening.
130
+ return new Promise((resolve, reject) => {
131
+ server.once('error', reject);
132
+ server.listen(port, bind, () => {
133
+ const actualPort = server.address().port;
134
+ const result = {
135
+ server,
136
+ port: actualPort,
137
+ fingerprint: certInfo.fingerprint,
138
+ bearer,
139
+ bearerMinted,
140
+ };
141
+ if (typeof opts.onListen === 'function') {
142
+ try { opts.onListen({ port: actualPort, fingerprint: certInfo.fingerprint }); }
143
+ catch (_) { /* user callback errors don't break the server */ }
144
+ }
145
+ // Patch close to also close the DB handle.
146
+ const originalClose = server.close.bind(server);
147
+ server.close = (cb) => {
148
+ try { db.close(); } catch (_) {}
149
+ originalClose(cb);
150
+ };
151
+ resolve(result);
152
+ });
153
+ });
154
+ }
155
+
156
+ /**
157
+ * Top-level request dispatcher. Routes by method + path. Day 2 surface:
158
+ * GET /sync/health — auth required, version + schema + row counts
159
+ * anything else — 404
160
+ *
161
+ * Day 3+ adds /sync/push and /sync/pull here.
162
+ */
163
+ function handleRequest(req, res, ctx) {
164
+ // Default headers — JSON everywhere
165
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
166
+
167
+ // Crude routing — fine for a 3-endpoint surface. If we ever hit 6+
168
+ // routes we'll switch to a small router.
169
+ const url = new URL(req.url, 'https://placeholder.local');
170
+ const path = url.pathname;
171
+
172
+ if (req.method === 'GET' && path === '/sync/health') {
173
+ if (!requireBearer(req, res, ctx.bearer)) return;
174
+ return handleHealth(req, res, ctx);
175
+ }
176
+ if (req.method === 'POST' && path === '/sync/push') {
177
+ if (!requireBearer(req, res, ctx.bearer)) return;
178
+ return ctx.pushHandler(req, res);
179
+ }
180
+ if (req.method === 'GET' && path === '/sync/pull') {
181
+ if (!requireBearer(req, res, ctx.bearer)) return;
182
+ return ctx.pullHandler(req, res);
183
+ }
184
+
185
+ res.statusCode = 404;
186
+ res.end(JSON.stringify({ error: 'not_found' }));
187
+ }
188
+
189
+ function handleHealth(req, res, ctx) {
190
+ try {
191
+ // Cheap counts — both queried via the read-only handle, no FTS5 hit.
192
+ const rowCount = ctx.db.prepare('SELECT COUNT(*) AS n FROM messages').get().n;
193
+ const lastIdRow = ctx.db.prepare('SELECT MAX(id) AS last_id FROM messages').get();
194
+ const lastId = lastIdRow?.last_id ?? 0;
195
+
196
+ res.statusCode = 200;
197
+ res.end(JSON.stringify({
198
+ version: getMemexVersion(),
199
+ schema_version: SYNC_SCHEMA_VERSION,
200
+ row_count: rowCount,
201
+ last_id: lastId,
202
+ }));
203
+ } catch (err) {
204
+ res.statusCode = 500;
205
+ res.end(JSON.stringify({ error: 'internal', detail: String(err.message || err) }));
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Read the running memex-mvp version from its package.json. Best-effort —
211
+ * if we can't resolve it (e.g. weird install layout), report "unknown".
212
+ */
213
+ let _cachedVersion = null;
214
+ function getMemexVersion() {
215
+ if (_cachedVersion) return _cachedVersion;
216
+ try {
217
+ const url = new URL('../../package.json', import.meta.url);
218
+ const pkg = JSON.parse(readFileSync(url, 'utf-8'));
219
+ _cachedVersion = pkg.version || 'unknown';
220
+ } catch (_) {
221
+ _cachedVersion = 'unknown';
222
+ }
223
+ return _cachedVersion;
224
+ }