@floomhq/floom 1.0.7 → 1.0.9

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
@@ -4,11 +4,13 @@ Publish AI skills from your terminal. Share them with a link. Add other people's
4
4
 
5
5
  ```bash
6
6
  npx -y @floomhq/floom init my-skill.md
7
+ npx -y @floomhq/floom init brand-voice.md --template brand-voice
7
8
  npx -y @floomhq/floom login
8
- npx -y @floomhq/floom publish my-skill.md
9
+ npx -y @floomhq/floom publish my-skill.md --share teammate@example.com
9
10
  npx -y @floomhq/floom share my-skill --add teammate@example.com
10
11
  npx -y @floomhq/floom search review
11
12
  npx -y @floomhq/floom add awesome-skill --setup
13
+ npx -y @floomhq/floom agent-prompt
12
14
  npx -y @floomhq/floom setup --target claude --dry-run
13
15
  npx -y @floomhq/floom list
14
16
  npx -y @floomhq/floom library list
@@ -21,31 +23,33 @@ The package is designed for `npx -y @floomhq/floom ...`. Global installs expose
21
23
  ## Commands
22
24
 
23
25
  - `npx -y @floomhq/floom login` — sign in with Google. New accounts are created on first login. Token stored at `~/.floom/config.json`.
24
- - `floom init [file.md]` — create a starter skill file.
25
- - `npx -y @floomhq/floom publish <file.md>` — upload a Markdown file. Optional `--public` / `--private` / `--unlisted`, `--type knowledge|instruction|workflow|skill`, `--installs-as <target>`, and `--skill-version <label>`.
26
- - `floom share <slug>` — email-share one of your skills. Optional `--add <email>`, `--remove <email>`, and `--list`.
27
- - `floom list` — show your published skills. Optional `--json`.
28
- - `npx -y @floomhq/floom add <url-or-slug>` — fetch a skill into `~/.claude/skills/<slug>.md`. Optional `--setup` connects Claude Code and `--force` replaces an existing local copy.
29
- - `floom info <url-or-slug>` — show skill metadata. Optional `--json`.
30
- - `floom search <query>` — search public skills and starter libraries. Optional `--library <slug>`, `--type knowledge|instruction|workflow|skill`, and `--json`.
31
- - `floom setup`add Floom usage guidance to `CLAUDE.md` or `AGENTS.md`. Optional `--target claude|codex`, `--dry-run`, `--yes`.
32
- - `floom connect` — alias for `floom setup`.
33
- - `floom mcp` — print MCP setup commands for supported agent CLIs.
34
- - `floom sync` — preview: pull your published, saved, and subscribed library skills into `~/.claude/skills/`.
35
- - `floom watch` — preview: run `floom sync` repeatedly. Optional `--interval <seconds>`; minimum `10`.
36
- - `floom library list` — list public starter libraries. Optional `--json`.
37
- - `floom library create <slug> --name <name>` create a personal or starter library. Optional `--public` / `--private` / `--unlisted`.
38
- - `floom library add <library> <skill> [--folder <path>] [--tags a,b]` — add a skill to a library.
39
- - `floom library subscribe <slug>` — subscribe to a public or unlisted library so sync can pull it locally.
40
- - `floom move <slug> --folder <path>` set your local folder override for a saved or library skill.
41
- - `floom delete <url-or-slug>` — delete one of your published skills. Optional `--yes`.
42
- - `npx -y @floomhq/floom doctor`diagnose your Floom setup.
43
- - `floom whoami`show the signed-in account.
44
- - `floom logout` — delete local credentials.
26
+ - `npx -y @floomhq/floom init [file.md]` — create a starter skill file. Optional `--template generic|brand-voice|pr-review|sales|support|onboarding`.
27
+ - `npx -y @floomhq/floom publish <file.md>` — scan and upload a Markdown file. Optional `--public` / `--private` / `--unlisted`, `--type knowledge|instruction|workflow|skill`, `--installs-as <target>`, `--skill-version <label>`, and `--share <email>`. `--share` sends the normal link by email; no account is needed to add unlisted or public links.
28
+ - `npx -y @floomhq/floom share <slug>` — email-share one of your skills. Optional `--add <email>`, `--remove <email>`, and `--list`.
29
+ - `npx -y @floomhq/floom list` — show your published skills. Optional `--json`.
30
+ - `npx -y @floomhq/floom add <url-or-slug>` — fetch a skill into `~/.claude/skills/<slug>.md`. Optional `--setup` connects Claude Code, `--force` / `--update` replaces an existing local copy, and `--json` prints machine-readable output.
31
+ - `npx -y @floomhq/floom update <url-or-slug>` — install the latest remote skill content, replacing the local copy.
32
+ - `npx -y @floomhq/floom info <url-or-slug>` — show skill metadata. Optional `--json`.
33
+ - `npx -y @floomhq/floom search <query>` search public skills and starter libraries. Optional `--library <slug>`, `--type knowledge|instruction|workflow|skill`, and `--json`.
34
+ - `npx -y @floomhq/floom agent-prompt` — print the sentence to paste into Claude Code or Codex.
35
+ - `npx -y @floomhq/floom setup` — add Floom usage guidance to `CLAUDE.md` or `AGENTS.md`. Optional `--target claude|codex`, `--dry-run`, `--yes`, `--file <path>`.
36
+ - `npx -y @floomhq/floom connect` — alias for setup.
37
+ - `npx -y @floomhq/floom mcp` — print MCP setup commands for supported agent CLIs.
38
+ - `npx -y @floomhq/floom sync` — preview: pull your published, saved, and subscribed library skills into `~/.claude/skills/`.
39
+ - `npx -y @floomhq/floom watch`preview: run sync repeatedly. Optional `--interval <seconds>`; minimum `10`.
40
+ - `npx -y @floomhq/floom library list` — list public starter libraries. Optional `--json`.
41
+ - `npx -y @floomhq/floom library create <slug> --name <name>` — create a personal or starter library. Optional `--public` / `--private` / `--unlisted`.
42
+ - `npx -y @floomhq/floom library add <library> <skill> [--folder <path>] [--tags a,b]` add a skill to a library.
43
+ - `npx -y @floomhq/floom library subscribe <slug>` — subscribe to a public or unlisted library so sync can pull it locally.
44
+ - `npx -y @floomhq/floom move <slug> --folder <path>` set your local folder override for a saved or library skill.
45
+ - `npx -y @floomhq/floom delete <url-or-slug>` delete one of your published skills. Optional `--yes`.
46
+ - `npx -y @floomhq/floom doctor` — diagnose your Floom setup. Optional `--json`.
47
+ - `npx -y @floomhq/floom whoami` — show the signed-in account.
48
+ - `npx -y @floomhq/floom logout` — delete local credentials.
45
49
 
46
50
  ## Skill format
47
51
 
48
- Optional YAML-ish frontmatter (`title`, `description`, `version`), then freeform Markdown.
52
+ Optional YAML frontmatter (`title`, `description`, `version`, `type`, `installs_as`), then freeform Markdown.
49
53
 
50
54
  ```markdown
51
55
  ---
@@ -62,7 +66,7 @@ version: 0.1.0
62
66
 
63
67
  Override the API host with `FLOOM_API_URL` (defaults to `https://floom.dev`).
64
68
 
65
- `floom sync` and `floom watch` are Version 1 preview commands for published, saved, and subscribed library skills. They store a machine-local manifest at `~/.floom/sync-manifest.json`.
69
+ `npx -y @floomhq/floom sync` and `npx -y @floomhq/floom watch` are Version 1 preview commands for published, saved, and subscribed library skills. They store a machine-local manifest at `~/.floom/sync-manifest.json`.
66
70
  The manifest records hashes for files Floom previously wrote. Version 1 sync writes missing files
67
71
  only. Remote updates, existing untracked files, and locally edited tracked files are skipped as
68
72
  conflicts. Symlinks are never followed. To replace a local skill manually, run
package/dist/cli.js CHANGED
@@ -63,23 +63,27 @@ function usage() {
63
63
  }
64
64
  function commandUsage() {
65
65
  const out = `
66
- ${c.bold("Usage:")} ${c.cyan("npx -y @floomhq/floom")} ${c.dim("<command> [flags]")}
67
- ${c.dim("Global install binary:")} ${c.cyan("floom-skills")} ${c.dim("<command> [flags]")}
66
+ ${c.bold("Usage:")} ${c.cyan("npx -y @floomhq/floom")} ${c.dim("<command> [args]")}
67
+ ${c.dim("Global install binary:")} ${c.cyan("floom-skills")} ${c.dim("<command> [args]")}
68
68
 
69
69
  ${c.bold("Commands")}
70
70
  ${c.dim("Skills")}
71
71
  ${c.cyan("add")} ${c.dim("<url>")} Install a skill into the local agent skills folder
72
72
  ${c.dim("Alias: install")}
73
- ${c.dim("Flags: --target claude|codex (default: claude), --setup, --force")}
73
+ ${c.dim("Flags: --target claude|codex (default: claude), --setup, --force, --update, --json")}
74
+ ${c.cyan("update")} ${c.dim("<url>")} Install the latest remote skill content, replacing the local copy
75
+ ${c.dim("Alias: add --force")}
74
76
  ${c.cyan("search")} ${c.dim("<query>")} Find public skills and libraries
75
77
  ${c.cyan("info")} ${c.dim("<url>")} Show skill metadata
76
78
 
77
79
  ${c.dim("Publishing")}
78
80
  ${c.cyan("init")} ${c.dim("[path]")} Create a skill scaffold
81
+ ${c.dim("Flags: --template generic|brand-voice|pr-review|sales|support|onboarding")}
79
82
  ${c.cyan("scan")} ${c.dim("<path>")} Check for secrets, injection, exfiltration
80
83
  ${c.cyan("publish")} ${c.dim("<path>")} Scan, publish, and print a share link
81
84
  ${c.dim("Flags: --public, --private, --type knowledge|instruction|workflow|skill")}
82
- ${c.dim(" --skill-version <label>")}
85
+ ${c.dim(" --skill-version <label>, --share <email>")}
86
+ ${c.dim(" --share emails the normal link; no account is needed to add it")}
83
87
  ${c.cyan("share")} ${c.dim("<slug>")} Email-share one of your skills
84
88
  ${c.dim("Flags: --add <email>, --remove <email>, --list")}
85
89
 
@@ -94,8 +98,12 @@ function commandUsage() {
94
98
  ${c.dim("Agent setup")}
95
99
  ${c.cyan("setup")} Configure Claude Code or Codex instructions
96
100
  ${c.dim("Alias: connect")}
97
- ${c.dim("Flags: --target claude|codex, --yes, --dry-run")}
101
+ ${c.dim("Flags: --target claude|codex, --yes, --dry-run, --file <path>")}
102
+ ${c.dim(" From a repo, setup writes that repo's CLAUDE.md/AGENTS.md")}
103
+ ${c.cyan("agent-prompt")} Print the sentence to paste into your agent
104
+ ${c.dim("Alias: paste")}
98
105
  ${c.cyan("doctor")} Troubleshoot auth, API, and local folders
106
+ ${c.dim("Flags: --json")}
99
107
 
100
108
  ${c.dim("Advanced")}
101
109
  ${c.cyan("library")} Create, browse, and subscribe to libraries
@@ -108,6 +116,7 @@ function commandUsage() {
108
116
  ${c.bold("Examples")}
109
117
  ${c.cyan("npx -y @floomhq/floom add")} ${c.dim("https://floom.dev/s/ffas93ud --setup")}
110
118
  ${c.cyan("npx -y @floomhq/floom publish")} ${c.dim("support-tone.md --type instruction --public")}
119
+ ${c.cyan("npx -y @floomhq/floom publish")} ${c.dim("support-tone.md --share teammate@example.com")}
111
120
  ${c.cyan("npx -y @floomhq/floom setup")} ${c.dim("--target claude --yes")}
112
121
 
113
122
  ${c.bold("Help")}
@@ -120,6 +129,7 @@ function commandUsage() {
120
129
  process.stdout.write(out);
121
130
  }
122
131
  const ASSET_TYPES = new Set(["knowledge", "instruction", "workflow", "skill"]);
132
+ const INIT_TEMPLATES = new Set(["generic", "brand-voice", "pr-review", "sales", "support", "onboarding"]);
123
133
  const INSTALL_TARGETS = new Set([
124
134
  "claude_skill",
125
135
  "memory",
@@ -140,7 +150,7 @@ function readFlagValue(argv, index, flag) {
140
150
  return { value, nextIndex: index + 1 };
141
151
  }
142
152
  function parseFlags(argv) {
143
- const out = { visibility: "unlisted", update: false, rest: [] };
153
+ const out = { visibility: "unlisted", update: false, shareEmails: [], explicitVisibility: false, rest: [] };
144
154
  let visibilityFlag = null;
145
155
  for (let i = 0; i < argv.length; i++) {
146
156
  const a = argv[i] ?? "";
@@ -151,12 +161,15 @@ function parseFlags(argv) {
151
161
  }
152
162
  visibilityFlag = nextVisibility;
153
163
  out.visibility = nextVisibility;
164
+ out.explicitVisibility = true;
154
165
  }
155
166
  else if (a === "--update") {
156
- throw new FloomError(V1_NOT_AVAILABLE, "`floom publish --update` is planned for a later Floom release.");
167
+ throw new FloomError("Publish updates are not available in Floom Version 1.", "Publish as a new skill for now. Use `npx -y @floomhq/floom update <link>` to refresh installed local copies.");
157
168
  }
158
169
  else if (a === "--share" || a.startsWith("--share=")) {
159
- throw new FloomError(V1_NOT_AVAILABLE, "`floom publish --share` is planned for a later Floom release.");
170
+ const { value, nextIndex } = readFlagValue(argv, i, "--share");
171
+ out.shareEmails.push(...parseEmailList(value, "--share"));
172
+ i = nextIndex;
160
173
  }
161
174
  else if (a === "--type" || a.startsWith("--type=")) {
162
175
  const { value, nextIndex } = readFlagValue(argv, i, "--type");
@@ -186,13 +199,36 @@ function parseFlags(argv) {
186
199
  throw new FloomError("`--version` prints the Floom CLI version at the top level.", "For skill version labels, use `--skill-version <label>`.");
187
200
  }
188
201
  else if (a.startsWith("--")) {
189
- throw new FloomError(`Unknown flag: ${a}`, "Try `floom publish skill.md --type instruction --public`.");
202
+ throw new FloomError(`Unknown flag: ${a}`, "Try `npx -y @floomhq/floom publish skill.md --type instruction --public`.");
190
203
  }
191
204
  else
192
205
  out.rest.push(a);
193
206
  }
207
+ if (out.shareEmails.length > 0) {
208
+ out.shareEmails = dedupeEmails(out.shareEmails);
209
+ if (out.shareEmails.length > 200) {
210
+ throw new FloomError("Too many --share recipients.", "Use 200 email addresses or fewer.");
211
+ }
212
+ if (out.visibility === "private") {
213
+ throw new FloomError("`--private --share` would email a link recipients cannot open.", "Use `--unlisted --share` for invite emails, or `npx -y @floomhq/floom share <slug> --add <email>` for email-gated access after publishing.");
214
+ }
215
+ }
194
216
  return out;
195
217
  }
218
+ function parseEmailList(value, source) {
219
+ const emails = value.split(",").map((email) => email.trim().toLowerCase()).filter(Boolean);
220
+ if (emails.length === 0)
221
+ throw new FloomError(`Missing value for ${source}.`, `Try \`${source} teammate@example.com\`.`);
222
+ for (const email of emails) {
223
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
224
+ throw new FloomError(`Invalid email for ${source}: ${email}`, "Use an address like teammate@example.com.");
225
+ }
226
+ }
227
+ return emails;
228
+ }
229
+ function dedupeEmails(emails) {
230
+ return [...new Set(emails)];
231
+ }
196
232
  function parseShareFlags(argv) {
197
233
  const out = { list: false, add: [], remove: [] };
198
234
  for (let i = 0; i < argv.length; i++) {
@@ -211,37 +247,48 @@ function parseShareFlags(argv) {
211
247
  i = nextIndex;
212
248
  }
213
249
  else if (a.startsWith("--")) {
214
- throw new FloomError(`Unknown flag: ${a}`, "Try `floom share <slug> --add person@example.com`.");
250
+ throw new FloomError(`Unknown flag: ${a}`, "Try `npx -y @floomhq/floom share <slug> --add person@example.com`.");
215
251
  }
216
252
  else if (!out.slug) {
217
253
  out.slug = a;
218
254
  }
219
255
  else {
220
- throw new FloomError(`Unexpected argument: ${a}`, "Try `floom share <slug> --add person@example.com`.");
256
+ throw new FloomError(`Unexpected argument: ${a}`, "Try `npx -y @floomhq/floom share <slug> --add person@example.com`.");
221
257
  }
222
258
  }
223
259
  if (!out.slug)
224
- throw new FloomError("Missing skill slug.", "Try `floom share <slug> --add person@example.com`.");
260
+ throw new FloomError("Missing skill slug.", "Try `npx -y @floomhq/floom share <slug> --add person@example.com`.");
225
261
  if (out.list && (out.add.length > 0 || out.remove.length > 0)) {
226
262
  throw new FloomError("Conflicting share flags.", "Use --list by itself, or use --add/--remove.");
227
263
  }
228
264
  if (!out.list && out.add.length === 0 && out.remove.length === 0) {
229
- throw new FloomError("Missing share action.", "Try `floom share <slug> --add person@example.com`.");
265
+ throw new FloomError("Missing share action.", "Try `npx -y @floomhq/floom share <slug> --add person@example.com`.");
230
266
  }
231
267
  return out;
232
268
  }
233
269
  function parseInitArgs(argv) {
234
270
  let file;
235
- for (const a of argv) {
271
+ let template = "generic";
272
+ for (let i = 0; i < argv.length; i++) {
273
+ const a = argv[i] ?? "";
274
+ if (a === "--template" || a.startsWith("--template=")) {
275
+ const { value, nextIndex } = readFlagValue(argv, i, "--template");
276
+ if (!INIT_TEMPLATES.has(value)) {
277
+ throw new FloomError(`Invalid --template: ${value}`, "Use one of: generic, brand-voice, pr-review, sales, support, onboarding.");
278
+ }
279
+ template = value;
280
+ i = nextIndex;
281
+ continue;
282
+ }
236
283
  if (a.startsWith("--")) {
237
- throw new FloomError(`Unknown flag: ${a}`, "Try `floom init skill.md`.");
284
+ throw new FloomError(`Unknown flag: ${a}`, "Try `npx -y @floomhq/floom init skill.md --template brand-voice`.");
238
285
  }
239
286
  if (file) {
240
- throw new FloomError(`Unexpected argument: ${a}`, "Try `floom init skill.md`.");
287
+ throw new FloomError(`Unexpected argument: ${a}`, "Try `npx -y @floomhq/floom init skill.md`.");
241
288
  }
242
289
  file = a;
243
290
  }
244
- return file ? { file } : {};
291
+ return file ? { file, template } : { template };
245
292
  }
246
293
  function parseListFlags(argv) {
247
294
  const out = { json: false };
@@ -249,10 +296,10 @@ function parseListFlags(argv) {
249
296
  if (a === "--json")
250
297
  out.json = true;
251
298
  else if (a.startsWith("--")) {
252
- throw new FloomError(`Unknown flag: ${a}`, "Try `floom list --help` for usage.");
299
+ throw new FloomError(`Unknown flag: ${a}`, "Try `npx -y @floomhq/floom list --help` for usage.");
253
300
  }
254
301
  else {
255
- throw new FloomError(`Unexpected argument: ${a}`, "Try `floom list --json`.");
302
+ throw new FloomError(`Unexpected argument: ${a}`, "Try `npx -y @floomhq/floom list --json`.");
256
303
  }
257
304
  }
258
305
  return out;
@@ -263,11 +310,11 @@ function parseInfoFlags(argv) {
263
310
  if (a === "--json")
264
311
  out.json = true;
265
312
  else if (a.startsWith("--"))
266
- throw new FloomError(`Unknown flag: ${a}`, "Try `floom info <slug> --json`.");
313
+ throw new FloomError(`Unknown flag: ${a}`, "Try `npx -y @floomhq/floom info <slug> --json`.");
267
314
  else if (!out.slug)
268
315
  out.slug = a;
269
316
  else
270
- throw new FloomError(`Unexpected argument: ${a}`, "Try `floom info <slug> --json`.");
317
+ throw new FloomError(`Unexpected argument: ${a}`, "Try `npx -y @floomhq/floom info <slug> --json`.");
271
318
  }
272
319
  return out;
273
320
  }
@@ -276,6 +323,7 @@ function parseAddArgs(argv) {
276
323
  let target;
277
324
  let setup = false;
278
325
  let force = false;
326
+ let json = false;
279
327
  for (let i = 0; i < argv.length; i++) {
280
328
  const a = argv[i] ?? "";
281
329
  if (a === "--target" || a.startsWith("--target=")) {
@@ -289,22 +337,40 @@ function parseAddArgs(argv) {
289
337
  else if (a === "--setup") {
290
338
  setup = true;
291
339
  }
292
- else if (a === "--force") {
340
+ else if (a === "--json") {
341
+ json = true;
342
+ }
343
+ else if (a === "--force" || a === "--update") {
293
344
  force = true;
294
345
  }
295
346
  else if (a.startsWith("--")) {
296
- throw new FloomError(`Unknown flag: ${a}`, "Try `floom add <url-or-slug> --setup`.");
347
+ throw new FloomError(`Unknown flag: ${a}`, "Try `npx -y @floomhq/floom add <url-or-slug> --setup`.");
297
348
  }
298
349
  else if (slug) {
299
- throw new FloomError(`Unexpected argument: ${a}`, "Try `floom add <url-or-slug> --setup`.");
350
+ throw new FloomError(`Unexpected argument: ${a}`, "Try `npx -y @floomhq/floom add <url-or-slug> --setup`.");
300
351
  }
301
352
  else
302
353
  slug = a;
303
354
  }
304
355
  if (!slug) {
305
- throw new FloomError("Missing skill slug.", "Try: `floom add <url-or-slug> --setup`");
356
+ throw new FloomError("Missing skill slug.", "Try: `npx -y @floomhq/floom add <url-or-slug> --setup`");
357
+ }
358
+ if (json && setup) {
359
+ throw new FloomError("Conflicting add flags.", "Use `--json` for machine output or `--setup` for interactive agent setup.");
306
360
  }
307
- return target ? { slug, target, setup, force } : { slug, setup, force };
361
+ return target ? { slug, target, setup, force, json } : { slug, setup, force, json };
362
+ }
363
+ function parseDoctorArgs(argv) {
364
+ const out = { json: false };
365
+ for (const a of argv) {
366
+ if (a === "--json")
367
+ out.json = true;
368
+ else if (a.startsWith("--"))
369
+ throw new FloomError(`Unknown flag: ${a}`, "Try `npx -y @floomhq/floom doctor --json`.");
370
+ else
371
+ throw new FloomError(`Unexpected argument: ${a}`, "Try `npx -y @floomhq/floom doctor --json`.");
372
+ }
373
+ return out;
308
374
  }
309
375
  function parseSearchFlags(argv) {
310
376
  const out = { json: false };
@@ -327,7 +393,7 @@ function parseSearchFlags(argv) {
327
393
  i = nextIndex;
328
394
  }
329
395
  else if (a.startsWith("--")) {
330
- throw new FloomError(`Unknown flag: ${a}`, "Try `floom search \"support tone\" --type instruction`.");
396
+ throw new FloomError(`Unknown flag: ${a}`, "Try `npx -y @floomhq/floom search \"support tone\" --type instruction`.");
331
397
  }
332
398
  else {
333
399
  terms.push(a);
@@ -342,11 +408,11 @@ function parseDeleteFlags(argv) {
342
408
  if (a === "--yes" || a === "-y")
343
409
  out.yes = true;
344
410
  else if (a.startsWith("--"))
345
- throw new FloomError(`Unknown flag: ${a}`, "Try `floom delete <slug> --yes`.");
411
+ throw new FloomError(`Unknown flag: ${a}`, "Try `npx -y @floomhq/floom delete <slug> --yes`.");
346
412
  else if (!out.slug)
347
413
  out.slug = a;
348
414
  else
349
- throw new FloomError(`Unexpected argument: ${a}`, "Try `floom delete <slug> --yes`.");
415
+ throw new FloomError(`Unexpected argument: ${a}`, "Try `npx -y @floomhq/floom delete <slug> --yes`.");
350
416
  }
351
417
  return out;
352
418
  }
@@ -380,12 +446,12 @@ function parseSetupFlags(argv) {
380
446
  i = nextIndex;
381
447
  }
382
448
  else if (a.startsWith("--")) {
383
- throw new FloomError(`Unknown flag: ${a}`, "Try `floom setup --target codex --dry-run`.");
449
+ throw new FloomError(`Unknown flag: ${a}`, "Try `npx -y @floomhq/floom setup --target codex --dry-run`.");
384
450
  }
385
451
  else if (!out.file)
386
452
  out.file = a;
387
453
  else
388
- throw new FloomError(`Unexpected argument: ${a}`, "Try `floom setup --target claude --yes`.");
454
+ throw new FloomError(`Unexpected argument: ${a}`, "Try `npx -y @floomhq/floom setup --target claude --yes`.");
389
455
  }
390
456
  return out;
391
457
  }
@@ -444,17 +510,21 @@ function parseLibraryCreateFlags(argv) {
444
510
  else if (a === "--unlisted")
445
511
  out.visibility = "unlisted";
446
512
  else if (a.startsWith("--")) {
447
- throw new FloomError(`Unknown flag: ${a}`, "Try `floom library create team-onboarding --name \"Team onboarding\"`.");
513
+ throw new FloomError(`Unknown flag: ${a}`, "Try `npx -y @floomhq/floom library create team-onboarding --name \"Team onboarding\"`.");
448
514
  }
449
515
  else if (!out.slug)
450
516
  out.slug = a;
451
517
  else
452
- throw new FloomError(`Unexpected argument: ${a}`, "Try `floom library create <slug> --name <name>`.");
518
+ throw new FloomError(`Unexpected argument: ${a}`, "Try `npx -y @floomhq/floom library create <slug> --name <name>`.");
453
519
  }
454
520
  return out;
455
521
  }
456
522
  async function runLibrary(argv) {
457
523
  const [subcommand, ...rest] = argv;
524
+ if (!subcommand || subcommand === "--json") {
525
+ await libraryList(parseListFlags(argv));
526
+ return;
527
+ }
458
528
  switch (subcommand ?? "list") {
459
529
  case "list": {
460
530
  const flags = parseListFlags(rest);
@@ -464,9 +534,9 @@ async function runLibrary(argv) {
464
534
  case "create": {
465
535
  const flags = parseLibraryCreateFlags(rest);
466
536
  if (!flags.slug)
467
- throw new FloomError("Missing library slug.", "Try `floom library create team-onboarding --name \"Team onboarding\"`.");
537
+ throw new FloomError("Missing library slug.", "Try `npx -y @floomhq/floom library create team-onboarding --name \"Team onboarding\"`.");
468
538
  if (!flags.name)
469
- throw new FloomError("Missing --name.", "Try `floom library create team-onboarding --name \"Team onboarding\"`.");
539
+ throw new FloomError("Missing --name.", "Try `npx -y @floomhq/floom library create team-onboarding --name \"Team onboarding\"`.");
470
540
  await libraryCreate({
471
541
  slug: flags.slug,
472
542
  name: flags.name,
@@ -479,10 +549,10 @@ async function runLibrary(argv) {
479
549
  const flags = parseFolderTagFlags(rest);
480
550
  const [librarySlug, skillSlug] = flags.rest;
481
551
  if (!librarySlug || !skillSlug) {
482
- throw new FloomError("Missing library or skill slug.", "Try `floom library add team-onboarding support-tone --folder support`.");
552
+ throw new FloomError("Missing library or skill slug.", "Try `npx -y @floomhq/floom library add team-onboarding support-tone --folder support`.");
483
553
  }
484
554
  if (flags.rest.length > 2) {
485
- throw new FloomError(`Unexpected argument: ${flags.rest[2]}`, "Try `floom library add team-onboarding support-tone --folder support`.");
555
+ throw new FloomError(`Unexpected argument: ${flags.rest[2]}`, "Try `npx -y @floomhq/floom library add team-onboarding support-tone --folder support`.");
486
556
  }
487
557
  await libraryAddSkill({
488
558
  librarySlug,
@@ -496,10 +566,10 @@ async function runLibrary(argv) {
496
566
  case "rm": {
497
567
  const [librarySlug, skillSlug] = rest;
498
568
  if (!librarySlug || !skillSlug) {
499
- throw new FloomError("Missing library or skill slug.", "Try `floom library remove team-onboarding support-tone`.");
569
+ throw new FloomError("Missing library or skill slug.", "Try `npx -y @floomhq/floom library remove team-onboarding support-tone`.");
500
570
  }
501
571
  if (rest.length > 2) {
502
- throw new FloomError(`Unexpected argument: ${rest[2]}`, "Try `floom library remove team-onboarding support-tone`.");
572
+ throw new FloomError(`Unexpected argument: ${rest[2]}`, "Try `npx -y @floomhq/floom library remove team-onboarding support-tone`.");
503
573
  }
504
574
  await libraryRemoveSkill(librarySlug, skillSlug);
505
575
  return;
@@ -507,9 +577,9 @@ async function runLibrary(argv) {
507
577
  case "subscribe": {
508
578
  const slug = rest[0];
509
579
  if (!slug)
510
- throw new FloomError("Missing library slug.", "Try `floom library subscribe superpowers`.");
580
+ throw new FloomError("Missing library slug.", "Try `npx -y @floomhq/floom library subscribe superpowers`.");
511
581
  if (rest.length > 1) {
512
- throw new FloomError(`Unexpected argument: ${rest[1]}`, "Try `floom library subscribe superpowers`.");
582
+ throw new FloomError(`Unexpected argument: ${rest[1]}`, "Try `npx -y @floomhq/floom library subscribe superpowers`.");
513
583
  }
514
584
  await librarySubscribe(slug);
515
585
  return;
@@ -517,9 +587,9 @@ async function runLibrary(argv) {
517
587
  case "unsubscribe": {
518
588
  const slug = rest[0];
519
589
  if (!slug)
520
- throw new FloomError("Missing library slug.", "Try `floom library unsubscribe superpowers`.");
590
+ throw new FloomError("Missing library slug.", "Try `npx -y @floomhq/floom library unsubscribe superpowers`.");
521
591
  if (rest.length > 1) {
522
- throw new FloomError(`Unexpected argument: ${rest[1]}`, "Try `floom library unsubscribe superpowers`.");
592
+ throw new FloomError(`Unexpected argument: ${rest[1]}`, "Try `npx -y @floomhq/floom library unsubscribe superpowers`.");
523
593
  }
524
594
  await libraryUnsubscribe(slug);
525
595
  return;
@@ -542,10 +612,10 @@ function parseWatchFlags(argv) {
542
612
  i = nextIndex;
543
613
  }
544
614
  else if (a.startsWith("--")) {
545
- throw new FloomError(`Unknown flag: ${a}`, "Try `floom watch --interval 60`.");
615
+ throw new FloomError(`Unknown flag: ${a}`, "Try `npx -y @floomhq/floom watch --interval 60`.");
546
616
  }
547
617
  else {
548
- throw new FloomError(`Unexpected argument: ${a}`, "Try `floom watch --interval 60`.");
618
+ throw new FloomError(`Unexpected argument: ${a}`, "Try `npx -y @floomhq/floom watch --interval 60`.");
549
619
  }
550
620
  }
551
621
  return out;
@@ -566,6 +636,9 @@ function parseSingleFileArg(argv, usageHint) {
566
636
  throw new FloomError("Missing file argument.", usageHint);
567
637
  return file;
568
638
  }
639
+ function agentPrompt() {
640
+ process.stdout.write("Use my installed Floom skills when they fit the task. Search ~/.claude/skills first.\n");
641
+ }
569
642
  function sleep(ms, signal) {
570
643
  if (signal.aborted)
571
644
  return Promise.resolve();
@@ -616,10 +689,10 @@ async function main() {
616
689
  // never block on update-notifier
617
690
  }
618
691
  }
619
- // Subcommand --help: any rest arg = --help/-h/help → show top-level usage.
692
+ // Subcommand --help: any rest arg = --help/-h/help → show top-level reference.
620
693
  // Subcommands are simple enough that one help screen is fine for Version 1.
621
694
  if (rest.includes("--help") || rest.includes("-h") || rest.includes("help")) {
622
- usage();
695
+ commandUsage();
623
696
  return;
624
697
  }
625
698
  try {
@@ -633,7 +706,7 @@ async function main() {
633
706
  commandUsage();
634
707
  return;
635
708
  case "commands":
636
- rejectArgs(rest, "Try `floom commands`.");
709
+ rejectArgs(rest, "Try `npx -y @floomhq/floom commands`.");
637
710
  commandUsage();
638
711
  return;
639
712
  case "--version":
@@ -641,31 +714,31 @@ async function main() {
641
714
  process.stdout.write(`${CLI_VERSION}\n`);
642
715
  return;
643
716
  case "login":
644
- rejectArgs(rest, "Try `floom login`.");
717
+ rejectArgs(rest, "Try `npx -y @floomhq/floom login`.");
645
718
  await login();
646
719
  return;
647
720
  case "logout":
648
- rejectArgs(rest, "Try `floom logout`.");
721
+ rejectArgs(rest, "Try `npx -y @floomhq/floom logout`.");
649
722
  await deleteConfig();
650
723
  process.stdout.write(`\n${symbols.ok} Signed out\n\n`);
651
724
  return;
652
725
  case "whoami":
653
- rejectArgs(rest, "Try `floom whoami`.");
726
+ rejectArgs(rest, "Try `npx -y @floomhq/floom whoami`.");
654
727
  await whoami();
655
728
  return;
656
729
  case "init": {
657
730
  const flags = parseInitArgs(rest);
658
- await init(flags.file);
731
+ await init(flags.file, flags.template);
659
732
  return;
660
733
  }
661
734
  case "publish": {
662
735
  const flags = parseFlags(rest);
663
736
  const file = flags.rest[0];
664
737
  if (!file) {
665
- throw new FloomError("Missing file argument.", "Try: `floom publish skill.md`");
738
+ throw new FloomError("Missing file argument.", "Try: `npx -y @floomhq/floom publish skill.md`");
666
739
  }
667
740
  if (flags.rest.length > 1) {
668
- throw new FloomError(`Unexpected argument: ${flags.rest[1]}`, "Try: `floom publish skill.md`");
741
+ throw new FloomError(`Unexpected argument: ${flags.rest[1]}`, "Try: `npx -y @floomhq/floom publish skill.md`");
669
742
  }
670
743
  await publish({
671
744
  file,
@@ -674,11 +747,12 @@ async function main() {
674
747
  ...(flags.assetType ? { assetType: flags.assetType } : {}),
675
748
  ...(flags.installsAs ? { installsAs: flags.installsAs } : {}),
676
749
  ...(flags.version ? { version: flags.version } : {}),
750
+ ...(flags.shareEmails.length > 0 ? { sharedWithEmails: flags.shareEmails } : {}),
677
751
  });
678
752
  return;
679
753
  }
680
754
  case "scan": {
681
- const file = parseSingleFileArg(rest, "Try `floom scan skill.md`.");
755
+ const file = parseSingleFileArg(rest, "Try `npx -y @floomhq/floom scan skill.md`.");
682
756
  await scanSkill(file);
683
757
  return;
684
758
  }
@@ -707,7 +781,7 @@ async function main() {
707
781
  case "search": {
708
782
  const flags = parseSearchFlags(rest);
709
783
  if (!flags.query) {
710
- throw new FloomError("Missing search query.", "Try: `floom search \"support tone\"`.");
784
+ throw new FloomError("Missing search query.", "Try: `npx -y @floomhq/floom search \"support tone\"`.");
711
785
  }
712
786
  await search({
713
787
  query: flags.query,
@@ -724,6 +798,20 @@ async function main() {
724
798
  ...(flags.target ? { target: flags.target } : {}),
725
799
  setup: flags.setup,
726
800
  force: flags.force,
801
+ json: flags.json,
802
+ });
803
+ if (flags.setup) {
804
+ await setupAgent({ target: flags.target ?? "claude", dryRun: false, yes: true });
805
+ }
806
+ return;
807
+ }
808
+ case "update": {
809
+ const flags = parseAddArgs(rest);
810
+ await install(flags.slug, {
811
+ ...(flags.target ? { target: flags.target } : {}),
812
+ setup: flags.setup,
813
+ force: true,
814
+ json: flags.json,
727
815
  });
728
816
  if (flags.setup) {
729
817
  await setupAgent({ target: flags.target ?? "claude", dryRun: false, yes: true });
@@ -731,7 +819,7 @@ async function main() {
731
819
  return;
732
820
  }
733
821
  case "sync":
734
- rejectArgs(rest, "Try `floom sync`.");
822
+ rejectArgs(rest, "Try `npx -y @floomhq/floom sync`.");
735
823
  await sync();
736
824
  return;
737
825
  case "setup":
@@ -740,6 +828,11 @@ async function main() {
740
828
  await setupAgent(flags);
741
829
  return;
742
830
  }
831
+ case "agent-prompt":
832
+ case "paste":
833
+ rejectArgs(rest, "Try `npx -y @floomhq/floom agent-prompt`.");
834
+ agentPrompt();
835
+ return;
743
836
  case "watch": {
744
837
  const flags = parseWatchFlags(rest);
745
838
  await watch(flags.intervalSeconds);
@@ -759,27 +852,29 @@ async function main() {
759
852
  const flags = parseFolderTagFlags(rest);
760
853
  const slug = flags.rest[0];
761
854
  if (!slug) {
762
- throw new FloomError("Missing skill slug.", "Try `floom move support-tone --folder support/tone`.");
855
+ throw new FloomError("Missing skill slug.", "Try `npx -y @floomhq/floom move support-tone --folder support/tone`.");
763
856
  }
764
857
  if (flags.folder === undefined) {
765
858
  throw new FloomError("Missing --folder.", "Use --folder <path> or --root. Add --tag or --tags when useful.");
766
859
  }
767
860
  if (flags.rest.length > 1) {
768
- throw new FloomError(`Unexpected argument: ${flags.rest[1]}`, "Try `floom move support-tone --folder support/tone`.");
861
+ throw new FloomError(`Unexpected argument: ${flags.rest[1]}`, "Try `npx -y @floomhq/floom move support-tone --folder support/tone`.");
769
862
  }
770
863
  await moveSkill({ slug, folder: flags.folder, tags: flags.tags });
771
864
  return;
772
865
  }
773
866
  case "mcp":
774
- rejectArgs(rest, "Try `floom mcp`.");
867
+ rejectArgs(rest, "Try `npx -y @floomhq/floom mcp`.");
775
868
  printMcpSetup();
776
869
  return;
777
870
  case "doctor":
778
- rejectArgs(rest, "Try `floom doctor`.");
779
- await doctor();
871
+ {
872
+ const flags = parseDoctorArgs(rest);
873
+ await doctor(flags);
874
+ }
780
875
  return;
781
876
  default:
782
- throw new FloomError(`Unknown command: ${cmd}`, "Run `floom --help` to see available commands.");
877
+ throw new FloomError(`Unknown command: ${cmd}`, "Run `npx -y @floomhq/floom --help` to see available commands.");
783
878
  }
784
879
  }
785
880
  catch (e) {