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.
- package/CHANGELOG.md +204 -0
- package/HELP.md +600 -0
- package/LICENSE +21 -0
- package/MULTI_MACHINE.md +152 -0
- package/README.md +417 -0
- package/README.ru.md +740 -0
- package/SYNC.md +844 -0
- package/bot/README.md +173 -0
- package/bot/config.js +66 -0
- package/bot/inbox.js +153 -0
- package/bot/index.js +294 -0
- package/bot/nexara.js +61 -0
- package/bot/poll.js +304 -0
- package/bot/search.js +155 -0
- package/bot/telegram.js +96 -0
- package/ingest.js +2712 -0
- package/lib/cli/index.js +1987 -0
- package/lib/config.js +220 -0
- package/lib/db-init.js +158 -0
- package/lib/hook/install.js +268 -0
- package/lib/import-telegram.js +158 -0
- package/lib/ingest-file.js +779 -0
- package/lib/notify-click-action.js +281 -0
- package/lib/openclaw-channel.js +643 -0
- package/lib/parse-cursor.js +172 -0
- package/lib/parse-obsidian.js +256 -0
- package/lib/parse-telegram-html.js +384 -0
- package/lib/parse.js +175 -0
- package/lib/render-markdown.js +0 -0
- package/lib/store-doc/canonicalize.js +116 -0
- package/lib/store-doc/detect.js +209 -0
- package/lib/store-doc/extract-title.js +162 -0
- package/lib/sync/auth.js +80 -0
- package/lib/sync/cert.js +144 -0
- package/lib/sync/cli.js +906 -0
- package/lib/sync/client.js +138 -0
- package/lib/sync/config.js +130 -0
- package/lib/sync/pair.js +145 -0
- package/lib/sync/pull.js +158 -0
- package/lib/sync/push.js +305 -0
- package/lib/sync/replicate.js +335 -0
- package/lib/sync/server.js +224 -0
- package/lib/sync/service.js +726 -0
- package/lib/tasks.js +215 -0
- package/lib/telegram-decisions.js +165 -0
- package/lib/telegram-discovery.js +373 -0
- package/lib/telegram-notify.js +272 -0
- package/lib/telegram-pending.js +200 -0
- package/lib/web/index.js +265 -0
- package/lib/web/routes/conversation.js +193 -0
- package/lib/web/routes/conversations.js +180 -0
- package/lib/web/routes/dashboard.js +175 -0
- package/lib/web/routes/pending.js +277 -0
- package/lib/web/routes/settings.js +226 -0
- package/lib/web/static/style.css +393 -0
- package/lib/web/templates.js +234 -0
- package/package.json +84 -0
- package/server.js +3816 -0
- package/skills/install-memex/README.md +109 -0
- package/skills/install-memex/SKILL.md +342 -0
- package/skills/install-memex/examples.md +294 -0
- 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
|
+
}
|