@floomhq/floom 1.0.53 → 1.0.54

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
@@ -21,6 +21,7 @@ import { auditSkills } from "./audit.js";
21
21
  import { share } from "./share.js";
22
22
  import { feedback } from "./feedback.js";
23
23
  import { status } from "./status.js";
24
+ import { launchGate } from "./launch.js";
24
25
  import { libraryAddSkill, libraryCreate, libraryList, libraryRemoveSkill, librarySubscribe, libraryUnsubscribe, moveSkill, } from "./library.js";
25
26
  import { c, symbols } from "./ui.js";
26
27
  import { printError, FloomError } from "./errors.js";
@@ -129,6 +130,8 @@ function commandUsage() {
129
130
  ${c.dim(`Flags: install|status|logs|run, --target ${TARGET_HINT}|all`)}
130
131
  ${c.cyan("audit skills")} Read-only skill quality and duplicate report
131
132
  ${c.dim("Flags: --json, --fix-plan")}
133
+ ${c.cyan("launch gate")} Check pinned release identity for launch evidence
134
+ ${c.dim("Flags: --json")}
132
135
 
133
136
  ${c.bold("Examples")}
134
137
  ${c.cyan("npx -y @floomhq/floom add")} ${c.dim("https://floom.dev/s/ffas93ud --setup")}
@@ -337,6 +340,9 @@ function subcommandUsage(cmd) {
337
340
  case "feedback":
338
341
  feedbackUsage();
339
342
  return true;
343
+ case "launch":
344
+ commandUsage();
345
+ return true;
340
346
  case "library":
341
347
  case "lib":
342
348
  libraryUsage();
@@ -1209,6 +1215,16 @@ async function main() {
1209
1215
  });
1210
1216
  return;
1211
1217
  }
1218
+ case "launch": {
1219
+ const sub = rest[0];
1220
+ if (sub !== "gate")
1221
+ throw new FloomError("Unknown launch command.", `Try \`${CLI_COMMAND} launch gate --json\`.`);
1222
+ const args = rest.slice(1);
1223
+ const json = args.includes("--json");
1224
+ rejectArgs(args.filter((arg) => arg !== "--json"), `Try \`${CLI_COMMAND} launch gate --json\`.`);
1225
+ await launchGate({ json });
1226
+ return;
1227
+ }
1212
1228
  case "status":
1213
1229
  await status(parseStatusFlags(rest));
1214
1230
  return;
package/dist/launch.js ADDED
@@ -0,0 +1,82 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { CONFIG_DIR, readConfig, resolveApiUrl } from "./config.js";
3
+ import { floomFetch } from "./lib/api.js";
4
+ import { CLI_VERSION } from "./version.js";
5
+ import { c, symbols } from "./ui.js";
6
+ import { FloomError } from "./errors.js";
7
+ import { join } from "node:path";
8
+ async function readDaemonStatus() {
9
+ try {
10
+ return JSON.parse(await readFile(join(CONFIG_DIR, "daemon-status.json"), "utf8"));
11
+ }
12
+ catch (err) {
13
+ if (err.code === "ENOENT")
14
+ return null;
15
+ throw err;
16
+ }
17
+ }
18
+ async function getJson(url, action, token) {
19
+ try {
20
+ const res = await floomFetch(url, action, {
21
+ ...(token ? { token } : {}),
22
+ timeoutMs: 8_000,
23
+ rateLimitRetries: 0,
24
+ });
25
+ return (await res.json());
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ export async function launchGate(opts) {
32
+ const cfg = await readConfig();
33
+ const apiUrl = resolveApiUrl(cfg ?? undefined);
34
+ const [health, cliVersion, daemon] = await Promise.all([
35
+ getJson(`${apiUrl}/api/v1/health`, "check launch health", cfg?.accessToken),
36
+ getJson(`${apiUrl}/api/v1/cli-version`, "check CLI version", cfg?.accessToken),
37
+ readDaemonStatus(),
38
+ ]);
39
+ const targetResults = daemon?.last_run ?? {};
40
+ const targets = daemon?.targets ?? [];
41
+ const daemonTargetsOk = targets.length > 0 && targets.every((target) => targetResults[target]?.ok === true);
42
+ const releaseAligned = health?.version === CLI_VERSION && cliVersion?.latest === CLI_VERSION;
43
+ const payload = {
44
+ ok: Boolean(health?.ok && releaseAligned && daemon?.running && daemon?.version === CLI_VERSION),
45
+ release: {
46
+ cli: CLI_VERSION,
47
+ web: health?.version ?? null,
48
+ cli_latest: cliVersion?.latest ?? null,
49
+ cli_min: cliVersion?.min ?? null,
50
+ release_aligned: releaseAligned,
51
+ },
52
+ health: health ?? null,
53
+ daemon: daemon ? {
54
+ running: daemon.running === true,
55
+ version: daemon.version ?? null,
56
+ hostname: daemon.hostname ?? null,
57
+ targets,
58
+ all_targets_ok: daemonTargetsOk,
59
+ last_completed_at: daemon.last_completed_at ?? null,
60
+ last_run: targetResults,
61
+ } : null,
62
+ escalations: [
63
+ ...(releaseAligned ? [] : ["CLI, web health, and server latest versions are not aligned."]),
64
+ ...(daemon?.running && daemon.version === CLI_VERSION ? [] : ["Daemon is not running on the pinned CLI version."]),
65
+ ...(targets.length === 0 || daemonTargetsOk ? [] : ["Latest daemon cycle has not yet passed for every configured target."]),
66
+ ],
67
+ };
68
+ if (opts.json) {
69
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
70
+ return;
71
+ }
72
+ process.stdout.write(`\n${symbols.dot} ${c.bold("Floom launch gate")}\n\n`);
73
+ process.stdout.write(` ${c.dim("CLI:")} ${payload.release.cli}\n`);
74
+ process.stdout.write(` ${c.dim("Web:")} ${payload.release.web ?? "unknown"}\n`);
75
+ process.stdout.write(` ${c.dim("Daemon:")} ${payload.daemon?.running ? `running (${payload.daemon.version})` : "not running"}\n`);
76
+ if (payload.escalations.length > 0) {
77
+ for (const escalation of payload.escalations)
78
+ process.stdout.write(` ${symbols.bullet} ${escalation}\n`);
79
+ throw new FloomError("Launch gate did not pass.", "Run with --json for machine-readable details.");
80
+ }
81
+ process.stdout.write(`\n${symbols.ok} Launch gate passed for the pinned local release identity.\n\n`);
82
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floomhq/floom",
3
- "version": "1.0.53",
3
+ "version": "1.0.54",
4
4
  "description": "Sync AI skills across agents and machines.",
5
5
  "license": "MIT",
6
6
  "type": "module",