@floomhq/floom-mcp-sync 1.0.27 → 1.0.28
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 +10 -5
- package/dist/lib/config.js +47 -2
- package/dist/server.js +1 -1
- package/package.json +1 -1
package/dist/auto-sync.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
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
|
-
import { apiUrlFromConfig, readConfig } from "./lib/config.js";
|
|
4
|
+
import { apiUrlFromConfig, readConfig, refreshConfigIfNeeded } from "./lib/config.js";
|
|
5
5
|
import { getJsonWithEtag, FloomApiError } from "./lib/api.js";
|
|
6
6
|
import { sha256 } from "./lib/hash.js";
|
|
7
7
|
import { assertValidSlug } from "./lib/slug.js";
|
|
@@ -349,11 +349,12 @@ async function manifestHasMissingTrackedFile(manifest, root) {
|
|
|
349
349
|
return false;
|
|
350
350
|
}
|
|
351
351
|
export async function autoSync(log = (message) => process.stderr.write(`${message}\n`), signal) {
|
|
352
|
-
const
|
|
353
|
-
if (!
|
|
352
|
+
const initialCfg = await readConfig();
|
|
353
|
+
if (!initialCfg) {
|
|
354
354
|
maybeAuthWarning(log, "[floom] not signed in; skipping sync (run `npx -y @floomhq/floom login`)");
|
|
355
355
|
return { synced: 0, unchanged: 0, updated: 0, conflicts: 0 };
|
|
356
356
|
}
|
|
357
|
+
let cfg = initialCfg;
|
|
357
358
|
await ensureSyncManifestDir();
|
|
358
359
|
return await withSyncLock(async () => {
|
|
359
360
|
const manifest = await readSyncManifest();
|
|
@@ -364,11 +365,15 @@ export async function autoSync(log = (message) => process.stderr.write(`${messag
|
|
|
364
365
|
response = await getJsonWithEtag(`${apiUrl}/api/v1/me/skills`, cfg.accessToken, cachedEtag, signal);
|
|
365
366
|
}
|
|
366
367
|
catch (err) {
|
|
367
|
-
if (err instanceof FloomApiError
|
|
368
|
+
if (!(err instanceof FloomApiError) || err.status !== 401)
|
|
369
|
+
throw err;
|
|
370
|
+
const refreshed = await refreshConfigIfNeeded(cfg, { force: true });
|
|
371
|
+
if (refreshed.accessToken === cfg.accessToken) {
|
|
368
372
|
maybeAuthWarning(log, "[floom] sign-in expired; skipping sync (run `npx -y @floomhq/floom login` again)");
|
|
369
373
|
return { synced: 0, unchanged: 0, updated: 0, conflicts: 0 };
|
|
370
374
|
}
|
|
371
|
-
|
|
375
|
+
cfg = refreshed;
|
|
376
|
+
response = await getJsonWithEtag(`${apiUrl}/api/v1/me/skills`, cfg.accessToken, cachedEtag, signal);
|
|
372
377
|
}
|
|
373
378
|
if (response.status === 304) {
|
|
374
379
|
if (await manifestHasMissingTrackedFile(manifest, root)) {
|
package/dist/lib/config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
1
|
+
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
2
3
|
import { configPath } from "./paths.js";
|
|
3
4
|
export const DEFAULT_API_URL = "https://floom.dev";
|
|
4
5
|
export function apiUrlFromConfig(cfg) {
|
|
@@ -10,13 +11,14 @@ export async function readConfig() {
|
|
|
10
11
|
const parsed = JSON.parse(raw);
|
|
11
12
|
if (typeof parsed.accessToken !== "string" || parsed.accessToken.length === 0)
|
|
12
13
|
return null;
|
|
13
|
-
|
|
14
|
+
const cfg = {
|
|
14
15
|
accessToken: parsed.accessToken,
|
|
15
16
|
...(typeof parsed.apiUrl === "string" ? { apiUrl: parsed.apiUrl } : {}),
|
|
16
17
|
...(typeof parsed.refreshToken === "string" ? { refreshToken: parsed.refreshToken } : {}),
|
|
17
18
|
...(typeof parsed.expiresAt === "number" ? { expiresAt: parsed.expiresAt } : {}),
|
|
18
19
|
...(typeof parsed.email === "string" || parsed.email === null ? { email: parsed.email } : {}),
|
|
19
20
|
};
|
|
21
|
+
return await refreshConfigIfNeeded(cfg);
|
|
20
22
|
}
|
|
21
23
|
catch (err) {
|
|
22
24
|
if (err.code === "ENOENT")
|
|
@@ -24,3 +26,46 @@ export async function readConfig() {
|
|
|
24
26
|
throw err;
|
|
25
27
|
}
|
|
26
28
|
}
|
|
29
|
+
export async function refreshConfigIfNeeded(cfg, opts = {}) {
|
|
30
|
+
if (!cfg.refreshToken)
|
|
31
|
+
return cfg;
|
|
32
|
+
const now = Math.floor(Date.now() / 1000);
|
|
33
|
+
if (!opts.force && (typeof cfg.expiresAt !== "number" || cfg.expiresAt > now + 120))
|
|
34
|
+
return cfg;
|
|
35
|
+
try {
|
|
36
|
+
const apiUrl = apiUrlFromConfig(cfg);
|
|
37
|
+
const res = await fetch(`${apiUrl}/api/auth/refresh`, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: { "content-type": "application/json" },
|
|
40
|
+
body: JSON.stringify({ refresh_token: cfg.refreshToken }),
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok)
|
|
43
|
+
throw new Error(`refresh failed with ${res.status}`);
|
|
44
|
+
const data = (await res.json());
|
|
45
|
+
if (!data.access_token || !data.refresh_token)
|
|
46
|
+
throw new Error("refresh response missing tokens");
|
|
47
|
+
const expiresIn = Number(data.expires_in ?? "3600");
|
|
48
|
+
const refreshed = {
|
|
49
|
+
...cfg,
|
|
50
|
+
accessToken: data.access_token,
|
|
51
|
+
refreshToken: data.refresh_token,
|
|
52
|
+
expiresAt: Math.floor(Date.now() / 1000) + (Number.isFinite(expiresIn) ? expiresIn : 3600),
|
|
53
|
+
...(typeof data.email === "string" || data.email === null
|
|
54
|
+
? { email: data.email }
|
|
55
|
+
: typeof cfg.email === "string" || cfg.email === null
|
|
56
|
+
? { email: cfg.email }
|
|
57
|
+
: {}),
|
|
58
|
+
};
|
|
59
|
+
await writeConfig(refreshed);
|
|
60
|
+
return refreshed;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return cfg;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function writeConfig(cfg) {
|
|
67
|
+
const target = configPath();
|
|
68
|
+
await mkdir(dirname(target), { recursive: true, mode: 0o700 });
|
|
69
|
+
await writeFile(target, `${JSON.stringify(cfg, null, 2)}\n`, { mode: 0o600 });
|
|
70
|
+
await chmod(target, 0o600);
|
|
71
|
+
}
|
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.28";
|
|
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"]);
|