wyrm-mcp 7.2.0 → 7.2.2
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/LICENSE +26 -667
- package/NOTICE +14 -33
- package/dist/activation.d.ts.map +1 -1
- package/dist/activation.js +1 -44
- package/dist/activation.js.map +1 -1
- package/dist/agent-daemon.js +4 -281
- package/dist/agent-loop.js +7 -332
- package/dist/analytics.js +13 -236
- package/dist/attribution.js +1 -49
- package/dist/audit.js +2 -457
- package/dist/auto-capture.js +3 -138
- package/dist/auto-orchestrator.js +1 -325
- package/dist/autoconfig.js +39 -840
- package/dist/buddy-runner.js +1 -109
- package/dist/buddy.js +14 -564
- package/dist/build-flags.js +1 -17
- package/dist/capabilities.js +3 -183
- package/dist/capture.js +1 -56
- package/dist/causality.js +6 -107
- package/dist/cli.js +20 -281
- package/dist/cloud/cli.js +5 -541
- package/dist/cloud/client.js +1 -221
- package/dist/cloud/crypto.js +1 -85
- package/dist/cloud/machine-id.js +2 -113
- package/dist/cloud/recovery.js +1 -60
- package/dist/cloud/sync-engine.js +7 -543
- package/dist/cloud-backup.js +5 -579
- package/dist/cloud-profile.js +1 -138
- package/dist/cloud-sync-entrypoint.js +1 -47
- package/dist/cloud-sync.js +2 -309
- package/dist/constellation.js +12 -168
- package/dist/context-build-budgeted.js +4 -144
- package/dist/context-ranking.js +1 -69
- package/dist/crypto.js +1 -179
- package/dist/daemon-write-endpoint.js +1 -290
- package/dist/daemon-writer.js +2 -406
- package/dist/database.js +43 -1110
- package/dist/deprecations.js +2 -162
- package/dist/design.js +13 -141
- package/dist/event-replication.js +1 -112
- package/dist/events-sse.js +7 -43
- package/dist/events.js +6 -238
- package/dist/failure-patterns.js +42 -659
- package/dist/federation.js +12 -236
- package/dist/goals.js +13 -101
- package/dist/golden.js +3 -355
- package/dist/handlers/agent.js +4 -165
- package/dist/handlers/alias-adapters.js +1 -129
- package/dist/handlers/aliases.js +1 -171
- package/dist/handlers/audit.js +1 -87
- package/dist/handlers/boundary.js +1 -221
- package/dist/handlers/capture.js +73 -1109
- package/dist/handlers/causality.js +7 -114
- package/dist/handlers/cloud.js +85 -382
- package/dist/handlers/companion.js +28 -459
- package/dist/handlers/datalake.js +7 -187
- package/dist/handlers/dispatch-context.js +0 -22
- package/dist/handlers/entity.js +25 -256
- package/dist/handlers/events.js +16 -335
- package/dist/handlers/failure.js +13 -340
- package/dist/handlers/goals.js +4 -296
- package/dist/handlers/intelligence.js +126 -674
- package/dist/handlers/invoicing.js +1 -70
- package/dist/handlers/mcpclient.js +6 -137
- package/dist/handlers/orchestration.js +40 -125
- package/dist/handlers/output-schemas.js +1 -24
- package/dist/handlers/presence.js +3 -99
- package/dist/handlers/project.js +28 -182
- package/dist/handlers/prompts.js +6 -157
- package/dist/handlers/quest.js +4 -224
- package/dist/handlers/recall.js +11 -218
- package/dist/handlers/registry.js +1 -167
- package/dist/handlers/resources.js +1 -288
- package/dist/handlers/review.js +11 -74
- package/dist/handlers/run.js +17 -487
- package/dist/handlers/search.js +15 -326
- package/dist/handlers/session.js +28 -615
- package/dist/handlers/share.js +8 -184
- package/dist/handlers/shims.js +1 -464
- package/dist/handlers/skill.js +67 -449
- package/dist/handlers/survivors.js +1 -120
- package/dist/handlers/symbols.js +8 -109
- package/dist/handlers/syncops.js +4 -302
- package/dist/handlers/types.js +1 -27
- package/dist/harvest.js +5 -191
- package/dist/hours.js +7 -156
- package/dist/http-auth.js +3 -321
- package/dist/http-fast.js +21 -1137
- package/dist/icons.js +1 -47
- package/dist/index.js +2 -924
- package/dist/indexer.js +4 -145
- package/dist/intelligence.js +31 -261
- package/dist/internal-dispatch.js +3 -212
- package/dist/keyset.js +1 -110
- package/dist/knowledge-graph.js +12 -176
- package/dist/license.d.ts +11 -0
- package/dist/license.d.ts.map +1 -1
- package/dist/license.js +2 -414
- package/dist/license.js.map +1 -1
- package/dist/logger.js +2 -199
- package/dist/maintenance.js +2 -148
- package/dist/mcp-client.js +6 -262
- package/dist/memory-artifacts.js +30 -449
- package/dist/migrate-prompt.js +2 -124
- package/dist/migrations.js +40 -655
- package/dist/performance.js +1 -228
- package/dist/presence.js +11 -140
- package/dist/priority-embed.js +5 -164
- package/dist/providers/embedding-provider.js +1 -196
- package/dist/readonly-gate.js +1 -29
- package/dist/rehydration.js +9 -157
- package/dist/reindex.js +1 -88
- package/dist/render-target.js +21 -514
- package/dist/render.js +4 -280
- package/dist/repl-guard.js +1 -173
- package/dist/replication-daemon-entrypoint.js +1 -31
- package/dist/replication-daemon.js +2 -262
- package/dist/resilience.js +1 -591
- package/dist/reverse-bridge.js +5 -360
- package/dist/security.js +1 -244
- package/dist/session-seen.js +3 -51
- package/dist/setup.js +1 -260
- package/dist/skill-author.js +5 -168
- package/dist/spec-kit.js +1 -191
- package/dist/sqlite-busy.js +1 -154
- package/dist/statusline.js +11 -315
- package/dist/sub-agent.js +13 -262
- package/dist/summarizer.js +13 -139
- package/dist/symbols.js +7 -283
- package/dist/sync.js +5 -359
- package/dist/tasks-dispatch.js +1 -84
- package/dist/tasks.js +1 -282
- package/dist/token-budget.js +1 -143
- package/dist/tool-analytics.js +7 -129
- package/dist/tool-annotations.js +1 -365
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/tool-profiles.js +1 -75
- package/dist/trace-harvest.js +6 -244
- package/dist/types.js +1 -30
- package/dist/ui-dashboard.js +41 -50
- package/dist/ulid.js +1 -81
- package/dist/validate.js +1 -129
- package/dist/vault.js +1 -534
- package/dist/vectors.js +3 -184
- package/dist/version-check.js +4 -136
- package/dist/visibility.js +19 -155
- package/dist/wyrm-cli.js +98 -2451
- package/dist/wyrm-cli.js.map +1 -1
- package/dist/wyrm-guard.js +14 -424
- package/dist/wyrm-loop.js +3 -150
- package/dist/wyrm-manifest.json +1 -1
- package/dist/wyrm-statusline-daemon.js +1 -11
- package/dist/wyrm-statusline.js +4 -56
- package/dist/wyrm-ui.js +9 -77
- package/package.json +4 -2
|
@@ -1,262 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Wraps the Phase-3 `replicateOnce` primitive in a background loop that syncs a
|
|
5
|
-
* registry of peers on an interval. Mirrors the `CloudSyncDaemon` /
|
|
6
|
-
* `CloudSyncManager` shape (detached child + PID file + liveness + rotating log).
|
|
7
|
-
*
|
|
8
|
-
* Secret hygiene: a peer's bearer token is NEVER stored in the DB. The registry
|
|
9
|
-
* holds the NAME of an env var (`tokenEnv`); the token is resolved from
|
|
10
|
-
* `process.env` at tick time, so the SQLite file never contains a credential.
|
|
11
|
-
*
|
|
12
|
-
* The daemon shares the MCP server's DB (`~/.wyrm/wyrm.db` by default, or
|
|
13
|
-
* `WYRM_DB_PATH`) — better-sqlite3 WAL supports the concurrent connections.
|
|
14
|
-
*
|
|
15
|
-
* @copyright 2026 Ghost Protocol (Pvt) Ltd.
|
|
16
|
-
* @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
|
|
17
|
-
*/
|
|
18
|
-
import { spawn } from 'child_process';
|
|
19
|
-
import { existsSync, readFileSync, writeFileSync, statSync, mkdirSync, openSync, closeSync, unlinkSync, renameSync, } from 'fs';
|
|
20
|
-
import { homedir } from 'os';
|
|
21
|
-
import { join } from 'path';
|
|
22
|
-
import { replicateOnce } from './event-replication.js';
|
|
23
|
-
import { checkPeerUrl, isValidTokenEnvName } from './repl-guard.js';
|
|
24
|
-
const WYRM_DIR = join(homedir(), '.wyrm');
|
|
25
|
-
const PID_FILE = join(WYRM_DIR, 'wyrm-replication.pid');
|
|
26
|
-
const LOG_FILE = join(WYRM_DIR, 'wyrm-replication.log');
|
|
27
|
-
const DEFAULT_INTERVAL_MS = 60_000; // 1 min — events are small + incremental
|
|
28
|
-
const MIN_INTERVAL_MS = 5_000; // floor: don't hammer a peer
|
|
29
|
-
const LOG_ROTATE_BYTES = 1_000_000; // 1 MB
|
|
30
|
-
const LIVENESS_SIGNAL = 0;
|
|
31
|
-
const PEERS_META_KEY = 'repl:peers';
|
|
32
|
-
const MAX_PEERS = 50;
|
|
33
|
-
/** Validate a peer entry at registration time (scheme/URL + tokenEnv name). */
|
|
34
|
-
export function validatePeerEntry(entry) {
|
|
35
|
-
if (!entry || typeof entry.baseUrl !== 'string')
|
|
36
|
-
return { ok: false, reason: 'baseUrl required' };
|
|
37
|
-
const url = checkPeerUrl(cleanBase(entry.baseUrl));
|
|
38
|
-
// Registration is lenient on private hosts (the egress guard re-checks at fetch
|
|
39
|
-
// time); only reject an unparseable URL or a bad scheme here.
|
|
40
|
-
if (!url.ok && !/loopback|private|link-local/.test(url.reason))
|
|
41
|
-
return { ok: false, reason: url.reason };
|
|
42
|
-
if (!entry.projectPath || typeof entry.projectPath !== 'string')
|
|
43
|
-
return { ok: false, reason: 'projectPath required' };
|
|
44
|
-
if (entry.tokenEnv && !isValidTokenEnvName(entry.tokenEnv)) {
|
|
45
|
-
return { ok: false, reason: `tokenEnv must be a WYRM_-prefixed env var name (got ${entry.tokenEnv})` };
|
|
46
|
-
}
|
|
47
|
-
return { ok: true };
|
|
48
|
-
}
|
|
49
|
-
// ==================== REGISTRY ====================
|
|
50
|
-
const cleanBase = (u) => String(u).replace(/\/+$/, '');
|
|
51
|
-
/** Read the persisted peer registry (tolerant of corruption — returns []). */
|
|
52
|
-
export function listPeers(db) {
|
|
53
|
-
try {
|
|
54
|
-
const raw = db.getMeta(PEERS_META_KEY);
|
|
55
|
-
const parsed = raw ? JSON.parse(raw) : [];
|
|
56
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
return [];
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
/** Upsert a peer (keyed on baseUrl + projectPath). Throws on an invalid entry. Returns the new registry. */
|
|
63
|
-
export function addPeer(db, entry) {
|
|
64
|
-
const v = validatePeerEntry(entry);
|
|
65
|
-
if (!v.ok)
|
|
66
|
-
throw new Error(v.reason || 'invalid peer');
|
|
67
|
-
const e = {
|
|
68
|
-
baseUrl: cleanBase(entry.baseUrl),
|
|
69
|
-
projectPath: entry.projectPath,
|
|
70
|
-
...(entry.remoteProject ? { remoteProject: entry.remoteProject } : {}),
|
|
71
|
-
...(entry.tokenEnv ? { tokenEnv: entry.tokenEnv } : {}),
|
|
72
|
-
};
|
|
73
|
-
const peers = listPeers(db).filter((p) => !(cleanBase(p.baseUrl) === e.baseUrl && p.projectPath === e.projectPath));
|
|
74
|
-
peers.push(e);
|
|
75
|
-
if (peers.length > MAX_PEERS)
|
|
76
|
-
peers.splice(0, peers.length - MAX_PEERS);
|
|
77
|
-
db.setMeta(PEERS_META_KEY, JSON.stringify(peers));
|
|
78
|
-
return peers;
|
|
79
|
-
}
|
|
80
|
-
/** Remove a peer by baseUrl + projectPath. Returns the new registry. */
|
|
81
|
-
export function removePeer(db, baseUrl, projectPath) {
|
|
82
|
-
const target = cleanBase(baseUrl);
|
|
83
|
-
const peers = listPeers(db).filter((p) => !(cleanBase(p.baseUrl) === target && p.projectPath === projectPath));
|
|
84
|
-
db.setMeta(PEERS_META_KEY, JSON.stringify(peers));
|
|
85
|
-
return peers;
|
|
86
|
-
}
|
|
87
|
-
// ==================== DAEMON ====================
|
|
88
|
-
export class ReplicationDaemon {
|
|
89
|
-
db;
|
|
90
|
-
intervalMs;
|
|
91
|
-
constructor(db, intervalMs = DEFAULT_INTERVAL_MS) {
|
|
92
|
-
this.db = db;
|
|
93
|
-
this.intervalMs = intervalMs;
|
|
94
|
-
ensureWyrmDir();
|
|
95
|
-
this.intervalMs = Math.max(MIN_INTERVAL_MS, intervalMs);
|
|
96
|
-
}
|
|
97
|
-
/** Run one replication round over all registered peers. Never throws. */
|
|
98
|
-
async tick(fetchImpl = globalThis.fetch) {
|
|
99
|
-
if (!this.db.liveMemoryEnabled())
|
|
100
|
-
return [];
|
|
101
|
-
const peers = listPeers(this.db);
|
|
102
|
-
const results = [];
|
|
103
|
-
for (const p of peers) {
|
|
104
|
-
const project = this.db.getProject(p.projectPath);
|
|
105
|
-
if (!project) {
|
|
106
|
-
results.push({ peer: p.baseUrl, errors: ['project not found: ' + p.projectPath] });
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
// Resolve the token only from a validly-named env var (never an arbitrary process secret).
|
|
110
|
-
const token = p.tokenEnv && isValidTokenEnvName(p.tokenEnv) ? process.env[p.tokenEnv] : undefined;
|
|
111
|
-
try {
|
|
112
|
-
const r = await replicateOnce(this.db, { baseUrl: cleanBase(p.baseUrl), token, localProjectId: project.id, remoteProject: p.remoteProject || project.name }, fetchImpl);
|
|
113
|
-
results.push({ peer: p.baseUrl, project: project.name, ...r });
|
|
114
|
-
}
|
|
115
|
-
catch (err) {
|
|
116
|
-
results.push({ peer: p.baseUrl, project: project.name, errors: [err instanceof Error ? err.message : String(err)] });
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return results;
|
|
120
|
-
}
|
|
121
|
-
/** Long-running loop. Spawned detached; logs to stdout (redirected to LOG_FILE). */
|
|
122
|
-
async run() {
|
|
123
|
-
log(`replication daemon started · interval=${this.intervalMs}ms · peers=${listPeers(this.db).length}`);
|
|
124
|
-
let inFlight = false;
|
|
125
|
-
const timer = setInterval(async () => {
|
|
126
|
-
if (inFlight)
|
|
127
|
-
return; // never overlap ticks
|
|
128
|
-
inFlight = true;
|
|
129
|
-
try {
|
|
130
|
-
const results = await this.tick();
|
|
131
|
-
for (const r of results) {
|
|
132
|
-
if (r.errors.length || (r.pulled || 0) + (r.pushed || 0) > 0) {
|
|
133
|
-
log(`tick: ${JSON.stringify(r)}`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
catch (e) {
|
|
138
|
-
log(`tick error: ${e instanceof Error ? e.message : String(e)}`);
|
|
139
|
-
}
|
|
140
|
-
finally {
|
|
141
|
-
inFlight = false;
|
|
142
|
-
}
|
|
143
|
-
}, this.intervalMs);
|
|
144
|
-
const shutdown = (signal) => {
|
|
145
|
-
clearInterval(timer);
|
|
146
|
-
log(`${signal} — shutting down`);
|
|
147
|
-
try {
|
|
148
|
-
this.db.close?.();
|
|
149
|
-
}
|
|
150
|
-
catch { /* best-effort WAL checkpoint + close */ }
|
|
151
|
-
process.exit(0);
|
|
152
|
-
};
|
|
153
|
-
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
154
|
-
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
// ==================== MANAGER ====================
|
|
158
|
-
export class ReplicationManager {
|
|
159
|
-
daemonEntrypoint;
|
|
160
|
-
constructor(daemonEntrypoint) {
|
|
161
|
-
this.daemonEntrypoint = daemonEntrypoint;
|
|
162
|
-
ensureWyrmDir();
|
|
163
|
-
}
|
|
164
|
-
start(opts = {}) {
|
|
165
|
-
if (this.isAlive())
|
|
166
|
-
return { ok: false, pid: this.readPid(), reason: 'already running' };
|
|
167
|
-
rotateLogIfBig();
|
|
168
|
-
const mins = Number(opts.interval_minutes);
|
|
169
|
-
const intervalMs = Number.isFinite(mins) && mins > 0 ? Math.round(mins * 60_000) : DEFAULT_INTERVAL_MS;
|
|
170
|
-
// One shared fd for both stdout+stderr; try/finally so a spawn throw can't leak it.
|
|
171
|
-
const logFd = openSync(LOG_FILE, 'a');
|
|
172
|
-
try {
|
|
173
|
-
const child = spawn(process.execPath, [this.daemonEntrypoint, String(intervalMs)], {
|
|
174
|
-
detached: true,
|
|
175
|
-
stdio: ['ignore', logFd, logFd],
|
|
176
|
-
env: { ...process.env },
|
|
177
|
-
});
|
|
178
|
-
child.unref();
|
|
179
|
-
if (typeof child.pid !== 'number')
|
|
180
|
-
return { ok: false, reason: 'spawn returned no pid' };
|
|
181
|
-
writeFileSync(PID_FILE, String(child.pid));
|
|
182
|
-
return { ok: true, pid: child.pid };
|
|
183
|
-
}
|
|
184
|
-
finally {
|
|
185
|
-
closeSync(logFd);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
stop() {
|
|
189
|
-
if (!this.isAlive()) {
|
|
190
|
-
this.cleanPid();
|
|
191
|
-
return { ok: false, reason: 'not running' };
|
|
192
|
-
}
|
|
193
|
-
const pid = this.readPid();
|
|
194
|
-
if (!pid)
|
|
195
|
-
return { ok: false, reason: 'pid file unreadable' };
|
|
196
|
-
try {
|
|
197
|
-
process.kill(pid, 'SIGTERM');
|
|
198
|
-
return { ok: true };
|
|
199
|
-
}
|
|
200
|
-
catch (e) {
|
|
201
|
-
return { ok: false, reason: e instanceof Error ? e.message : String(e) };
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
status() {
|
|
205
|
-
const running = this.isAlive();
|
|
206
|
-
return { running, pid: running ? this.readPid() ?? undefined : undefined };
|
|
207
|
-
}
|
|
208
|
-
isAlive() {
|
|
209
|
-
if (!existsSync(PID_FILE))
|
|
210
|
-
return false;
|
|
211
|
-
const pid = this.readPid();
|
|
212
|
-
if (!pid)
|
|
213
|
-
return false;
|
|
214
|
-
try {
|
|
215
|
-
process.kill(pid, LIVENESS_SIGNAL);
|
|
216
|
-
return true;
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
readPid() {
|
|
223
|
-
try {
|
|
224
|
-
const n = Number(readFileSync(PID_FILE, 'utf8').trim());
|
|
225
|
-
return Number.isFinite(n) && n > 0 ? n : undefined;
|
|
226
|
-
}
|
|
227
|
-
catch {
|
|
228
|
-
return undefined;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
cleanPid() {
|
|
232
|
-
try {
|
|
233
|
-
unlinkSync(PID_FILE);
|
|
234
|
-
}
|
|
235
|
-
catch { /* idempotent */ }
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
// ==================== HELPERS ====================
|
|
239
|
-
function ensureWyrmDir() {
|
|
240
|
-
if (!existsSync(WYRM_DIR))
|
|
241
|
-
mkdirSync(WYRM_DIR, { recursive: true });
|
|
242
|
-
}
|
|
243
|
-
function log(msg) {
|
|
244
|
-
process.stdout.write(`[${new Date().toISOString()}] ${msg}\n`);
|
|
245
|
-
}
|
|
246
|
-
function rotateLogIfBig() {
|
|
247
|
-
if (!existsSync(LOG_FILE))
|
|
248
|
-
return;
|
|
249
|
-
try {
|
|
250
|
-
if (statSync(LOG_FILE).size > LOG_ROTATE_BYTES) {
|
|
251
|
-
const rotated = `${LOG_FILE}.1`;
|
|
252
|
-
try {
|
|
253
|
-
unlinkSync(rotated);
|
|
254
|
-
}
|
|
255
|
-
catch { /* ignore */ }
|
|
256
|
-
renameSync(LOG_FILE, rotated);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
catch { /* non-fatal */ }
|
|
260
|
-
}
|
|
261
|
-
export { DEFAULT_INTERVAL_MS, MIN_INTERVAL_MS, PEERS_META_KEY };
|
|
262
|
-
//# sourceMappingURL=replication-daemon.js.map
|
|
1
|
+
import{spawn as b}from"child_process";import{existsSync as h,readFileSync as j,writeFileSync as M,statSync as I,mkdirSync as _,openSync as N,closeSync as w,unlinkSync as k,renameSync as U}from"fs";import{homedir as T}from"os";import{join as m}from"path";import{replicateOnce as A}from"./event-replication.js";import{checkPeerUrl as R,isValidTokenEnvName as g}from"./repl-guard.js";const l=m(T(),".wyrm"),p=m(l,"wyrm-replication.pid"),c=m(l,"wyrm-replication.log"),E=6e4,v=5e3,x=1e6,L=0,u="repl:peers",S=50;function $(r){if(!r||typeof r.baseUrl!="string")return{ok:!1,reason:"baseUrl required"};const e=R(i(r.baseUrl));return!e.ok&&!/loopback|private|link-local/.test(e.reason)?{ok:!1,reason:e.reason}:!r.projectPath||typeof r.projectPath!="string"?{ok:!1,reason:"projectPath required"}:r.tokenEnv&&!g(r.tokenEnv)?{ok:!1,reason:`tokenEnv must be a WYRM_-prefixed env var name (got ${r.tokenEnv})`}:{ok:!0}}const i=r=>String(r).replace(/\/+$/,"");function d(r){try{const e=r.getMeta(u),n=e?JSON.parse(e):[];return Array.isArray(n)?n:[]}catch{return[]}}function B(r,e){const n=$(e);if(!n.ok)throw new Error(n.reason||"invalid peer");const s={baseUrl:i(e.baseUrl),projectPath:e.projectPath,...e.remoteProject?{remoteProject:e.remoteProject}:{},...e.tokenEnv?{tokenEnv:e.tokenEnv}:{}},t=d(r).filter(o=>!(i(o.baseUrl)===s.baseUrl&&o.projectPath===s.projectPath));return t.push(s),t.length>S&&t.splice(0,t.length-S),r.setMeta(u,JSON.stringify(t)),t}function W(r,e,n){const s=i(e),t=d(r).filter(o=>!(i(o.baseUrl)===s&&o.projectPath===n));return r.setMeta(u,JSON.stringify(t)),t}class q{db;intervalMs;constructor(e,n=E){this.db=e,this.intervalMs=n,P(),this.intervalMs=Math.max(v,n)}async tick(e=globalThis.fetch){if(!this.db.liveMemoryEnabled())return[];const n=d(this.db),s=[];for(const t of n){const o=this.db.getProject(t.projectPath);if(!o){s.push({peer:t.baseUrl,errors:["project not found: "+t.projectPath]});continue}const y=t.tokenEnv&&g(t.tokenEnv)?process.env[t.tokenEnv]:void 0;try{const a=await A(this.db,{baseUrl:i(t.baseUrl),token:y,localProjectId:o.id,remoteProject:t.remoteProject||o.name},e);s.push({peer:t.baseUrl,project:o.name,...a})}catch(a){s.push({peer:t.baseUrl,project:o.name,errors:[a instanceof Error?a.message:String(a)]})}}return s}async run(){f(`replication daemon started \xB7 interval=${this.intervalMs}ms \xB7 peers=${d(this.db).length}`);let e=!1;const n=setInterval(async()=>{if(!e){e=!0;try{const t=await this.tick();for(const o of t)(o.errors.length||(o.pulled||0)+(o.pushed||0)>0)&&f(`tick: ${JSON.stringify(o)}`)}catch(t){f(`tick error: ${t instanceof Error?t.message:String(t)}`)}finally{e=!1}}},this.intervalMs),s=t=>{clearInterval(n),f(`${t} \u2014 shutting down`);try{this.db.close?.()}catch{}process.exit(0)};process.on("SIGTERM",()=>s("SIGTERM")),process.on("SIGINT",()=>s("SIGINT"))}}class z{daemonEntrypoint;constructor(e){this.daemonEntrypoint=e,P()}start(e={}){if(this.isAlive())return{ok:!1,pid:this.readPid(),reason:"already running"};F();const n=Number(e.interval_minutes),s=Number.isFinite(n)&&n>0?Math.round(n*6e4):E,t=N(c,"a");try{const o=b(process.execPath,[this.daemonEntrypoint,String(s)],{detached:!0,stdio:["ignore",t,t],env:{...process.env}});return o.unref(),typeof o.pid!="number"?{ok:!1,reason:"spawn returned no pid"}:(M(p,String(o.pid)),{ok:!0,pid:o.pid})}finally{w(t)}}stop(){if(!this.isAlive())return this.cleanPid(),{ok:!1,reason:"not running"};const e=this.readPid();if(!e)return{ok:!1,reason:"pid file unreadable"};try{return process.kill(e,"SIGTERM"),{ok:!0}}catch(n){return{ok:!1,reason:n instanceof Error?n.message:String(n)}}}status(){const e=this.isAlive();return{running:e,pid:e?this.readPid()??void 0:void 0}}isAlive(){if(!h(p))return!1;const e=this.readPid();if(!e)return!1;try{return process.kill(e,L),!0}catch{return!1}}readPid(){try{const e=Number(j(p,"utf8").trim());return Number.isFinite(e)&&e>0?e:void 0}catch{return}}cleanPid(){try{k(p)}catch{}}}function P(){h(l)||_(l,{recursive:!0})}function f(r){process.stdout.write(`[${new Date().toISOString()}] ${r}
|
|
2
|
+
`)}function F(){if(h(c))try{if(I(c).size>x){const r=`${c}.1`;try{k(r)}catch{}U(c,r)}}catch{}}export{E as DEFAULT_INTERVAL_MS,v as MIN_INTERVAL_MS,u as PEERS_META_KEY,q as ReplicationDaemon,z as ReplicationManager,B as addPeer,d as listPeers,W as removePeer,$ as validatePeerEntry};
|