@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 +191 -60
- package/dist/version.js +1 -1
- package/package.json +1 -1
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.
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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({
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
5864
|
-
log.info("Floom
|
|
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 &&
|
|
5940
|
+
if (!planMode && !flags.yes && !isInteractive()) {
|
|
5871
5941
|
if (json) {
|
|
5872
|
-
emitJson({ workspace: { name: "Library", signedIn: true }, error: "floom sync needs to
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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(
|
|
7052
|
-
const a =
|
|
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(
|
|
7926
|
-
program.
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
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
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
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.
|
|
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.
|
|
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",
|