@floomhq/floom-mcp-sync 1.0.36 → 1.0.38
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/dist/auto-sync.js +7 -3
- package/dist/lib/manifest.js +7 -1
- package/dist/server.js +30 -8
- package/package.json +1 -1
package/dist/auto-sync.js
CHANGED
|
@@ -346,7 +346,7 @@ async function manifestHasMissingTrackedFile(manifest, root) {
|
|
|
346
346
|
}
|
|
347
347
|
return false;
|
|
348
348
|
}
|
|
349
|
-
export async function autoSync(log = (message) => process.stderr.write(`${message}\n`), signal) {
|
|
349
|
+
export async function autoSync(log = (message) => process.stderr.write(`${message}\n`), signal, opts = {}) {
|
|
350
350
|
const initialCfg = await readConfig();
|
|
351
351
|
if (!initialCfg) {
|
|
352
352
|
maybeAuthWarning(log, "[floom] not signed in; skipping sync (run `npx -y @floomhq/floom login`)");
|
|
@@ -354,7 +354,7 @@ export async function autoSync(log = (message) => process.stderr.write(`${messag
|
|
|
354
354
|
}
|
|
355
355
|
let cfg = initialCfg;
|
|
356
356
|
await ensureSyncManifestDir();
|
|
357
|
-
|
|
357
|
+
const result = await withSyncLock(async () => {
|
|
358
358
|
const manifest = await readSyncManifest();
|
|
359
359
|
const root = skillsDir();
|
|
360
360
|
const apiUrl = apiUrlFromConfig(cfg);
|
|
@@ -527,7 +527,11 @@ export async function autoSync(log = (message) => process.stderr.write(`${messag
|
|
|
527
527
|
maybeHeartbeat(log, () => `[floom] heartbeat: ${total} skills tracked, all up-to-date`);
|
|
528
528
|
}
|
|
529
529
|
return { synced: total, unchanged, updated, conflicts };
|
|
530
|
-
});
|
|
530
|
+
}, { wait: !opts.skipIfLocked });
|
|
531
|
+
if (result)
|
|
532
|
+
return result;
|
|
533
|
+
log("[floom] sync already running; skipping background poll");
|
|
534
|
+
return { synced: 0, unchanged: 0, updated: 0, conflicts: 0 };
|
|
531
535
|
}
|
|
532
536
|
async function loadSyncPayload(apiUrl, token, signal) {
|
|
533
537
|
const all = [];
|
package/dist/lib/manifest.js
CHANGED
|
@@ -225,10 +225,11 @@ export async function ensureSyncManifestDir() {
|
|
|
225
225
|
throw err;
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
|
-
export async function withSyncLock(fn) {
|
|
228
|
+
export async function withSyncLock(fn, opts = {}) {
|
|
229
229
|
await ensureSyncManifestDir();
|
|
230
230
|
const lockPath = join(dirname(syncManifestPath()), "sync.lock");
|
|
231
231
|
const startedAt = Date.now();
|
|
232
|
+
const wait = opts.wait !== false;
|
|
232
233
|
for (;;) {
|
|
233
234
|
try {
|
|
234
235
|
await mkdir(lockPath, { mode: 0o700 });
|
|
@@ -249,6 +250,8 @@ export async function withSyncLock(fn) {
|
|
|
249
250
|
continue;
|
|
250
251
|
throw statErr;
|
|
251
252
|
}
|
|
253
|
+
if (!wait)
|
|
254
|
+
return null;
|
|
252
255
|
if (Date.now() - startedAt > LOCK_TIMEOUT_MS) {
|
|
253
256
|
throw new Error("Timed out waiting for Floom sync lock.");
|
|
254
257
|
}
|
|
@@ -262,6 +265,9 @@ export async function withSyncLock(fn) {
|
|
|
262
265
|
await rm(lockPath, { recursive: true, force: true }).catch(() => { });
|
|
263
266
|
}
|
|
264
267
|
}
|
|
268
|
+
export async function clearSyncLock() {
|
|
269
|
+
await rm(join(dirname(syncManifestPath()), "sync.lock"), { recursive: true, force: true }).catch(() => { });
|
|
270
|
+
}
|
|
265
271
|
export function manifestKey(root, target) {
|
|
266
272
|
const relativeTarget = relative(resolve(root), resolve(target));
|
|
267
273
|
if (relativeTarget === ".." || relativeTarget.startsWith(`..${sep}`)) {
|
package/dist/server.js
CHANGED
|
@@ -2,21 +2,23 @@
|
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
3
|
import { stdin, stdout } from "node:process";
|
|
4
4
|
import { autoSync } from "./auto-sync.js";
|
|
5
|
+
import { clearSyncLock } from "./lib/manifest.js";
|
|
5
6
|
import { getSkill } from "./tools/get.js";
|
|
6
7
|
import { searchSkills } from "./tools/search.js";
|
|
7
8
|
import { syncStatus } from "./tools/status.js";
|
|
8
|
-
const SERVER_VERSION = "1.0.
|
|
9
|
+
const SERVER_VERSION = "1.0.38";
|
|
9
10
|
const DEFAULT_INTERVAL_MS = 60_000;
|
|
10
11
|
const MIN_INTERVAL_MS = 10_000;
|
|
11
12
|
const SEARCH_TYPES = new Set(["knowledge", "instruction", "workflow", "skill"]);
|
|
12
13
|
let syncInFlight = null;
|
|
13
14
|
let syncAbortController = null;
|
|
14
|
-
|
|
15
|
+
let shuttingDown = false;
|
|
16
|
+
function runAutoSync(opts = {}) {
|
|
15
17
|
if (syncInFlight)
|
|
16
18
|
return syncInFlight;
|
|
17
19
|
const controller = new AbortController();
|
|
18
20
|
syncAbortController = controller;
|
|
19
|
-
syncInFlight = autoSync(undefined, controller.signal).finally(() => {
|
|
21
|
+
syncInFlight = autoSync(undefined, controller.signal, opts).finally(() => {
|
|
20
22
|
if (syncAbortController === controller)
|
|
21
23
|
syncAbortController = null;
|
|
22
24
|
syncInFlight = null;
|
|
@@ -27,6 +29,24 @@ function abortAutoSync() {
|
|
|
27
29
|
syncAbortController?.abort();
|
|
28
30
|
syncAbortController = null;
|
|
29
31
|
}
|
|
32
|
+
async function stopAutoSync() {
|
|
33
|
+
const inFlight = syncInFlight;
|
|
34
|
+
abortAutoSync();
|
|
35
|
+
if (!inFlight)
|
|
36
|
+
return;
|
|
37
|
+
try {
|
|
38
|
+
await Promise.race([
|
|
39
|
+
inFlight,
|
|
40
|
+
new Promise((resolve) => setTimeout(resolve, 750)),
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Shutdown is best-effort; autoSync already logged actionable failures.
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
await clearSyncLock();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
30
50
|
function usage() {
|
|
31
51
|
return `
|
|
32
52
|
floom-mcp-sync v${SERVER_VERSION}
|
|
@@ -94,7 +114,7 @@ function startPolling(intervalMs, state) {
|
|
|
94
114
|
return;
|
|
95
115
|
}
|
|
96
116
|
state.inFlight = true;
|
|
97
|
-
runAutoSync().catch((err) => {
|
|
117
|
+
runAutoSync({ skipIfLocked: true }).catch((err) => {
|
|
98
118
|
process.stderr.write(`[floom] poll failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
99
119
|
}).finally(() => {
|
|
100
120
|
state.inFlight = false;
|
|
@@ -273,17 +293,19 @@ async function main() {
|
|
|
273
293
|
const intervalMs = resolvePollIntervalMs();
|
|
274
294
|
process.stderr.write(`[floom] starting sync poller (interval ${intervalMs}ms)\n`);
|
|
275
295
|
const syncState = { inFlight: true };
|
|
276
|
-
void runAutoSync().catch((err) => {
|
|
296
|
+
void runAutoSync({ skipIfLocked: true }).catch((err) => {
|
|
277
297
|
process.stderr.write(`[floom] initial sync failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
278
298
|
}).finally(() => {
|
|
279
299
|
syncState.inFlight = false;
|
|
280
300
|
});
|
|
281
301
|
const pollHandle = startPolling(intervalMs, syncState);
|
|
282
302
|
const shutdown = (signal) => {
|
|
303
|
+
if (shuttingDown)
|
|
304
|
+
return;
|
|
305
|
+
shuttingDown = true;
|
|
283
306
|
clearInterval(pollHandle);
|
|
284
|
-
abortAutoSync();
|
|
285
307
|
process.stderr.write(`[floom] received ${signal}, stopping sync poller\n`);
|
|
286
|
-
process.exit(0);
|
|
308
|
+
void stopAutoSync().finally(() => process.exit(0));
|
|
287
309
|
};
|
|
288
310
|
process.once("SIGINT", () => shutdown("SIGINT"));
|
|
289
311
|
process.once("SIGTERM", () => shutdown("SIGTERM"));
|
|
@@ -307,7 +329,7 @@ async function main() {
|
|
|
307
329
|
}
|
|
308
330
|
finally {
|
|
309
331
|
clearInterval(pollHandle);
|
|
310
|
-
|
|
332
|
+
await stopAutoSync();
|
|
311
333
|
process.stderr.write("[floom] stdin closed, stopping sync poller\n");
|
|
312
334
|
}
|
|
313
335
|
}
|