@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 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.coral(" ________")}
27
- ${c.coral(" / ____/ /___ ____ ____ ___")} ${c.dim(`v${CLI_VERSION}`)}
28
- ${c.coral(" / /_ / / __ \\/ __ \\/ __ `__ \\")}
29
- ${c.coral(" / __/ / / /_/ / /_/ / / / / / /")}
30
- ${c.coral(" /_/ /_/\\____/\\____/_/ /_/ /_/")}
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("Share AI agent skills with a link.")}
33
- ${c.dim("Publish knowledge, instructions, and workflows from your terminal.")}
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("Do this next")}
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
- ${c.dim("2. Publish your own skill")}
40
- ${c.cyan("npx -y @floomhq/floom init")} ${c.dim("support-tone.md")}
41
- ${c.dim("# edit support-tone.md")}
42
- ${c.cyan("npx -y @floomhq/floom scan")} ${c.dim("support-tone.md")}
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
- ${c.dim("3. Tell your agent where skills land")}
47
- ${c.cyan("npx -y @floomhq/floom setup")} ${c.dim("--target claude --yes")}
48
- ${c.cyan("npx -y @floomhq/floom setup")} ${c.dim("--target codex --yes")}
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("Safety")}
51
- ${c.yellow("!")} ${c.dim("publish scans for API keys, prompt injection, and exfiltration.")}
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("More")}
54
- ${c.cyan("floom commands")} ${c.dim("Full command list")}
55
- ${c.cyan("floom doctor")} ${c.dim("Check auth, API, and local folders")}
56
- ${c.dim("Docs")} https://floom.dev
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> --target claude`.");
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> --target claude`.");
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> --target claude`");
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
- notAvailable("`floom share`");
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, flags.target ? { target: flags.target } : {});
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
- process.stdout.write(` ${c.dim("1.")} Tell your agent to use ${c.bold(slug)} when it matches the task.\n`);
205
- process.stdout.write(` ${c.dim("2.")} One-time setup: ${c.cyan(setupCommand(targetAgent))}\n\n`);
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
- // Coral / teal palette to match the warm Lovable vibe.
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;209m${s}\x1b[0m` : s),
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floomhq/floom",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Publish AI skills from your terminal. Share with a link.",
5
5
  "license": "MIT",
6
6
  "type": "module",