@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 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 { getJsonWithEtag, FloomApiError } from "./lib/api.js";
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: ETag from the last successful (non-304) response, plus
11
- // the last time we logged a heartbeat. Survives across setInterval ticks
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 response;
361
+ let payload;
364
362
  try {
365
- response = await getJsonWithEtag(`${apiUrl}/api/v1/me/skills`, cfg.accessToken, cachedEtag, signal);
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
- response = await getJsonWithEtag(`${apiUrl}/api/v1/me/skills`, cfg.accessToken, cachedEtag, signal);
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.28";
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"]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floomhq/floom-mcp-sync",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
4
4
  "description": "Lightweight Floom MCP server for installing, publishing, and startup-syncing skills.",
5
5
  "license": "MIT",
6
6
  "type": "module",