@floomhq/floom 1.0.29 → 1.0.31

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/cli.js CHANGED
@@ -654,7 +654,7 @@ function sleep(ms, signal) {
654
654
  }, { once: true });
655
655
  });
656
656
  }
657
- async function watch(intervalSeconds) {
657
+ async function watch(intervalSeconds, target, once) {
658
658
  const cfg = await readConfig();
659
659
  if (!cfg) {
660
660
  throw new FloomError("Not signed in.", `Run \`${CLI_COMMAND} login\` before \`${CLI_COMMAND} watch\`, or use \`${CLI_COMMAND} add <link>\` without an account.`);
@@ -671,9 +671,11 @@ async function watch(intervalSeconds) {
671
671
  };
672
672
  process.on("SIGINT", stop);
673
673
  process.on("SIGTERM", stop);
674
- process.stdout.write(`${symbols.bullet} Watching Floom sync every ${intervalSeconds}s. Press Ctrl-C to stop.\n`);
674
+ process.stdout.write(`${symbols.bullet} Watching Floom sync for ${target} every ${intervalSeconds}s. Press Ctrl-C to stop.\n`);
675
675
  while (!controller.signal.aborted) {
676
- await sync({ spinner: false, quietUnchanged: true });
676
+ await sync({ spinner: false, quietUnchanged: true, target });
677
+ if (once)
678
+ return;
677
679
  await sleep(intervalSeconds * 1000, controller.signal);
678
680
  }
679
681
  }
@@ -827,7 +829,7 @@ async function main() {
827
829
  });
828
830
  return;
829
831
  }
830
- await watch(flags.intervalSeconds);
832
+ await watch(flags.intervalSeconds, flags.target, flags.once);
831
833
  return;
832
834
  }
833
835
  case "delete":
package/dist/config.js CHANGED
@@ -20,7 +20,7 @@ export async function readConfig() {
20
20
  const parsed = JSON.parse(buf);
21
21
  if (!parsed.accessToken || !parsed.refreshToken)
22
22
  return null;
23
- return parsed;
23
+ return refreshConfigIfNeeded(parsed);
24
24
  }
25
25
  catch (e) {
26
26
  if (e.code === "ENOENT")
@@ -28,6 +28,44 @@ export async function readConfig() {
28
28
  throw e;
29
29
  }
30
30
  }
31
+ export async function refreshConfigIfNeeded(cfg, opts = {}) {
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 refreshed = await refreshConfig(cfg);
37
+ await writeConfig(refreshed);
38
+ return refreshed;
39
+ }
40
+ catch {
41
+ return cfg;
42
+ }
43
+ }
44
+ async function refreshConfig(cfg) {
45
+ const apiUrl = resolveApiUrl(cfg);
46
+ const res = await fetch(`${apiUrl}/api/auth/refresh`, {
47
+ method: "POST",
48
+ headers: { "content-type": "application/json" },
49
+ body: JSON.stringify({ refresh_token: cfg.refreshToken }),
50
+ });
51
+ if (!res.ok)
52
+ throw new Error(`refresh failed with ${res.status}`);
53
+ const data = (await res.json());
54
+ if (!data.access_token || !data.refresh_token)
55
+ throw new Error("refresh response missing tokens");
56
+ const expiresIn = Number(data.expires_in ?? "3600");
57
+ return {
58
+ ...cfg,
59
+ accessToken: data.access_token,
60
+ refreshToken: data.refresh_token,
61
+ expiresAt: Math.floor(Date.now() / 1000) + (Number.isFinite(expiresIn) ? expiresIn : 3600),
62
+ ...(typeof data.email === "string" || data.email === null
63
+ ? { email: data.email }
64
+ : typeof cfg.email === "string" || cfg.email === null
65
+ ? { email: cfg.email }
66
+ : {}),
67
+ };
68
+ }
31
69
  export async function writeConfig(cfg) {
32
70
  const targetPath = process.env.FLOOM_CONFIG_PATH ?? CONFIG_PATH;
33
71
  const targetDir = dirname(targetPath);
package/dist/doctor.js CHANGED
@@ -4,7 +4,7 @@ import { delimiter } from "node:path";
4
4
  import { join } from "node:path";
5
5
  import { stat, readFile, access, readdir, constants, realpath } from "node:fs/promises";
6
6
  import { promisify } from "node:util";
7
- import { readConfig, CONFIG_PATH, resolveApiUrl } from "./config.js";
7
+ import { readConfig, refreshConfigIfNeeded, CONFIG_PATH, resolveApiUrl } from "./config.js";
8
8
  import { floomFetch } from "./lib/api.js";
9
9
  import { c, symbols } from "./ui.js";
10
10
  import { CLI_VERSION, compareSemverish, formatVersionLabel } from "./version.js";
@@ -33,6 +33,21 @@ async function checkAuth() {
33
33
  checkOk: false,
34
34
  });
35
35
  if (res.status === 401) {
36
+ const refreshed = await refreshConfigIfNeeded(cfg, { force: true });
37
+ if (refreshed.accessToken !== cfg.accessToken) {
38
+ const retry = await floomFetch(`${apiUrl}/api/me`, "check authentication", {
39
+ token: refreshed.accessToken,
40
+ checkOk: false,
41
+ });
42
+ if (retry.ok) {
43
+ const data = (await retry.json());
44
+ return {
45
+ name: "Auth",
46
+ status: "ok",
47
+ detail: data.email ? `Signed in as ${data.email} (refreshed)` : "Signed in (refreshed)",
48
+ };
49
+ }
50
+ }
36
51
  return {
37
52
  name: "Auth",
38
53
  status: "fail",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floomhq/floom",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
4
4
  "description": "Sync AI skills across agents and machines.",
5
5
  "license": "MIT",
6
6
  "type": "module",