@floomhq/floom 1.0.4 → 1.0.6
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/README.md +2 -0
- package/dist/cli.js +94 -32
- package/dist/install.js +8 -2
- package/dist/ui.js +3 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ npm install -g @floomhq/floom
|
|
|
7
7
|
floom init my-skill.md
|
|
8
8
|
floom login
|
|
9
9
|
floom publish my-skill.md
|
|
10
|
+
floom share my-skill --add teammate@example.com
|
|
10
11
|
floom search review
|
|
11
12
|
floom add awesome-skill
|
|
12
13
|
floom setup --target claude --dry-run
|
|
@@ -21,6 +22,7 @@ Returns a shareable link like `https://floom.dev/s/ffas93ud`. Anyone with the UR
|
|
|
21
22
|
- `floom login` — sign in with Google. New accounts are created on first login. Token stored at `~/.floom/config.json`.
|
|
22
23
|
- `floom init [file.md]` — create a starter skill file.
|
|
23
24
|
- `floom publish <file.md>` — upload a Markdown file. Optional `--public` / `--private` / `--unlisted`, `--type knowledge|instruction|workflow|skill`, `--installs-as <target>`, and `--version <label>`.
|
|
25
|
+
- `floom share <slug>` — email-share one of your skills. Optional `--add <email>`, `--remove <email>`, and `--list`.
|
|
24
26
|
- `floom list` — show your published skills. Optional `--json`.
|
|
25
27
|
- `floom add <url-or-slug>` — fetch a skill into `~/.claude/skills/<slug>.md`.
|
|
26
28
|
- `floom info <url-or-slug>` — show skill metadata. Optional `--json`.
|
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import { printMcpSetup } from "./mcp.js";
|
|
|
15
15
|
import { setupAgent } from "./setup.js";
|
|
16
16
|
import { search } from "./search.js";
|
|
17
17
|
import { scanSkill } from "./scan.js";
|
|
18
|
+
import { share } from "./share.js";
|
|
18
19
|
import { libraryAddSkill, libraryCreate, libraryList, libraryRemoveSkill, librarySubscribe, libraryUnsubscribe, moveSkill, } from "./library.js";
|
|
19
20
|
import { c, symbols } from "./ui.js";
|
|
20
21
|
import { printError, FloomError } from "./errors.js";
|
|
@@ -23,37 +24,40 @@ const PKG = { name: "@floomhq/floom", version: CLI_VERSION };
|
|
|
23
24
|
const V1_NOT_AVAILABLE = "Not available in Floom Version 1.";
|
|
24
25
|
function usage() {
|
|
25
26
|
const out = `
|
|
26
|
-
${c.
|
|
27
|
-
${c.
|
|
28
|
-
${c.
|
|
29
|
-
${c.
|
|
30
|
-
${c.
|
|
27
|
+
${c.blue(" ________ ")}
|
|
28
|
+
${c.blue(" / ____/ /___ ____ ____ ___ ")} ${c.dim(`v${CLI_VERSION}`)}
|
|
29
|
+
${c.blue("/ /_ / / __ \\/ __ \\/ __ `__ \\ ")}
|
|
30
|
+
${c.blue("/ __/ / / /_/ / /_/ / / / / / / ")}
|
|
31
|
+
${c.blue("/_/ /_/\\____/\\____/_/ /_/ /_/ ")}
|
|
31
32
|
|
|
32
|
-
${c.bold("
|
|
33
|
-
${c.dim("
|
|
33
|
+
${c.bold("Floom lets you share AI workflows with anyone.")}
|
|
34
|
+
${c.dim("A skill is reusable knowledge, instructions, or a workflow for your AI agent.")}
|
|
35
|
+
${c.dim("Examples: brand voice, PR review checklist, sales research workflow.")}
|
|
34
36
|
|
|
35
|
-
${c.bold("
|
|
36
|
-
${c.dim("1. Add a skill someone sent you")}
|
|
37
|
-
${c.cyan("npx -y @floomhq/floom add")} ${c.dim("https://floom.dev/s/ffas93ud --target claude")}
|
|
37
|
+
${c.bold("You installed Floom. Copy one recipe:")}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
${c.cyan("npx -y @floomhq/floom login")}
|
|
44
|
-
${c.cyan("npx -y @floomhq/floom publish")} ${c.dim("support-tone.md --type instruction --public")}
|
|
39
|
+
${c.bold("1. I received a Floom link")}
|
|
40
|
+
${c.dim("Replace <skill-link> with the full Floom URL someone sent you:")}
|
|
41
|
+
${c.cyan("npx -y @floomhq/floom add")} ${c.dim("<skill-link> --setup")}
|
|
42
|
+
${c.dim('Then tell Claude Code: "Use my Floom skills when they fit this task."')}
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
${c.bold("2. I want to make a share link")}
|
|
45
|
+
${c.cyan("npx -y @floomhq/floom init")} ${c.dim("my-skill.md")}
|
|
46
|
+
${c.dim("Write what your agent needs to know or do in my-skill.md.")}
|
|
47
|
+
${c.cyan("npx -y @floomhq/floom login")}
|
|
48
|
+
${c.cyan("npx -y @floomhq/floom publish")} ${c.dim("my-skill.md --public")}
|
|
49
|
+
${c.dim("Floom scans it, prints a link, and copies the link when possible.")}
|
|
49
50
|
|
|
50
|
-
${c.bold("
|
|
51
|
-
|
|
51
|
+
${c.bold("Good to know")}
|
|
52
|
+
${symbols.ok} ${c.dim("No account is needed to add a shared skill.")}
|
|
53
|
+
${symbols.ok} ${c.dim("Sign in only when you publish or manage your skills.")}
|
|
54
|
+
${symbols.ok} ${c.dim("Every command prints success or the exact problem to fix.")}
|
|
52
55
|
|
|
53
|
-
${c.bold("
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
${c.bold("Stuck?")}
|
|
57
|
+
${c.cyan("npx -y @floomhq/floom doctor")} ${c.dim("Find the problem")}
|
|
58
|
+
${c.cyan("npx -y @floomhq/floom scan my-skill.md")} ${c.dim("Check a file before publishing")}
|
|
59
|
+
${c.cyan("npx -y @floomhq/floom commands")} ${c.dim("See every command")}
|
|
60
|
+
${c.dim("Step-by-step guide")} https://floom.dev/docs/getting-started
|
|
57
61
|
`;
|
|
58
62
|
process.stdout.write(out);
|
|
59
63
|
}
|
|
@@ -65,7 +69,7 @@ function commandUsage() {
|
|
|
65
69
|
${c.dim("Skills")}
|
|
66
70
|
${c.cyan("add")} ${c.dim("<url>")} Install a skill into the local agent skills folder
|
|
67
71
|
${c.dim("Alias: install")}
|
|
68
|
-
${c.dim("Flags: --target claude|codex (default: claude)")}
|
|
72
|
+
${c.dim("Flags: --target claude|codex (default: claude), --setup")}
|
|
69
73
|
${c.cyan("search")} ${c.dim("<query>")} Find public skills and libraries
|
|
70
74
|
${c.cyan("info")} ${c.dim("<url>")} Show skill metadata
|
|
71
75
|
|
|
@@ -75,6 +79,8 @@ function commandUsage() {
|
|
|
75
79
|
${c.cyan("publish")} ${c.dim("<path>")} Scan, publish, and print a share link
|
|
76
80
|
${c.dim("Flags: --public, --private, --type knowledge|instruction|workflow|skill")}
|
|
77
81
|
${c.dim(" --skill-version <label>")}
|
|
82
|
+
${c.cyan("share")} ${c.dim("<slug>")} Email-share one of your skills
|
|
83
|
+
${c.dim("Flags: --add <email>, --remove <email>, --list")}
|
|
78
84
|
|
|
79
85
|
${c.dim("Account")}
|
|
80
86
|
${c.cyan("login")} Authenticate
|
|
@@ -184,6 +190,43 @@ function parseFlags(argv) {
|
|
|
184
190
|
}
|
|
185
191
|
return out;
|
|
186
192
|
}
|
|
193
|
+
function parseShareFlags(argv) {
|
|
194
|
+
const out = { list: false, add: [], remove: [] };
|
|
195
|
+
for (let i = 0; i < argv.length; i++) {
|
|
196
|
+
const a = argv[i] ?? "";
|
|
197
|
+
if (a === "--list") {
|
|
198
|
+
out.list = true;
|
|
199
|
+
}
|
|
200
|
+
else if (a === "--add" || a.startsWith("--add=")) {
|
|
201
|
+
const { value, nextIndex } = readFlagValue(argv, i, "--add");
|
|
202
|
+
out.add.push(value);
|
|
203
|
+
i = nextIndex;
|
|
204
|
+
}
|
|
205
|
+
else if (a === "--remove" || a.startsWith("--remove=")) {
|
|
206
|
+
const { value, nextIndex } = readFlagValue(argv, i, "--remove");
|
|
207
|
+
out.remove.push(value);
|
|
208
|
+
i = nextIndex;
|
|
209
|
+
}
|
|
210
|
+
else if (a.startsWith("--")) {
|
|
211
|
+
throw new FloomError(`Unknown flag: ${a}`, "Try `floom share <slug> --add person@example.com`.");
|
|
212
|
+
}
|
|
213
|
+
else if (!out.slug) {
|
|
214
|
+
out.slug = a;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
throw new FloomError(`Unexpected argument: ${a}`, "Try `floom share <slug> --add person@example.com`.");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (!out.slug)
|
|
221
|
+
throw new FloomError("Missing skill slug.", "Try `floom share <slug> --add person@example.com`.");
|
|
222
|
+
if (out.list && (out.add.length > 0 || out.remove.length > 0)) {
|
|
223
|
+
throw new FloomError("Conflicting share flags.", "Use --list by itself, or use --add/--remove.");
|
|
224
|
+
}
|
|
225
|
+
if (!out.list && out.add.length === 0 && out.remove.length === 0) {
|
|
226
|
+
throw new FloomError("Missing share action.", "Try `floom share <slug> --add person@example.com`.");
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
187
230
|
function parseInitArgs(argv) {
|
|
188
231
|
let file;
|
|
189
232
|
for (const a of argv) {
|
|
@@ -228,6 +271,7 @@ function parseInfoFlags(argv) {
|
|
|
228
271
|
function parseAddArgs(argv) {
|
|
229
272
|
let slug;
|
|
230
273
|
let target;
|
|
274
|
+
let setup = false;
|
|
231
275
|
for (let i = 0; i < argv.length; i++) {
|
|
232
276
|
const a = argv[i] ?? "";
|
|
233
277
|
if (a === "--target" || a.startsWith("--target=")) {
|
|
@@ -238,19 +282,22 @@ function parseAddArgs(argv) {
|
|
|
238
282
|
target = value;
|
|
239
283
|
i = nextIndex;
|
|
240
284
|
}
|
|
285
|
+
else if (a === "--setup") {
|
|
286
|
+
setup = true;
|
|
287
|
+
}
|
|
241
288
|
else if (a.startsWith("--")) {
|
|
242
|
-
throw new FloomError(`Unknown flag: ${a}`, "Try `floom add <url-or-slug> --
|
|
289
|
+
throw new FloomError(`Unknown flag: ${a}`, "Try `floom add <url-or-slug> --setup`.");
|
|
243
290
|
}
|
|
244
291
|
else if (slug) {
|
|
245
|
-
throw new FloomError(`Unexpected argument: ${a}`, "Try `floom add <url-or-slug> --
|
|
292
|
+
throw new FloomError(`Unexpected argument: ${a}`, "Try `floom add <url-or-slug> --setup`.");
|
|
246
293
|
}
|
|
247
294
|
else
|
|
248
295
|
slug = a;
|
|
249
296
|
}
|
|
250
297
|
if (!slug) {
|
|
251
|
-
throw new FloomError("Missing skill slug.", "Try: `floom add <url-or-slug> --
|
|
298
|
+
throw new FloomError("Missing skill slug.", "Try: `floom add <url-or-slug> --setup`");
|
|
252
299
|
}
|
|
253
|
-
return target ? { slug, target } : { slug };
|
|
300
|
+
return target ? { slug, target, setup } : { slug, setup };
|
|
254
301
|
}
|
|
255
302
|
function parseSearchFlags(argv) {
|
|
256
303
|
const out = { json: false };
|
|
@@ -625,7 +672,16 @@ async function main() {
|
|
|
625
672
|
return;
|
|
626
673
|
}
|
|
627
674
|
case "share":
|
|
628
|
-
|
|
675
|
+
{
|
|
676
|
+
const flags = parseShareFlags(rest);
|
|
677
|
+
if (flags.list) {
|
|
678
|
+
await share({ slug: flags.slug ?? "", kind: "list" });
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
await share({ slug: flags.slug ?? "", kind: "patch", add: flags.add, remove: flags.remove });
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return;
|
|
629
685
|
case "list": {
|
|
630
686
|
const flags = parseListFlags(rest);
|
|
631
687
|
await list(flags);
|
|
@@ -653,7 +709,13 @@ async function main() {
|
|
|
653
709
|
case "add":
|
|
654
710
|
case "install": {
|
|
655
711
|
const flags = parseAddArgs(rest);
|
|
656
|
-
await install(flags.slug,
|
|
712
|
+
await install(flags.slug, {
|
|
713
|
+
...(flags.target ? { target: flags.target } : {}),
|
|
714
|
+
setup: flags.setup,
|
|
715
|
+
});
|
|
716
|
+
if (flags.setup) {
|
|
717
|
+
await setupAgent({ target: flags.target ?? "claude", dryRun: false, yes: true });
|
|
718
|
+
}
|
|
657
719
|
return;
|
|
658
720
|
}
|
|
659
721
|
case "sync":
|
package/dist/install.js
CHANGED
|
@@ -201,6 +201,12 @@ export async function install(slugInput, opts = {}) {
|
|
|
201
201
|
process.stdout.write(`\n${symbols.ok} [floom] ${action} ${c.bold(slug)}\n`);
|
|
202
202
|
process.stdout.write(` ${c.dim(target)}\n\n`);
|
|
203
203
|
process.stdout.write(` ${c.bold("Next")}\n`);
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
if (opts.setup) {
|
|
205
|
+
process.stdout.write(` ${c.dim("1.")} Floom is connecting ${targetAgent === "claude" ? "Claude Code" : "Codex"} now.\n`);
|
|
206
|
+
process.stdout.write(` ${c.dim("2.")} Tell your agent to use ${c.bold(slug)} when it matches the task.\n\n`);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
process.stdout.write(` ${c.dim("1.")} Tell your agent to use ${c.bold(slug)} when it matches the task.\n`);
|
|
210
|
+
process.stdout.write(` ${c.dim("2.")} One-time setup: ${c.cyan(setupCommand(targetAgent))}\n\n`);
|
|
211
|
+
}
|
|
206
212
|
}
|
package/dist/ui.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import pc from "picocolors";
|
|
2
|
-
//
|
|
3
|
-
// picocolors only supports ANSI named colors, so we map:
|
|
4
|
-
// - coral / primary action: yellow (warm) for the dot, red for emphasis when needed
|
|
5
|
-
// - success: green
|
|
6
|
-
// - muted: gray (dim)
|
|
2
|
+
// Cool, restrained terminal palette. Keep orange out of the default CLI surface.
|
|
7
3
|
const isTty = process.stdout.isTTY === true;
|
|
8
4
|
export const c = {
|
|
9
|
-
coral: (s) => (isTty ? `\x1b[38;5;
|
|
5
|
+
coral: (s) => (isTty ? `\x1b[38;5;45m${s}\x1b[0m` : s),
|
|
6
|
+
blue: (s) => (isTty ? `\x1b[38;5;75m${s}\x1b[0m` : s),
|
|
10
7
|
teal: (s) => (isTty ? `\x1b[38;5;73m${s}\x1b[0m` : s),
|
|
11
8
|
green: pc.green,
|
|
12
9
|
red: pc.red,
|