@floomhq/floom-mcp-sync 1.0.28 → 1.0.29
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 +30 -27
- package/dist/server.js +1 -1
- package/package.json +1 -1
package/dist/auto-sync.js
CHANGED
|
@@ -2,15 +2,13 @@ import { constants } from "node:fs";
|
|
|
2
2
|
import { lstat, mkdir, open } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { apiUrlFromConfig, readConfig, refreshConfigIfNeeded } from "./lib/config.js";
|
|
5
|
-
import {
|
|
5
|
+
import { getJson, FloomApiError } from "./lib/api.js";
|
|
6
6
|
import { sha256 } from "./lib/hash.js";
|
|
7
7
|
import { assertValidSlug } from "./lib/slug.js";
|
|
8
8
|
import { skillsDir, skillTargetPath } from "./lib/paths.js";
|
|
9
9
|
import { ensureSyncManifestDir, manifestKey, markSynced, readSyncManifest, unmarkSynced, withSyncLock, writeSyncManifest } from "./lib/manifest.js";
|
|
10
|
-
// Module-level cache:
|
|
11
|
-
//
|
|
12
|
-
// inside a single MCP server process.
|
|
13
|
-
let cachedEtag = null;
|
|
10
|
+
// Module-level cache: the last time we logged a heartbeat. Survives across
|
|
11
|
+
// setInterval ticks inside a single MCP server process.
|
|
14
12
|
let lastHeartbeatAt = 0;
|
|
15
13
|
let lastAuthWarningAt = 0;
|
|
16
14
|
const HEARTBEAT_MS = 10 * 60 * 1000; // 10 minutes
|
|
@@ -360,9 +358,9 @@ export async function autoSync(log = (message) => process.stderr.write(`${messag
|
|
|
360
358
|
const manifest = await readSyncManifest();
|
|
361
359
|
const root = skillsDir();
|
|
362
360
|
const apiUrl = apiUrlFromConfig(cfg);
|
|
363
|
-
let
|
|
361
|
+
let payload;
|
|
364
362
|
try {
|
|
365
|
-
|
|
363
|
+
payload = await loadSyncPayload(apiUrl, cfg.accessToken, signal);
|
|
366
364
|
}
|
|
367
365
|
catch (err) {
|
|
368
366
|
if (!(err instanceof FloomApiError) || err.status !== 401)
|
|
@@ -373,26 +371,7 @@ export async function autoSync(log = (message) => process.stderr.write(`${messag
|
|
|
373
371
|
return { synced: 0, unchanged: 0, updated: 0, conflicts: 0 };
|
|
374
372
|
}
|
|
375
373
|
cfg = refreshed;
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
if (response.status === 304) {
|
|
379
|
-
if (await manifestHasMissingTrackedFile(manifest, root)) {
|
|
380
|
-
response = await getJsonWithEtag(`${apiUrl}/api/v1/me/skills`, cfg.accessToken, null, signal);
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
maybeHeartbeat(log);
|
|
384
|
-
return { synced: 0, unchanged: 0, updated: 0, conflicts: 0 };
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
if (response.status === 304) {
|
|
388
|
-
maybeHeartbeat(log);
|
|
389
|
-
return { synced: 0, unchanged: 0, updated: 0, conflicts: 0 };
|
|
390
|
-
}
|
|
391
|
-
if (response.etag)
|
|
392
|
-
cachedEtag = response.etag;
|
|
393
|
-
const payload = response.body;
|
|
394
|
-
if (!payload) {
|
|
395
|
-
return { synced: 0, unchanged: 0, updated: 0, conflicts: 0 };
|
|
374
|
+
payload = await loadSyncPayload(apiUrl, cfg.accessToken, signal);
|
|
396
375
|
}
|
|
397
376
|
await mkdir(skillsDir(), { recursive: true, mode: 0o700 });
|
|
398
377
|
if (!Array.isArray(payload.skills)) {
|
|
@@ -550,6 +529,30 @@ export async function autoSync(log = (message) => process.stderr.write(`${messag
|
|
|
550
529
|
return { synced: total, unchanged, updated, conflicts };
|
|
551
530
|
});
|
|
552
531
|
}
|
|
532
|
+
async function loadSyncPayload(apiUrl, token, signal) {
|
|
533
|
+
const all = [];
|
|
534
|
+
let fullSync = false;
|
|
535
|
+
let cursor;
|
|
536
|
+
const seenCursors = new Set();
|
|
537
|
+
for (let page = 0; page < 1000; page += 1) {
|
|
538
|
+
const url = new URL(`${apiUrl}/api/v1/me/skills`);
|
|
539
|
+
url.searchParams.set("limit", "25");
|
|
540
|
+
if (cursor)
|
|
541
|
+
url.searchParams.set("cursor", cursor);
|
|
542
|
+
const payload = await getJson(url.toString(), token, signal);
|
|
543
|
+
if (!Array.isArray(payload.skills))
|
|
544
|
+
throw new Error("Invalid sync response");
|
|
545
|
+
all.push(...payload.skills);
|
|
546
|
+
fullSync = payload.full_sync === true;
|
|
547
|
+
if (!payload.next_cursor)
|
|
548
|
+
return { skills: all, full_sync: fullSync };
|
|
549
|
+
if (seenCursors.has(payload.next_cursor))
|
|
550
|
+
throw new Error("Invalid sync response");
|
|
551
|
+
seenCursors.add(payload.next_cursor);
|
|
552
|
+
cursor = payload.next_cursor;
|
|
553
|
+
}
|
|
554
|
+
throw new Error("Invalid sync response");
|
|
555
|
+
}
|
|
553
556
|
async function emitSyncCompleted(apiUrl, token, props, signal) {
|
|
554
557
|
try {
|
|
555
558
|
const init = {
|
package/dist/server.js
CHANGED
|
@@ -5,7 +5,7 @@ import { autoSync } from "./auto-sync.js";
|
|
|
5
5
|
import { getSkill } from "./tools/get.js";
|
|
6
6
|
import { searchSkills } from "./tools/search.js";
|
|
7
7
|
import { syncStatus } from "./tools/status.js";
|
|
8
|
-
const SERVER_VERSION = "1.0.
|
|
8
|
+
const SERVER_VERSION = "1.0.29";
|
|
9
9
|
const DEFAULT_INTERVAL_MS = 60_000;
|
|
10
10
|
const MIN_INTERVAL_MS = 10_000;
|
|
11
11
|
const SEARCH_TYPES = new Set(["knowledge", "instruction", "workflow", "skill"]);
|