@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 +28 -24
- package/dist/cli.js +158 -63
- package/dist/delete.js +2 -2
- package/dist/doctor.js +21 -8
- package/dist/errors.js +2 -2
- package/dist/info.js +2 -2
- package/dist/init.js +154 -6
- package/dist/install.js +20 -8
- package/dist/library.js +7 -7
- package/dist/list.js +2 -2
- package/dist/login.js +1 -1
- package/dist/mcp.js +2 -2
- package/dist/publish.js +11 -9
- package/dist/secrets.js +23 -3
- package/dist/setup.js +8 -8
- package/dist/share.js +2 -2
- package/dist/sync.js +2 -2
- package/dist/whoami.js +1 -1
- package/package.json +1 -1
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>`,
|
|
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
|
|
29
|
-
- `floom
|
|
30
|
-
- `floom
|
|
31
|
-
- `floom
|
|
32
|
-
- `floom
|
|
33
|
-
- `floom
|
|
34
|
-
- `floom
|
|
35
|
-
- `floom
|
|
36
|
-
- `floom
|
|
37
|
-
- `
|
|
38
|
-
- `
|
|
39
|
-
- `floom library
|
|
40
|
-
- `floom
|
|
41
|
-
- `floom
|
|
42
|
-
- `npx -y @floomhq/floom
|
|
43
|
-
- `floom
|
|
44
|
-
- `floom
|
|
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
|
|
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> [
|
|
67
|
-
${c.dim("Global install binary:")} ${c.cyan("floom-skills")} ${c.dim("<command> [
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 === "--
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
779
|
-
|
|
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) {
|