@floomhq/floom 3.0.0 → 3.0.2

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/index.js CHANGED
@@ -3040,6 +3040,21 @@ function emitJson(value) {
3040
3040
  writeStdout(JSON.stringify(value, null, 2) + "\n");
3041
3041
  }
3042
3042
 
3043
+ // src/lib/pipe-guard.ts
3044
+ var installed = false;
3045
+ function installPipeGuards() {
3046
+ if (installed) return;
3047
+ installed = true;
3048
+ const swallow = (err) => {
3049
+ if (err && err.code === "EPIPE") {
3050
+ process.exit(0);
3051
+ }
3052
+ throw err;
3053
+ };
3054
+ process.stdout.on("error", swallow);
3055
+ process.stderr.on("error", swallow);
3056
+ }
3057
+
3043
3058
  // src/commands/login.ts
3044
3059
  init_src();
3045
3060
  import { spawn as spawn2 } from "node:child_process";
@@ -3238,7 +3253,7 @@ async function getMachineIdentity() {
3238
3253
  }
3239
3254
 
3240
3255
  // src/version.ts
3241
- var VERSION = "3.0.0";
3256
+ var VERSION = "3.0.2";
3242
3257
 
3243
3258
  // src/api-client.ts
3244
3259
  var DEFAULT_TIMEOUT_MS = 2e4;
@@ -3289,12 +3304,12 @@ var HELP = "https://floom.dev/docs#troubleshooting";
3289
3304
  function friendlyApiErrorMessage(code, raw, status) {
3290
3305
  if (code === "AUTH_REQUIRED" || code === "TOKEN_INVALID" || status === 401) {
3291
3306
  return `Not signed in.
3292
- Run: npx -y @floomhq/floom login
3307
+ Run: floom login
3293
3308
  More help: ${HELP}`;
3294
3309
  }
3295
3310
  if (code === "TOKEN_EXPIRED") {
3296
3311
  return `Your session has expired.
3297
- Run: npx -y @floomhq/floom login
3312
+ Run: floom login
3298
3313
  More help: ${HELP}`;
3299
3314
  }
3300
3315
  if (code === "SKILL_ACCESS_DENIED" || status === 403) {
@@ -3303,7 +3318,7 @@ function friendlyApiErrorMessage(code, raw, status) {
3303
3318
  }
3304
3319
  if (code === "SKILL_NOT_FOUND" || status === 404) {
3305
3320
  return `Skill not found. It may have been deleted or the slug is wrong.
3306
- Run: npx -y @floomhq/floom list
3321
+ Run: floom list
3307
3322
  More help: ${HELP}`;
3308
3323
  }
3309
3324
  if (code === "RATE_LIMITED" || status === 429) {
@@ -3312,7 +3327,7 @@ function friendlyApiErrorMessage(code, raw, status) {
3312
3327
  }
3313
3328
  if (code === "CLI_VERSION_TOO_OLD") {
3314
3329
  return `Your CLI is out of date.
3315
- Run: npx -y @floomhq/floom@latest <command>
3330
+ Run: npm i -g @floomhq/floom@latest
3316
3331
  More help: ${HELP}`;
3317
3332
  }
3318
3333
  if (code === "FILE_TOO_LARGE" || code === "BUNDLE_TOO_LARGE") {
@@ -3331,7 +3346,7 @@ async function api(path, opts = {}) {
3331
3346
  if (opts.authRequired && !token) {
3332
3347
  throw new FloomError(
3333
3348
  "AUTH_REQUIRED",
3334
- "Not signed in.\n Run: npx -y @floomhq/floom login\n More help: https://floom.dev/docs#troubleshooting"
3349
+ "Not signed in.\n Run: floom login\n More help: https://floom.dev/docs#troubleshooting"
3335
3350
  );
3336
3351
  }
3337
3352
  let lastError = null;
@@ -4013,13 +4028,13 @@ async function writeSkillAtomically(root, skill) {
4013
4028
  await rm(tempDir, { recursive: true, force: true });
4014
4029
  await rm(replacedDir, { recursive: true, force: true });
4015
4030
  await mkdir4(tempDir, { recursive: true });
4016
- const installed = [];
4031
+ const installed2 = [];
4017
4032
  for (const file of skill.files) {
4018
4033
  const safePath = safeRemotePath(file.path);
4019
4034
  const dest = join7(tempDir, ...safePath.split("/"));
4020
4035
  await mkdir4(dirname(dest), { recursive: true });
4021
4036
  await writeFile3(dest, bytesForFile(file));
4022
- installed.push([slug, ...safePath.split("/")].join(sep4));
4037
+ installed2.push([slug, ...safePath.split("/")].join(sep4));
4023
4038
  }
4024
4039
  let movedExisting = false;
4025
4040
  try {
@@ -4053,7 +4068,7 @@ async function writeSkillAtomically(root, skill) {
4053
4068
  await rm(tempDir, { recursive: true, force: true });
4054
4069
  if (!movedExisting) await rm(replacedDir, { recursive: true, force: true });
4055
4070
  }
4056
- return installed.sort();
4071
+ return installed2.sort();
4057
4072
  }
4058
4073
  async function backupSkill(root, stamp, slug) {
4059
4074
  slug = safeSkillSlug(slug);
@@ -4686,14 +4701,19 @@ async function pushCommand(pathArg, options = {}, deps = {}) {
4686
4701
  wouldMutate: false,
4687
4702
  syncedCount: 0,
4688
4703
  plan: [],
4704
+ error: { code: "AUTH_REQUIRED", message: "Not signed in \u2014 log in first to publish." },
4689
4705
  next: ["floom login"]
4690
4706
  });
4707
+ process.exitCode = 1;
4691
4708
  return;
4692
4709
  }
4693
4710
  log.blank();
4711
+ log.err("Not signed in \u2014 log in first to publish.");
4712
+ log.blank();
4694
4713
  if (skills.length === 0) {
4695
4714
  log.info("No local skill folders found on this machine.");
4696
4715
  printNext([{ command: "floom login" }]);
4716
+ process.exitCode = 1;
4697
4717
  return;
4698
4718
  }
4699
4719
  log.info(` Found ${skills.length} skill${skills.length === 1 ? "" : "s"} on this machine:`);
@@ -4704,6 +4724,7 @@ async function pushCommand(pathArg, options = {}, deps = {}) {
4704
4724
  log.blank();
4705
4725
  log.info("After login, run:");
4706
4726
  log.command("floom push");
4727
+ process.exitCode = 1;
4707
4728
  return;
4708
4729
  }
4709
4730
  const me = await fetchMe().catch(() => null);
@@ -4894,13 +4915,23 @@ async function pushExplicitPath(pathArg, options, pushApi, authed) {
4894
4915
  }
4895
4916
  if (!authed) {
4896
4917
  if (json) {
4897
- emitJson({ workspace: { name: "Library", signedIn: false }, mode: "plan", applied: false, wouldMutate: true, syncedCount: 0, plan: [], next: ["floom login"] });
4918
+ emitJson({
4919
+ workspace: { name: "Library", signedIn: false },
4920
+ mode: "plan",
4921
+ applied: false,
4922
+ wouldMutate: false,
4923
+ syncedCount: 0,
4924
+ plan: [],
4925
+ error: { code: "AUTH_REQUIRED", message: "Not signed in \u2014 log in first to publish." },
4926
+ next: ["floom login"]
4927
+ });
4898
4928
  } else {
4899
4929
  log.blank();
4930
+ log.err("Not signed in \u2014 log in first to publish.");
4900
4931
  log.info(`Found local skill at ${tildePath(folder)}.`);
4901
- log.info("To publish it, sign in to Floom.");
4902
4932
  printNext([{ command: "floom login" }]);
4903
4933
  }
4934
+ process.exitCode = 1;
4904
4935
  return;
4905
4936
  }
4906
4937
  const slug = basename(folder);
@@ -5177,10 +5208,42 @@ async function deleteCommand(slug, opts = {}, deps = { api }) {
5177
5208
  }
5178
5209
 
5179
5210
  // src/commands/list.ts
5211
+ init_src();
5180
5212
  var MAX_HUMAN_ROWS = 20;
5181
5213
  async function listCommand(opts = {}) {
5182
5214
  const json = Boolean(opts.json);
5183
- const result = await api("/skills", { authRequired: true });
5215
+ if (!await readAuth()) {
5216
+ if (json) {
5217
+ emitJson({
5218
+ workspace: { name: "Library", signedIn: false },
5219
+ error: { code: "AUTH_REQUIRED", message: "Not signed in." },
5220
+ skills: [],
5221
+ next: ["floom login"]
5222
+ });
5223
+ } else {
5224
+ log.err("Not signed in.");
5225
+ printNext([{ command: "floom login" }]);
5226
+ }
5227
+ process.exitCode = 1;
5228
+ return;
5229
+ }
5230
+ let result;
5231
+ try {
5232
+ result = await api("/skills", { authRequired: true });
5233
+ } catch (e) {
5234
+ if (json) {
5235
+ const fe = e instanceof FloomError ? e : null;
5236
+ emitJson({
5237
+ workspace: { name: "Library", signedIn: true },
5238
+ error: { code: fe?.code ?? "INTERNAL_ERROR", message: e.message },
5239
+ skills: [],
5240
+ next: ["floom login"]
5241
+ });
5242
+ process.exitCode = 1;
5243
+ return;
5244
+ }
5245
+ throw e;
5246
+ }
5184
5247
  const localSkills = await discoverSkills();
5185
5248
  const installedBySlug = /* @__PURE__ */ new Map();
5186
5249
  for (const skill of localSkills) {
@@ -5814,9 +5877,9 @@ function buildApplyJson(workspaceName, applied, opts) {
5814
5877
  next: ["floom status"]
5815
5878
  };
5816
5879
  }
5817
- async function planForAgent(agent, scope, skillsDir) {
5880
+ async function planForAgent(agent, scope, skillsDir, statusFn = statusLibrary) {
5818
5881
  try {
5819
- const status = await statusLibrary(agent, { installDir: skillsDir });
5882
+ const status = await statusFn(agent, { installDir: skillsDir });
5820
5883
  return {
5821
5884
  agent,
5822
5885
  scope,
@@ -5829,7 +5892,12 @@ async function planForAgent(agent, scope, skillsDir) {
5829
5892
  return { agent, scope, skillsDir, pull: [], push: [], conflicts: [] };
5830
5893
  }
5831
5894
  }
5832
- async function syncCommand(rawOpts = {}) {
5895
+ async function syncCommand(rawOpts = {}, deps = {}) {
5896
+ const detectAgentsFn = deps.detectAgents ?? detectAgents;
5897
+ const runSyncFn = deps.runSyncForTarget ?? runSyncForTarget;
5898
+ const statusFn = deps.statusLibrary ?? statusLibrary;
5899
+ const fetchMeFn = deps.fetchMe ?? fetchMe;
5900
+ const readAuthFn = deps.readAuth ?? readAuth;
5833
5901
  const cleanup = installCancellationHandler();
5834
5902
  try {
5835
5903
  let flags;
@@ -5845,7 +5913,7 @@ async function syncCommand(rawOpts = {}) {
5845
5913
  }
5846
5914
  const json = flags.json;
5847
5915
  const planMode = isPlanMode(flags);
5848
- if (!await readAuth()) {
5916
+ if (!await readAuthFn()) {
5849
5917
  if (json) {
5850
5918
  emitJson({ workspace: { name: "Library", signedIn: false }, mode: "plan", applied: false, wouldMutate: false, hasSkipped: false, agents: [], next: ["floom login"] });
5851
5919
  } else {
@@ -5855,30 +5923,30 @@ async function syncCommand(rawOpts = {}) {
5855
5923
  process.exitCode = 2;
5856
5924
  return;
5857
5925
  }
5858
- const detected = await detectAgents();
5926
+ const detected = await detectAgentsFn();
5859
5927
  if (detected.length === 0) {
5860
5928
  if (json) {
5861
- emitJson({ workspace: { name: "Library", signedIn: true }, mode: "plan", applied: false, wouldMutate: false, hasSkipped: false, agents: [], next: ["floom sync --agent claude"] });
5929
+ emitJson({ workspace: { name: "Library", signedIn: true }, mode: "plan", applied: false, wouldMutate: false, hasSkipped: false, agents: [], error: "no agents detected \u2014 install Claude, Codex, Cursor, Gemini, or OpenCode", next: ["floom sync --agent claude"] });
5862
5930
  } else {
5863
- log.info("No AI agents found on this machine.");
5864
- log.info("Floom looks for Claude, Codex, Cursor, Gemini, or OpenCode.");
5931
+ log.err("No agents detected \u2014 install Claude, Codex, Cursor, Gemini, or OpenCode, then re-run.");
5932
+ log.info("Floom syncs into one of: Claude, Codex, Cursor, Gemini, OpenCode.");
5933
+ log.info("More: https://floom.dev/docs#agents");
5865
5934
  }
5866
5935
  process.exitCode = 1;
5867
5936
  return;
5868
5937
  }
5938
+ const defaultedToAll = flags.agents.length === 0 && !flags.allAgents;
5869
5939
  const selectedAgents = flags.agents.length > 0 ? flags.agents : detected;
5870
- if (!planMode && flags.agents.length === 0 && !flags.allAgents && !isInteractive()) {
5940
+ if (!planMode && !flags.yes && !isInteractive()) {
5871
5941
  if (json) {
5872
- emitJson({ workspace: { name: "Library", signedIn: true }, error: "floom sync needs to know which agents to update", next: ["floom sync --all-agents --yes"] });
5942
+ emitJson({ workspace: { name: "Library", signedIn: true }, error: "floom sync needs --yes to apply changes in a non-interactive shell", next: ["floom sync --yes"] });
5873
5943
  } else {
5874
- log.err("floom sync needs to know which agents to update.");
5875
- log.blank();
5876
- log.info("Pass --agent <name>, repeat --agent, or use --all-agents.");
5944
+ log.err("floom sync needs --yes to apply changes in a non-interactive shell.");
5877
5945
  log.blank();
5878
5946
  log.info("Examples:");
5947
+ log.command("floom sync --yes # sync every detected agent");
5879
5948
  log.command("floom sync --agent claude --yes");
5880
5949
  log.command("floom sync --agent claude,codex --yes");
5881
- log.command("floom sync --all-agents --yes");
5882
5950
  }
5883
5951
  process.exitCode = 2;
5884
5952
  return;
@@ -5888,7 +5956,7 @@ async function syncCommand(rawOpts = {}) {
5888
5956
  process.exitCode = 2;
5889
5957
  return;
5890
5958
  }
5891
- const me = await fetchMe().catch(() => null);
5959
+ const me = await fetchMeFn().catch(() => null);
5892
5960
  const workspaceName = me?.workspace?.name ?? "your Workspace";
5893
5961
  const hasProjectScope = await projectSkillsDirExists(process.cwd());
5894
5962
  const scopeChoice = flags.allScopes ? ["global", "project"] : flags.scope === "project" ? ["project"] : planMode && flags.scope === null && hasProjectScope ? ["global", "project"] : ["global"];
@@ -5897,7 +5965,7 @@ async function syncCommand(rawOpts = {}) {
5897
5965
  for (const scope of scopeChoice) {
5898
5966
  const dir = scope === "project" ? resolve6(projectSkillsDir(agent)) : resolve6(globalSkillsDir(agent));
5899
5967
  if (scope === "project" && !await projectSkillsDirExists(process.cwd())) continue;
5900
- plans.push(await planForAgent(agent, scope, dir));
5968
+ plans.push(await planForAgent(agent, scope, dir, statusFn));
5901
5969
  }
5902
5970
  }
5903
5971
  const actionable = plans.filter((p) => p.pull.length > 0 || p.push.length > 0);
@@ -5950,12 +6018,24 @@ async function syncCommand(rawOpts = {}) {
5950
6018
  return;
5951
6019
  }
5952
6020
  if (!wouldMutate && !hasSkipped) {
6021
+ if (json) {
6022
+ const noopApplied = plans.map((p) => ({ plan: p, ok: true, message: "up to date" }));
6023
+ emitJson(buildApplyJson(workspaceName, noopApplied, { failed: false, hasSkipped: false }));
6024
+ return;
6025
+ }
6026
+ if (defaultedToAll && detected.length > 1) {
6027
+ log.info(`Syncing every detected agent (${detected.map((a) => AGENT_LABELS[a]).join(", ")}).`);
6028
+ }
5953
6029
  log.info("Everything is in sync.");
5954
6030
  log.blank();
5955
6031
  for (const p of plans) log.row([AGENT_LABELS[p.agent], "up to date"], [10]);
5956
6032
  printNext([{ command: "floom status" }]);
5957
6033
  return;
5958
6034
  }
6035
+ if (defaultedToAll && detected.length > 1) {
6036
+ log.info(`Syncing every detected agent (${detected.map((a) => AGENT_LABELS[a]).join(", ")}).`);
6037
+ log.blank();
6038
+ }
5959
6039
  log.heading("Sync plan for this machine");
5960
6040
  log.blank();
5961
6041
  for (const p of plans) {
@@ -5993,7 +6073,7 @@ async function syncCommand(rawOpts = {}) {
5993
6073
  const applied = [];
5994
6074
  for (const p of toSync) {
5995
6075
  try {
5996
- const result = await runSyncForTarget({ target: p.agent, yes: true, installDir: p.skillsDir });
6076
+ const result = await runSyncFn({ target: p.agent, yes: true, installDir: p.skillsDir });
5997
6077
  const entry = appliedFromResult(p, result);
5998
6078
  if (!entry.ok) failed = true;
5999
6079
  applied.push(entry);
@@ -6642,19 +6722,19 @@ async function installCommand(input, rawOpts = {}) {
6642
6722
  }
6643
6723
  if (planMode) {
6644
6724
  const planAgents = await Promise.all(targets.map(async (t) => {
6645
- const installed = await existsAt(t.skillsDir, slug);
6725
+ const installed2 = await existsAt(t.skillsDir, slug);
6646
6726
  return {
6647
6727
  name: t.agent,
6648
6728
  scope: t.scope,
6649
6729
  skillsDir: t.skillsDir,
6650
6730
  path: join12(t.skillsDir, slug),
6651
- action: installed ? "update" : "install",
6731
+ action: installed2 ? "update" : "install",
6652
6732
  status: "ok",
6653
6733
  refusedReason: null,
6654
6734
  fromVersion: null,
6655
6735
  toVersion: shareData.skill.version.version_seq,
6656
6736
  backupPath: null,
6657
- message: installed ? `${shareData.skill.title} would be updated` : `${shareData.skill.title} would be installed`
6737
+ message: installed2 ? `${shareData.skill.title} would be updated` : `${shareData.skill.title} would be installed`
6658
6738
  };
6659
6739
  }));
6660
6740
  if (json) {
@@ -7048,8 +7128,8 @@ function npmLatest() {
7048
7128
  }, 5e3);
7049
7129
  });
7050
7130
  }
7051
- function significantlyOutdated(installed, latest) {
7052
- const a = installed.split(".").map((n) => Number.parseInt(n, 10));
7131
+ function significantlyOutdated(installed2, latest) {
7132
+ const a = installed2.split(".").map((n) => Number.parseInt(n, 10));
7053
7133
  const b = latest.split(".").map((n) => Number.parseInt(n, 10));
7054
7134
  if ((b[0] ?? 0) > (a[0] ?? 0)) return true;
7055
7135
  if ((b[0] ?? 0) === (a[0] ?? 0) && (b[1] ?? 0) > (a[1] ?? 0)) return true;
@@ -7922,61 +8002,108 @@ function aliasNotice(oldName, newName) {
7922
8002
  log.notice(`"${oldName}" is now "${newName}" \u2014 both work for now.`);
7923
8003
  }
7924
8004
  var program = new Command();
7925
- program.name("floom").description("Floom: one shared skill library, synced across every AI agent.").version(VERSION).addHelpCommand(false).helpOption(false).allowExcessArguments(true);
7926
- program.option("-h, --help", "show grouped help");
7927
- program.action(async (opts) => {
7928
- if (opts.help) {
7929
- printGroupedHelp();
8005
+ program.name("floom").description("Floom: one shared skill library, synced across every AI agent.").version(VERSION).addHelpCommand(false).helpOption("-h, --help", "show grouped help").allowExcessArguments(true).showSuggestionAfterError(true).showHelpAfterError(false);
8006
+ program.helpInformation = function helpInformation() {
8007
+ printGroupedHelp();
8008
+ return "";
8009
+ };
8010
+ function helpOpt(cmd) {
8011
+ return cmd.helpOption("-h, --help", "show help for this command").allowExcessArguments(false);
8012
+ }
8013
+ program.action(async (_opts, cmd) => {
8014
+ const stray = cmd.args.filter((a) => !a.startsWith("-"));
8015
+ if (stray.length > 0) {
8016
+ const unknown = stray[0];
8017
+ log.err(`unknown command: ${unknown}`);
8018
+ const knownNames = program.commands.map((c) => c.name());
8019
+ const suggestion = closestCommand(unknown, knownNames);
8020
+ if (suggestion) log.info(`Did you mean: floom ${suggestion}?`);
8021
+ log.info("Run: floom --help");
8022
+ process.exitCode = 2;
7930
8023
  return;
7931
8024
  }
7932
8025
  await dashboardCommand();
7933
8026
  });
7934
- program.command("login").description("sign in to your Floom workspace").action(loginCommand);
7935
- program.command("logout").description("sign out on this machine").action(logoutCommand);
7936
- program.command("account").description("show account details").option("--json", "print account state as JSON").action((opts) => accountCommand(opts));
7937
- program.command("whoami").description("show account details (alias)").option("--json", "print account state as JSON").action((opts) => {
8027
+ function closestCommand(input, candidates) {
8028
+ let best = null;
8029
+ let bestDist = Infinity;
8030
+ for (const c of candidates) {
8031
+ const d = editDistance(input, c);
8032
+ if (d < bestDist) {
8033
+ bestDist = d;
8034
+ best = c;
8035
+ }
8036
+ }
8037
+ const threshold = Math.max(2, Math.floor(input.length * 0.4));
8038
+ return best && bestDist <= threshold ? best : null;
8039
+ }
8040
+ function editDistance(a, b) {
8041
+ if (a === b) return 0;
8042
+ if (a.length === 0) return b.length;
8043
+ if (b.length === 0) return a.length;
8044
+ const prev = Array(b.length + 1).fill(0).map((_, i) => i);
8045
+ const curr = Array(b.length + 1).fill(0);
8046
+ for (let i = 1; i <= a.length; i += 1) {
8047
+ curr[0] = i;
8048
+ for (let j = 1; j <= b.length; j += 1) {
8049
+ const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
8050
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
8051
+ }
8052
+ for (let j = 0; j <= b.length; j += 1) prev[j] = curr[j];
8053
+ }
8054
+ return curr[b.length];
8055
+ }
8056
+ helpOpt(program.command("login").description("sign in to your Floom workspace")).action(loginCommand);
8057
+ helpOpt(program.command("logout").description("sign out on this machine")).action(logoutCommand);
8058
+ helpOpt(program.command("account").description("show account details").option("--json", "print account state as JSON")).action((opts) => accountCommand(opts));
8059
+ helpOpt(program.command("whoami").description("show account details (alias)").option("--json", "print account state as JSON")).action((opts) => {
7938
8060
  aliasNotice("whoami", "account");
7939
8061
  return accountCommand(opts);
7940
8062
  });
7941
- program.command("push [path]").description("publish local skills to the Library").option("--yes", "proceed with all safe changes").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without changing anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty").option("--no-secret-check", "skip the pre-publish secret scan").option("--concurrency <n>", "bulk push concurrency, 1-16", "6").action((path, opts) => pushCommand(path, opts));
7942
- program.command("pull [skill]").description("update local agents from the Library").option("--agent <name>", "limit to one or more agents (repeatable, comma-separated)", collectAgent, []).option("--target <name>", "alias for --agent", collectAgent, []).option("--all-agents", "use every detected agent").option("--global", "use the user-level agent skills folder").option("--project", "use this project's agent skills folder").option("--all-scopes", "use both global and project-local copies").option("--yes", "skip confirmation for safe mutations").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without changing anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty").action((skill, opts) => {
8063
+ helpOpt(program.command("push [path]").description("publish local skills to the Library").option("--yes", "proceed with all safe changes").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without changing anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty").option("--no-secret-check", "skip the pre-publish secret scan").option("--concurrency <n>", "bulk push concurrency, 1-16", "6")).action((path, opts) => pushCommand(path, opts));
8064
+ helpOpt(program.command("pull [skill]").description("update local agents from the Library").option("--agent <name>", "limit to one or more agents (repeatable, comma-separated)", collectAgent, []).option("--target <name>", "alias for --agent", collectAgent, []).option("--all-agents", "use every detected agent").option("--global", "use the user-level agent skills folder").option("--global-only", "(deprecated) alias for --global").option("--project", "use this project's agent skills folder").option("--all-scopes", "use both global and project-local copies").option("--yes", "skip confirmation for safe mutations").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without changing anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty")).action((skill, opts) => {
7943
8065
  if (Array.isArray(opts.target) && opts.target.length > 0) aliasNotice("--target", "--agent");
8066
+ if (opts.globalOnly) {
8067
+ aliasNotice("--global-only", "--global");
8068
+ opts.global = true;
8069
+ }
7944
8070
  return pullCommand(skill, opts);
7945
8071
  });
7946
- program.command("sync").description("review both directions and resolve differences").option("--agent <name>", "limit to one or more agents (repeatable, comma-separated)", collectAgent, []).option("--target <name>", "alias for --agent", collectAgent, []).option("--all-agents", "use every detected agent").option("--global", "use the user-level agent skills folder").option("--project", "use this project's agent skills folder").option("--all-scopes", "use both global and project-local copies").option("--yes", "skip confirmation for safe mutations").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without changing anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty").action((opts) => {
8072
+ helpOpt(program.command("sync").description("review both directions and resolve differences").option("--agent <name>", "limit to one or more agents (repeatable, comma-separated)", collectAgent, []).option("--target <name>", "alias for --agent", collectAgent, []).option("--all-agents", "use every detected agent").option("--global", "use the user-level agent skills folder").option("--project", "use this project's agent skills folder").option("--all-scopes", "use both global and project-local copies").option("--yes", "skip confirmation for safe mutations").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without changing anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty")).action((opts) => {
7947
8073
  if (Array.isArray(opts.target) && opts.target.length > 0) aliasNotice("--target", "--agent");
7948
8074
  return syncCommand(opts);
7949
8075
  });
7950
- program.command("new <name>").description("create a ready-to-edit skill").option("--agent <name>", "create in this agent's skills folder").option("--all-scopes", "(rejected \u2014 new creates one skill in one location)").option("--yes", "skip confirmation when non-interactive").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without creating anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty").action((name, opts) => newCommand(name, opts));
7951
- program.command("restore [skill]").description("restore a local skill from a safety backup").option("--agent <name>", "the agent whose copy to restore").option("--backup <timestamp>", "restore a specific backup snapshot").option("--list", "list all available backups").option("--yes", "skip confirmation").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without restoring anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty").action((skill, opts) => restoreCommand(skill, opts));
7952
- program.command("list").description("list team skills and local install status").option("--json", "print the Library as JSON").action((opts) => listCommand(opts));
7953
- program.command("library").description("list team skills and local install status (alias)").option("--json", "print the Library as JSON").action((opts) => listCommand(opts));
7954
- program.command("delete [skill]").description("delete a skill from the Library").option("--yes", "skip the type-the-name confirmation").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without deleting anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty").action((skill, opts) => deleteCommand(skill, opts));
7955
- program.command("remove [skill]").description("delete a skill from the Library (alias)").option("--yes", "skip the type-the-name confirmation").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without deleting anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty").action((skill, opts) => {
8076
+ helpOpt(program.command("new <name>").description("create a ready-to-edit skill").option("--agent <name>", "create in this agent's skills folder").option("--all-scopes", "(rejected \u2014 new creates one skill in one location)").option("--yes", "skip confirmation when non-interactive").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without creating anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty")).action((name, opts) => newCommand(name, opts));
8077
+ helpOpt(program.command("restore [skill]").description("restore a local skill from a safety backup").option("--agent <name>", "the agent whose copy to restore").option("--backup <timestamp>", "restore a specific backup snapshot").option("--list", "list all available backups").option("--yes", "skip confirmation").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without restoring anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty")).action((skill, opts) => restoreCommand(skill, opts));
8078
+ helpOpt(program.command("list").description("list team skills and local install status").option("--json", "print the Library as JSON")).action((opts) => listCommand(opts));
8079
+ helpOpt(program.command("library").description("list team skills and local install status (alias)").option("--json", "print the Library as JSON")).action((opts) => listCommand(opts));
8080
+ helpOpt(program.command("delete [skill]").description("delete a skill from the Library").option("--yes", "skip the type-the-name confirmation").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without deleting anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty")).action((skill, opts) => deleteCommand(skill, opts));
8081
+ helpOpt(program.command("remove [skill]").description("delete a skill from the Library (alias)").option("--yes", "skip the type-the-name confirmation").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without deleting anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty")).action((skill, opts) => {
7956
8082
  aliasNotice("remove", "delete");
7957
8083
  return deleteCommand(skill, opts);
7958
8084
  });
7959
- program.command("install <link>").description("install a skill from a share link").option("--agent <name>", "install into one or more agents (repeatable, comma-separated)", collectAgent, []).option("--target <name>", "alias for --agent", collectAgent, []).option("--all-agents", "install into every detected agent").option("--global", "install into the user-level agent skills folder").option("--project", "install into this project's agent skills folder").option("--all-scopes", "install into both global and project-local copies").option("--yes", "skip confirmation for safe mutations").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without installing anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty").action((link, opts) => installCommand(link, opts));
7960
- program.command("add <link>").description("install a skill from a share link (alias)").option("--agent <name>", "install into one or more agents", collectAgent, []).option("--target <name>", "alias for --agent", collectAgent, []).option("--all-agents", "install into every detected agent").option("--global", "install into the user-level agent skills folder").option("--project", "install into this project's agent skills folder").option("--all-scopes", "install into both global and project-local copies").option("--yes", "skip confirmation for safe mutations").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without installing anything").action((link, opts) => {
8085
+ helpOpt(program.command("install <link>").description("install a skill from a share link").option("--agent <name>", "install into one or more agents (repeatable, comma-separated)", collectAgent, []).option("--target <name>", "alias for --agent", collectAgent, []).option("--all-agents", "install into every detected agent").option("--global", "install into the user-level agent skills folder").option("--project", "install into this project's agent skills folder").option("--all-scopes", "install into both global and project-local copies").option("--yes", "skip confirmation for safe mutations").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without installing anything").option("--exit-zero", "with --dry-run, force exit 0 even if the plan is non-empty")).action((link, opts) => installCommand(link, opts));
8086
+ helpOpt(program.command("add <link>").description("install a skill from a share link (alias)").option("--agent <name>", "install into one or more agents", collectAgent, []).option("--target <name>", "alias for --agent", collectAgent, []).option("--all-agents", "install into every detected agent").option("--global", "install into the user-level agent skills folder").option("--project", "install into this project's agent skills folder").option("--all-scopes", "install into both global and project-local copies").option("--yes", "skip confirmation for safe mutations").option("--json", "print the plan or result as JSON").option("--dry-run", "print the plan without installing anything")).action((link, opts) => {
7961
8087
  aliasNotice("add", "install");
7962
8088
  return installCommand(link, opts);
7963
8089
  });
7964
- program.command("rename-device <label>").description("set a friendly name for this machine").action((label) => renameDeviceCommand(label));
7965
- program.command("rename-machine <label>").description("set a friendly name for this machine (alias)").action((label) => {
8090
+ helpOpt(program.command("rename-device <label>").description("set a friendly name for this machine")).action((label) => renameDeviceCommand(label));
8091
+ helpOpt(program.command("rename-machine <label>").description("set a friendly name for this machine (alias)")).action((label) => {
7966
8092
  aliasNotice("rename-machine", "rename-device");
7967
8093
  return renameDeviceCommand(label);
7968
8094
  });
7969
- program.command("status").description("inspect sync state across Library and agents").option("--agent <name>", "limit to one or more agents (repeatable, comma-separated)", collectAgent, []).option("--target <name>", "alias for --agent", collectAgent, []).option("--verbose", "show every skill in every agent").option("--json", "print status as JSON").action((opts) => {
8095
+ helpOpt(program.command("status").description("inspect sync state across Library and agents").option("--agent <name>", "limit to one or more agents (repeatable, comma-separated)", collectAgent, []).option("--target <name>", "alias for --agent", collectAgent, []).option("--verbose", "show every skill in every agent").option("--json", "print status as JSON")).action((opts) => {
7970
8096
  if (Array.isArray(opts.target) && opts.target.length > 0) aliasNotice("--target", "--agent");
7971
8097
  return statusCommand(opts);
7972
8098
  });
7973
- program.command("diff <skill>").description("compare local copies with the Library").option("--agent <name>", "limit to one agent").option("--json", "print the comparison as JSON").action((skill, opts) => diffCommand(skill, opts));
7974
- program.command("doctor").description("diagnose auth, agents, manifests, and version").option("--json", "print diagnostics as JSON").action((opts) => doctorCommand(opts));
7975
- program.command("mcp").description("run the local MCP server (for agent configuration)").action(mcpCommand);
8099
+ helpOpt(program.command("diff <skill>").description("compare local copies with the Library").option("--agent <name>", "limit to one agent").option("--json", "print the comparison as JSON")).action((skill, opts) => diffCommand(skill, opts));
8100
+ helpOpt(program.command("doctor").description("diagnose auth, agents, manifests, and version").option("--json", "print diagnostics as JSON")).action((opts) => doctorCommand(opts));
8101
+ helpOpt(program.command("mcp").description("run the local MCP server (for agent configuration)")).action(mcpCommand);
7976
8102
  function collectAgent(value, previous) {
7977
8103
  return [...previous, value];
7978
8104
  }
7979
8105
  async function main() {
8106
+ installPipeGuards();
7980
8107
  detectJsonEarly();
7981
8108
  try {
7982
8109
  await program.parseAsync(process.argv);
@@ -7985,6 +8112,10 @@ async function main() {
7985
8112
  log.err(e.message);
7986
8113
  process.exit(1);
7987
8114
  }
8115
+ const cerr = e;
8116
+ if (cerr && typeof cerr.code === "string" && cerr.code.startsWith("commander.")) {
8117
+ process.exit(typeof cerr.exitCode === "number" ? cerr.exitCode : 1);
8118
+ }
7988
8119
  log.err(e.message ?? "Unknown error");
7989
8120
  if (process.env.FLOOM_DEBUG) {
7990
8121
  const raw = e?.stack ?? String(e);
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "3.0.0";
1
+ export const VERSION = "3.0.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floomhq/floom",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "Floom CLI \u2014 one shared skill library, pulled into the AI agent you choose (Claude, Codex, Cursor, Gemini, OpenCode).",
5
5
  "license": "MIT",
6
6
  "homepage": "https://floom.dev",