@floomhq/floom 1.0.64 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js DELETED
@@ -1,1313 +0,0 @@
1
- #!/usr/bin/env node
2
- import updateNotifier from "update-notifier";
3
- import { login } from "./login.js";
4
- import { publish } from "./publish.js";
5
- import { whoami } from "./whoami.js";
6
- import { init, INIT_TEMPLATES } from "./init.js";
7
- import { deleteConfig, readConfig } from "./config.js";
8
- import { list } from "./list.js";
9
- import { install } from "./install.js";
10
- import { info } from "./info.js";
11
- import { deleteSkill } from "./delete.js";
12
- import { doctor } from "./doctor.js";
13
- import { sync } from "./sync.js";
14
- import { watchPush } from "./push-watch.js";
15
- import { daemon, normalizeDaemonOptions, parseDaemonTarget } from "./daemon.js";
16
- import { printMcpSetup } from "./mcp.js";
17
- import { setupAgent } from "./setup.js";
18
- import { search } from "./search.js";
19
- import { scanSkill } from "./scan.js";
20
- import { auditSkills } from "./audit.js";
21
- import { share } from "./share.js";
22
- import { feedback } from "./feedback.js";
23
- import { status } from "./status.js";
24
- import { launchGate } from "./launch.js";
25
- import { libraryAddSkill, libraryCreate, libraryList, libraryRemoveSkill, librarySubscribe, libraryUnsubscribe, moveSkill, } from "./library.js";
26
- import { c, symbols } from "./ui.js";
27
- import { printError, FloomError } from "./errors.js";
28
- import { CLI_VERSION } from "./version.js";
29
- import { TARGET_HINT, isAgentTarget } from "./targets.js";
30
- const PKG = { name: "@floomhq/floom", version: CLI_VERSION };
31
- const V1_NOT_AVAILABLE = "Not available in Floom Version 1.";
32
- const CLI_COMMAND = "npx -y @floomhq/floom";
33
- function exitOnBrokenPipe(err) {
34
- if (err.code === "EPIPE")
35
- process.exit(0);
36
- throw err;
37
- }
38
- process.stdout.on("error", exitOnBrokenPipe);
39
- process.stderr.on("error", exitOnBrokenPipe);
40
- function usage() {
41
- const out = `
42
- ${c.blue(" ________ ")}
43
- ${c.blue(" / ____/ /___ ____ ____ ___ ")} ${c.dim(`v${CLI_VERSION}`)}
44
- ${c.blue("/ /_ / / __ \\/ __ \\/ __ `__ \\ ")}
45
- ${c.blue("/ __/ / / /_/ / /_/ / / / / / / ")}
46
- ${c.blue("/_/ /_/\\____/\\____/_/ /_/ /_/ ")}
47
-
48
- ${c.bold("Floom syncs your AI skills across agents and machines.")}
49
- ${c.dim("A skill is reusable knowledge, instructions, or a workflow for your AI agent.")}
50
- ${c.dim("Examples: brand voice, PR review checklist, sales research workflow.")}
51
-
52
- ${c.bold("Start with one command:")}
53
-
54
- ${c.bold("1. I received a Floom link")}
55
- ${c.dim("Replace <skill-link> with the full Floom URL someone sent you:")}
56
- ${c.cyan("npx -y @floomhq/floom add")} ${c.dim("<skill-link> --setup")}
57
- ${c.dim('Then tell Claude Code: "Use my Floom skills when they fit this task."')}
58
-
59
- ${c.bold("2. I want my own synced skill library")}
60
- ${c.cyan("npx -y @floomhq/floom init")} ${c.dim("my-skill")}
61
- ${c.dim("Write what your agent needs to know or do in my-skill/SKILL.md.")}
62
- ${c.cyan("npx -y @floomhq/floom login")}
63
- ${c.cyan("npx -y @floomhq/floom publish")} ${c.dim("my-skill --public")}
64
- ${c.dim("Floom scans it, prints a link, and copies the link when possible.")}
65
-
66
- ${c.bold("Good to know")}
67
- ${symbols.ok} ${c.dim("No account is needed to add a shared skill.")}
68
- ${symbols.ok} ${c.dim("Sign in only when you publish or manage your skills.")}
69
- ${symbols.ok} ${c.dim("Every command prints success or the exact problem to fix.")}
70
-
71
- ${c.bold("Stuck?")}
72
- ${c.cyan("npx -y @floomhq/floom doctor")} ${c.dim("Find the problem")}
73
- ${c.cyan("npx -y @floomhq/floom scan my-skill")} ${c.dim("Check a skill before publishing")}
74
- ${c.cyan("npx -y @floomhq/floom commands")} ${c.dim("See every command")}
75
- ${c.dim("Step-by-step guide")} https://floom.dev/docs/getting-started
76
- `;
77
- process.stdout.write(out);
78
- }
79
- function commandUsage() {
80
- const out = `
81
- ${c.bold("Usage:")} ${c.cyan("npx -y @floomhq/floom")} ${c.dim("<command> [flags]")}
82
- ${c.dim("After global install:")} ${c.cyan("floom")} ${c.dim("<command> [flags]")}
83
-
84
- ${c.bold("Commands")}
85
- ${c.dim("Skills")}
86
- ${c.cyan("add")} ${c.dim("<url>")} Install a skill into the local agent skills folder
87
- ${c.dim("Alias: install")}
88
- ${c.dim(`Flags: --target ${TARGET_HINT} (default: claude), --setup, --force`)}
89
- ${c.cyan("search")} ${c.dim("<query>")} Find public skills and libraries
90
- ${c.cyan("info")} ${c.dim("<url>")} Show skill metadata
91
-
92
- ${c.dim("Publishing")}
93
- ${c.cyan("init")} ${c.dim("[path]")} Create a skill scaffold
94
- ${c.dim("Flags: --template generic|brand-voice|pr-review|sales|support|onboarding")}
95
- ${c.cyan("scan")} ${c.dim("<path>")} Check for secrets, injection, exfiltration
96
- ${c.cyan("publish")} ${c.dim("<path>")} Scan, publish, and print a share link
97
- ${c.dim("Flags: --public, --private, --type knowledge|instruction|workflow|skill")}
98
- ${c.dim(" --skill-version <label>")}
99
- ${c.cyan("share")} ${c.dim("<slug>")} Email-share one of your skills
100
- ${c.dim("Flags: --add <email>, --remove <email>, --list")}
101
-
102
- ${c.dim("Account")}
103
- ${c.cyan("login")} Authenticate
104
- ${c.cyan("list")} Your published skills
105
- ${c.cyan("delete")} ${c.dim("<url>")} Delete one of your skills
106
- ${c.dim("Alias: rm")}
107
- ${c.cyan("whoami")} Show the signed-in account
108
- ${c.cyan("logout")} Switch accounts or remove local credentials
109
- ${c.cyan("feedback")} Send agent feedback to Floom
110
-
111
- ${c.dim("Agent setup")}
112
- ${c.cyan("setup")} Configure Claude Code or Codex instructions
113
- ${c.dim("Alias: connect")}
114
- ${c.dim(`Flags: --target ${TARGET_HINT}, --yes, --dry-run`)}
115
- ${c.cyan("doctor")} Troubleshoot auth, API, and local folders
116
- ${c.dim(`Flags: --target ${TARGET_HINT} (default: claude)`)}
117
-
118
- ${c.dim("Advanced")}
119
- ${c.cyan("status")} Show cloud, cache, native projection, and daemon counts
120
- ${c.dim("Flags: --json")}
121
- ${c.cyan("library")} Create, browse, and subscribe to libraries
122
- ${c.dim("Alias: lib")}
123
- ${c.cyan("move")} ${c.dim("<slug> --folder <path>")} Place a saved skill in a relative folder
124
- ${c.cyan("mcp")} Print optional MCP setup guidance
125
- ${c.cyan("sync")} Preview pull of published, saved, and library skills
126
- ${c.dim(`Flags: --target ${TARGET_HINT} (default: claude)`)}
127
- ${c.cyan("watch")} Preview polling sync loop
128
- ${c.dim(`Flags: --push, --no-yolo, --target ${TARGET_HINT}`)}
129
- ${c.cyan("daemon")} Install and inspect always-on Floom sync
130
- ${c.dim(`Flags: install|status|logs|run, --target ${TARGET_HINT}|all`)}
131
- ${c.cyan("audit skills")} Read-only skill quality and duplicate report
132
- ${c.dim("Flags: --json, --fix-plan")}
133
- ${c.cyan("launch gate")} Check pinned release identity for launch evidence
134
- ${c.dim("Flags: --json")}
135
-
136
- ${c.bold("Examples")}
137
- ${c.cyan("npx -y @floomhq/floom add")} ${c.dim("https://floom.dev/s/ffas93ud --setup")}
138
- ${c.cyan("npx -y @floomhq/floom publish")} ${c.dim("support-tone --type instruction --public")}
139
- ${c.cyan("npx -y @floomhq/floom setup")} ${c.dim("--target claude --yes")}
140
-
141
- ${c.bold("Help")}
142
- ${c.cyan("npx -y @floomhq/floom commands")} Show this reference
143
- ${c.cyan("--help")} Show this reference
144
- ${c.cyan("--version")} Show CLI version
145
-
146
- ${c.dim("Run")} ${c.cyan("npx -y @floomhq/floom")} ${c.dim("with no command for the guided start screen.")}
147
- `;
148
- process.stdout.write(out);
149
- }
150
- function shareUsage() {
151
- process.stdout.write(`
152
- ${c.bold("Usage:")} ${c.cyan(`${CLI_COMMAND} share`)} ${c.dim("<slug> [flags]")}
153
-
154
- ${c.bold("Manage email access for one of your skills.")}
155
- ${c.cyan(`${CLI_COMMAND} share support-tone --add person@example.com`)}
156
- ${c.cyan(`${CLI_COMMAND} share support-tone --remove person@example.com`)}
157
- ${c.cyan(`${CLI_COMMAND} share support-tone --list`)}
158
-
159
- ${c.bold("Flags")}
160
- ${c.cyan("--add <email>")} Grant access
161
- ${c.cyan("--remove <email>")} Revoke access
162
- ${c.cyan("--list")} Show who can access the skill
163
- `);
164
- }
165
- function libraryUsage() {
166
- process.stdout.write(`
167
- ${c.bold("Usage:")} ${c.cyan(`${CLI_COMMAND} library`)} ${c.dim("<command> [args] [flags]")}
168
-
169
- ${c.bold("Commands")}
170
- ${c.cyan("list")} Browse public libraries
171
- ${c.cyan("create <slug> --name <name>")} Create a library
172
- ${c.cyan("add <library> <skill>")} Add a skill to a library
173
- ${c.cyan("remove <library> <skill>")} Remove a skill from a library
174
- ${c.cyan("subscribe <library>")} Follow a library
175
- ${c.cyan("unsubscribe <library>")} Stop following a library
176
-
177
- ${c.bold("Examples")}
178
- ${c.cyan(`${CLI_COMMAND} library list --json`)}
179
- ${c.cyan(`${CLI_COMMAND} library create team-onboarding --name "Team onboarding" --public`)}
180
- ${c.cyan(`${CLI_COMMAND} library add team-onboarding support-tone --folder support --tags support,tone`)}
181
- `);
182
- }
183
- function moveUsage() {
184
- process.stdout.write(`
185
- ${c.bold("Usage:")} ${c.cyan(`${CLI_COMMAND} move`)} ${c.dim("<slug> --folder <path> [--tag <tag>]")}
186
-
187
- ${c.bold("Place a saved or subscribed skill in a portable library folder.")}
188
- ${c.cyan(`${CLI_COMMAND} move support-tone --folder support/tone`)}
189
- ${c.cyan(`${CLI_COMMAND} move support-tone --root`)}
190
- ${c.cyan(`${CLI_COMMAND} move support-tone --folder support --tags support,tone`)}
191
-
192
- ${c.bold("Flags")}
193
- ${c.cyan("--folder <path>")} Relative folder path for synced installs
194
- ${c.cyan("--root")} Put the skill at the root
195
- ${c.cyan("--tag <tag>")} Add one tag, repeatable
196
- ${c.cyan("--tags a,b")} Add comma-separated tags
197
- `);
198
- }
199
- function addUsage() {
200
- process.stdout.write(`
201
- ${c.bold("Usage:")} ${c.cyan(`${CLI_COMMAND} add`)} ${c.dim("<slug-or-url> [flags]")}
202
-
203
- ${c.bold("Install a shared skill locally.")}
204
- ${c.cyan(`${CLI_COMMAND} add https://floom.dev/s/ffas93ud --target claude --setup`)}
205
- ${c.cyan(`${CLI_COMMAND} add support-tone --target codex --force`)}
206
-
207
- ${c.bold("Flags")}
208
- ${c.cyan(`--target ${TARGET_HINT}`)} Agent target, default claude
209
- ${c.cyan("--setup")} Add Floom instructions for the target agent
210
- ${c.cyan("--force")} Replace an existing tracked install
211
- `);
212
- }
213
- function syncUsage() {
214
- process.stdout.write(`
215
- ${c.bold("Usage:")} ${c.cyan(`${CLI_COMMAND} sync`)} ${c.dim("[flags]")}
216
-
217
- ${c.bold("Pull published, saved, and subscribed library skills into one target.")}
218
- ${c.cyan(`${CLI_COMMAND} sync --target claude`)}
219
- ${c.cyan(`${CLI_COMMAND} sync --target codex`)}
220
-
221
- ${c.bold("Flags")}
222
- ${c.cyan(`--target ${TARGET_HINT}`)} Agent target, default claude
223
- `);
224
- }
225
- function watchUsage() {
226
- process.stdout.write(`
227
- ${c.bold("Usage:")} ${c.cyan(`${CLI_COMMAND} watch`)} ${c.dim("[flags]")}
228
-
229
- ${c.bold("Run a polling loop for one target.")}
230
- ${c.cyan(`${CLI_COMMAND} watch --target claude`)}
231
- ${c.cyan(`${CLI_COMMAND} watch --push --target codex`)}
232
-
233
- ${c.bold("Flags")}
234
- ${c.cyan("--push")} Publish/update local skill changes
235
- ${c.cyan("--no-yolo")} Adopt local skills without auto-publishing new ones
236
- ${c.cyan("--once")} Run one cycle and exit
237
- ${c.cyan("--interval <seconds>")} Poll interval, minimum 10
238
- ${c.cyan(`--target ${TARGET_HINT}`)} Agent target, default claude
239
- `);
240
- }
241
- function doctorUsage() {
242
- process.stdout.write(`
243
- ${c.bold("Usage:")} ${c.cyan(`${CLI_COMMAND} doctor`)} ${c.dim("[flags]")}
244
-
245
- ${c.bold("Check auth, MCP registration, local folders, and CLI freshness.")}
246
- ${c.cyan(`${CLI_COMMAND} doctor --target claude`)}
247
- ${c.cyan(`${CLI_COMMAND} doctor --target opencode`)}
248
-
249
- ${c.bold("Flags")}
250
- ${c.cyan(`--target ${TARGET_HINT}`)} Agent target, default claude
251
- `);
252
- }
253
- function daemonUsage() {
254
- process.stdout.write(`
255
- ${c.bold("Usage:")} ${c.cyan(`${CLI_COMMAND} daemon`)} ${c.dim("<command> [flags]")}
256
-
257
- ${c.bold("Install and inspect always-on Floom sync.")}
258
- ${c.cyan(`${CLI_COMMAND} daemon install --target all --interval 300 --timeout 180`)}
259
- ${c.cyan(`${CLI_COMMAND} daemon status --json`)}
260
- ${c.cyan(`${CLI_COMMAND} daemon logs --tail 100`)}
261
- ${c.cyan(`${CLI_COMMAND} daemon run --foreground --target all`)}
262
-
263
- ${c.bold("Commands")}
264
- ${c.cyan("install")} Write systemd or launchd service definition
265
- ${c.cyan("status")} Print latest daemon status
266
- ${c.cyan("logs")} Print daemon log tail
267
- ${c.cyan("run")} Run the daemon loop in the foreground
268
-
269
- ${c.bold("Flags")}
270
- ${c.cyan("--target <target|all>")} claude|codex|cursor|opencode|kimi|all
271
- ${c.cyan("--interval <seconds>")} Poll interval, minimum 30, default 300
272
- ${c.cyan("--timeout <seconds>")} Per-command timeout, minimum 30, default 180
273
- ${c.cyan("--push / --no-push")} Push/adopt local changes, default push
274
- ${c.cyan("--yolo / --no-yolo")} Auto-publish changed local skills, default yolo
275
- ${c.cyan("--dry-run")} Print generated service file without writing
276
- ${c.cyan("--json")} Machine-readable status output
277
- `);
278
- }
279
- function auditUsage() {
280
- process.stdout.write(`
281
- ${c.bold("Usage:")} ${c.cyan(`${CLI_COMMAND} audit skills`)} ${c.dim("[flags]")}
282
-
283
- ${c.bold("Find low-quality, duplicate, fixture, and malformed owned skills.")}
284
- ${c.cyan(`${CLI_COMMAND} audit skills`)}
285
- ${c.cyan(`${CLI_COMMAND} audit skills --json`)}
286
- ${c.cyan(`${CLI_COMMAND} audit skills --fix-plan`)}
287
- ${c.cyan(`${CLI_COMMAND} audit skills --archive-plan plan.json`)} ${c.dim("(dry-run)")}
288
- ${c.cyan(`${CLI_COMMAND} audit skills --archive-plan plan.json --yes`)}
289
-
290
- ${c.bold("Flags")}
291
- ${c.cyan("--json")} Full machine-readable report
292
- ${c.cyan("--fix-plan")} Include archive recommendations, without applying them
293
- ${c.cyan("--archive-plan <file>")} Preview or apply a reviewed archive plan
294
- ${c.cyan("--yes")} Apply the archive plan instead of dry-run preview
295
- `);
296
- }
297
- function feedbackUsage() {
298
- process.stdout.write(`
299
- ${c.bold("Usage:")} ${c.cyan(`${CLI_COMMAND} feedback`)} ${c.dim("--kind <kind> --message <message> [flags]")}
300
-
301
- ${c.bold("Send agent feedback to Floom.")}
302
- ${c.cyan(`${CLI_COMMAND} feedback --kind bug --message "MCP sync missed a file"`)}
303
- ${c.cyan(`${CLI_COMMAND} feedback --kind idea --message "Add team defaults" --target codex --skill support-tone`)}
304
-
305
- ${c.bold("Flags")}
306
- ${c.cyan("--kind <kind>")} Feedback type, for example bug, idea, friction, praise
307
- ${c.cyan("--message <text>")} Required non-interactive feedback message
308
- ${c.cyan("--target <target>")} Optional agent target
309
- ${c.cyan("--skill <slug>")} Optional related skill slug
310
- ${c.cyan("--json")} Machine-readable success or error
311
- `);
312
- }
313
- function isHelpArg(value) {
314
- return value === "--help" || value === "-h" || value === "help";
315
- }
316
- function subcommandUsage(cmd) {
317
- switch (cmd) {
318
- case "add":
319
- case "install":
320
- addUsage();
321
- return true;
322
- case "sync":
323
- syncUsage();
324
- return true;
325
- case "watch":
326
- watchUsage();
327
- return true;
328
- case "doctor":
329
- doctorUsage();
330
- return true;
331
- case "daemon":
332
- daemonUsage();
333
- return true;
334
- case "share":
335
- shareUsage();
336
- return true;
337
- case "audit":
338
- auditUsage();
339
- return true;
340
- case "feedback":
341
- feedbackUsage();
342
- return true;
343
- case "launch":
344
- commandUsage();
345
- return true;
346
- case "library":
347
- case "lib":
348
- libraryUsage();
349
- return true;
350
- case "move":
351
- moveUsage();
352
- return true;
353
- default:
354
- commandUsage();
355
- return true;
356
- }
357
- }
358
- const ASSET_TYPES = new Set(["knowledge", "instruction", "workflow", "skill"]);
359
- const INSTALL_TARGETS = new Set([
360
- "claude_skill",
361
- "memory",
362
- "rule",
363
- "codex_instruction",
364
- "opencode_instruction",
365
- "cursor_rule",
366
- "other",
367
- ]);
368
- const VERSION_RE = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$/;
369
- function readFlagValue(argv, index, flag) {
370
- const current = argv[index] ?? "";
371
- if (current.startsWith(`${flag}=`))
372
- return { value: current.slice(flag.length + 1), nextIndex: index };
373
- const value = argv[index + 1];
374
- if (!value || value.startsWith("--"))
375
- throw new FloomError(`Missing value for ${flag}.`);
376
- return { value, nextIndex: index + 1 };
377
- }
378
- function parseFlags(argv) {
379
- const out = { update: false, rest: [] };
380
- let visibilityFlag = null;
381
- for (let i = 0; i < argv.length; i++) {
382
- const a = argv[i] ?? "";
383
- if (a === "--public" || a === "--private" || a === "--unlisted") {
384
- const nextVisibility = a.slice(2);
385
- if (visibilityFlag && visibilityFlag !== nextVisibility) {
386
- throw new FloomError("Conflicting visibility flags.", "Use only one of: --public, --private, or --unlisted.");
387
- }
388
- visibilityFlag = nextVisibility;
389
- out.visibility = nextVisibility;
390
- }
391
- else if (a === "--update" || a.startsWith("--update=")) {
392
- out.update = true;
393
- const inline = a.startsWith("--update=") ? a.slice("--update=".length) : undefined;
394
- const next = argv[i + 1];
395
- const value = inline ?? (next && !next.startsWith("--") ? next : undefined);
396
- if (value) {
397
- out.updateSlug = value;
398
- if (!inline)
399
- i += 1;
400
- }
401
- else {
402
- }
403
- }
404
- else if (a === "--share" || a.startsWith("--share=")) {
405
- throw new FloomError(V1_NOT_AVAILABLE, `\`${CLI_COMMAND} publish --share\` is planned for a later Floom release.`);
406
- }
407
- else if (a === "--type" || a.startsWith("--type=")) {
408
- const { value, nextIndex } = readFlagValue(argv, i, "--type");
409
- if (!ASSET_TYPES.has(value)) {
410
- throw new FloomError(`Invalid --type: ${value}`, "Use one of: knowledge, instruction, workflow, skill.");
411
- }
412
- out.assetType = value;
413
- i = nextIndex;
414
- }
415
- else if (a === "--installs-as" || a.startsWith("--installs-as=")) {
416
- const { value, nextIndex } = readFlagValue(argv, i, "--installs-as");
417
- if (!INSTALL_TARGETS.has(value)) {
418
- throw new FloomError(`Invalid --installs-as: ${value}`, "Use one of: claude_skill, memory, rule, codex_instruction, opencode_instruction, cursor_rule, other.");
419
- }
420
- out.installsAs = value;
421
- i = nextIndex;
422
- }
423
- else if (a === "--skill-version" || a.startsWith("--skill-version=")) {
424
- const { value, nextIndex } = readFlagValue(argv, i, "--skill-version");
425
- if (!VERSION_RE.test(value)) {
426
- throw new FloomError(`Invalid --skill-version: ${value}`, "Use 1-64 characters: letters, numbers, dots, underscores, plus, or hyphen.");
427
- }
428
- out.version = value;
429
- i = nextIndex;
430
- }
431
- else if (a === "--version" || a.startsWith("--version=")) {
432
- throw new FloomError("`--version` prints the Floom CLI version at the top level.", "For skill version labels, use `--skill-version <label>`.");
433
- }
434
- else if (a.startsWith("--")) {
435
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} publish my-skill --type instruction --public\`.`);
436
- }
437
- else
438
- out.rest.push(a);
439
- }
440
- return out;
441
- }
442
- function parseShareFlags(argv) {
443
- const out = { list: false, add: [], remove: [] };
444
- for (let i = 0; i < argv.length; i++) {
445
- const a = argv[i] ?? "";
446
- if (a === "--list") {
447
- out.list = true;
448
- }
449
- else if (a === "--add" || a.startsWith("--add=")) {
450
- const { value, nextIndex } = readFlagValue(argv, i, "--add");
451
- out.add.push(value);
452
- i = nextIndex;
453
- }
454
- else if (a === "--remove" || a.startsWith("--remove=")) {
455
- const { value, nextIndex } = readFlagValue(argv, i, "--remove");
456
- out.remove.push(value);
457
- i = nextIndex;
458
- }
459
- else if (a.startsWith("--")) {
460
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} share <slug> --add person@example.com\`.`);
461
- }
462
- else if (!out.slug) {
463
- out.slug = a;
464
- }
465
- else {
466
- throw new FloomError(`Unexpected argument: ${a}`, `Try \`${CLI_COMMAND} share <slug> --add person@example.com\`.`);
467
- }
468
- }
469
- if (!out.slug)
470
- throw new FloomError("Missing skill slug.", `Try \`${CLI_COMMAND} share <slug> --add person@example.com\`.`);
471
- if (out.list && (out.add.length > 0 || out.remove.length > 0)) {
472
- throw new FloomError("Conflicting share flags.", "Use --list by itself, or use --add/--remove.");
473
- }
474
- if (!out.list && out.add.length === 0 && out.remove.length === 0) {
475
- throw new FloomError("Missing share action.", `Try \`${CLI_COMMAND} share <slug> --add person@example.com\`.`);
476
- }
477
- return out;
478
- }
479
- function parseInitArgs(argv) {
480
- let file;
481
- let template;
482
- for (let i = 0; i < argv.length; i++) {
483
- const a = argv[i] ?? "";
484
- if (a === "--template" || a.startsWith("--template=")) {
485
- const { value, nextIndex } = readFlagValue(argv, i, "--template");
486
- if (!INIT_TEMPLATES.includes(value)) {
487
- throw new FloomError(`Invalid --template: ${value}`, `Use one of: ${INIT_TEMPLATES.join(", ")}.`);
488
- }
489
- template = value;
490
- i = nextIndex;
491
- continue;
492
- }
493
- if (a.startsWith("--")) {
494
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} init my-skill\`.`);
495
- }
496
- if (file) {
497
- throw new FloomError(`Unexpected argument: ${a}`, `Try \`${CLI_COMMAND} init my-skill\`.`);
498
- }
499
- file = a;
500
- }
501
- return { ...(file ? { file } : {}), ...(template ? { template } : {}) };
502
- }
503
- function parseTargetFlag(value) {
504
- if (isAgentTarget(value))
505
- return value;
506
- throw new FloomError("Invalid --target.", `Use ${TARGET_HINT}.`);
507
- }
508
- function parseListFlags(argv) {
509
- const out = { json: false };
510
- for (const a of argv) {
511
- if (a === "--json")
512
- out.json = true;
513
- else if (a.startsWith("--")) {
514
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} list --help\` for usage.`);
515
- }
516
- else {
517
- throw new FloomError(`Unexpected argument: ${a}`, `Try \`${CLI_COMMAND} list --json\`.`);
518
- }
519
- }
520
- return out;
521
- }
522
- function parseSyncFlags(argv) {
523
- const out = {};
524
- for (let i = 0; i < argv.length; i++) {
525
- const a = argv[i] ?? "";
526
- if (a === "--target" || a.startsWith("--target=")) {
527
- const { value, nextIndex } = readFlagValue(argv, i, "--target");
528
- out.target = parseTargetFlag(value);
529
- i = nextIndex;
530
- }
531
- else if (a.startsWith("--")) {
532
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} sync --target claude\`.`);
533
- }
534
- else {
535
- throw new FloomError(`Unexpected argument: ${a}`, `Try \`${CLI_COMMAND} sync --target claude\`.`);
536
- }
537
- }
538
- return out;
539
- }
540
- function parseInfoFlags(argv) {
541
- const out = { json: false };
542
- for (const a of argv) {
543
- if (a === "--json")
544
- out.json = true;
545
- else if (a.startsWith("--"))
546
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} info <slug> --json\`.`);
547
- else if (!out.slug)
548
- out.slug = a;
549
- else
550
- throw new FloomError(`Unexpected argument: ${a}`, `Try \`${CLI_COMMAND} info <slug> --json\`.`);
551
- }
552
- return out;
553
- }
554
- function parseFeedbackFlags(argv) {
555
- const out = { json: false };
556
- for (let i = 0; i < argv.length; i++) {
557
- const a = argv[i] ?? "";
558
- if (a === "--json") {
559
- out.json = true;
560
- }
561
- else if (a === "--kind" || a.startsWith("--kind=")) {
562
- const { value, nextIndex } = readFlagValue(argv, i, "--kind");
563
- out.kind = value.trim();
564
- i = nextIndex;
565
- }
566
- else if (a === "--message" || a.startsWith("--message=")) {
567
- const { value, nextIndex } = readFlagValue(argv, i, "--message");
568
- out.message = value.trim();
569
- i = nextIndex;
570
- }
571
- else if (a === "--target" || a.startsWith("--target=")) {
572
- const { value, nextIndex } = readFlagValue(argv, i, "--target");
573
- out.target = value.trim();
574
- i = nextIndex;
575
- }
576
- else if (a === "--skill" || a.startsWith("--skill=")) {
577
- const { value, nextIndex } = readFlagValue(argv, i, "--skill");
578
- out.skill = value.trim();
579
- i = nextIndex;
580
- }
581
- else if (a.startsWith("--")) {
582
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} feedback --kind bug --message "What happened"\`.`);
583
- }
584
- else {
585
- throw new FloomError(`Unexpected argument: ${a}`, `Use \`${CLI_COMMAND} feedback --kind bug --message "What happened"\`.`);
586
- }
587
- }
588
- if (!out.kind)
589
- throw new FloomError("Missing --kind.", `Try \`${CLI_COMMAND} feedback --kind bug --message "What happened"\`.`);
590
- if (!out.message)
591
- throw new FloomError("Missing --message.", "Feedback is non-interactive; pass the message with --message.");
592
- if (out.kind.length > 64)
593
- throw new FloomError("Invalid --kind.", "Use 1-64 characters.");
594
- if (out.message.length > 4000)
595
- throw new FloomError("Invalid --message.", "Use 1-4000 characters.");
596
- if (out.target !== undefined && out.target.length > 128)
597
- throw new FloomError("Invalid --target.", "Use at most 128 characters.");
598
- if (out.skill !== undefined && out.skill.length > 128)
599
- throw new FloomError("Invalid --skill.", "Use at most 128 characters.");
600
- return out;
601
- }
602
- function parseAddArgs(argv) {
603
- let slug;
604
- let target;
605
- let setup = false;
606
- let force = false;
607
- for (let i = 0; i < argv.length; i++) {
608
- const a = argv[i] ?? "";
609
- if (a === "--target" || a.startsWith("--target=")) {
610
- const { value, nextIndex } = readFlagValue(argv, i, "--target");
611
- target = parseTargetFlag(value);
612
- i = nextIndex;
613
- }
614
- else if (a === "--setup") {
615
- setup = true;
616
- }
617
- else if (a === "--force") {
618
- force = true;
619
- }
620
- else if (a.startsWith("--")) {
621
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} add <url-or-slug> --setup\`.`);
622
- }
623
- else if (slug) {
624
- throw new FloomError(`Unexpected argument: ${a}`, `Try \`${CLI_COMMAND} add <url-or-slug> --setup\`.`);
625
- }
626
- else
627
- slug = a;
628
- }
629
- if (!slug) {
630
- throw new FloomError("Missing skill slug.", `Try: \`${CLI_COMMAND} add <url-or-slug> --setup\``);
631
- }
632
- return target ? { slug, target, setup, force } : { slug, setup, force };
633
- }
634
- function parseSearchFlags(argv) {
635
- const out = { json: false };
636
- const terms = [];
637
- for (let i = 0; i < argv.length; i++) {
638
- const a = argv[i] ?? "";
639
- if (a === "--json")
640
- out.json = true;
641
- else if (a === "--library" || a.startsWith("--library=")) {
642
- const { value, nextIndex } = readFlagValue(argv, i, "--library");
643
- out.library = value;
644
- i = nextIndex;
645
- }
646
- else if (a === "--type" || a.startsWith("--type=")) {
647
- const { value, nextIndex } = readFlagValue(argv, i, "--type");
648
- if (!ASSET_TYPES.has(value)) {
649
- throw new FloomError(`Invalid --type: ${value}`, "Use one of: knowledge, instruction, workflow, skill.");
650
- }
651
- out.type = value;
652
- i = nextIndex;
653
- }
654
- else if (a.startsWith("--")) {
655
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} search "support tone" --type instruction\`.`);
656
- }
657
- else {
658
- terms.push(a);
659
- }
660
- }
661
- out.query = terms.join(" ").trim();
662
- return out;
663
- }
664
- function parseDeleteFlags(argv) {
665
- const out = { yes: false };
666
- for (const a of argv) {
667
- if (a === "--yes" || a === "-y")
668
- out.yes = true;
669
- else if (a.startsWith("--"))
670
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} delete <slug> --yes\`.`);
671
- else if (!out.slug)
672
- out.slug = a;
673
- else
674
- throw new FloomError(`Unexpected argument: ${a}`, `Try \`${CLI_COMMAND} delete <slug> --yes\`.`);
675
- }
676
- return out;
677
- }
678
- function rejectArgs(argv, usageHint) {
679
- const arg = argv[0];
680
- if (!arg)
681
- return;
682
- if (arg.startsWith("--"))
683
- throw new FloomError(`Unknown flag: ${arg}`, usageHint);
684
- throw new FloomError(`Unexpected argument: ${arg}`, usageHint);
685
- }
686
- function parseSetupFlags(argv) {
687
- const out = { dryRun: false, yes: false };
688
- for (let i = 0; i < argv.length; i++) {
689
- const a = argv[i] ?? "";
690
- if (a === "--dry-run" || a === "--preview")
691
- out.dryRun = true;
692
- else if (a === "--yes" || a === "-y")
693
- out.yes = true;
694
- else if (a === "--global")
695
- out.global = true;
696
- else if (a === "--target" || a.startsWith("--target=")) {
697
- const { value, nextIndex } = readFlagValue(argv, i, "--target");
698
- out.target = parseTargetFlag(value);
699
- i = nextIndex;
700
- }
701
- else if (a === "--file" || a.startsWith("--file=")) {
702
- const { value, nextIndex } = readFlagValue(argv, i, "--file");
703
- out.file = value;
704
- i = nextIndex;
705
- }
706
- else if (a.startsWith("--")) {
707
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} setup --target codex --dry-run\`.`);
708
- }
709
- else if (!out.file)
710
- out.file = a;
711
- else
712
- throw new FloomError(`Unexpected argument: ${a}`, `Try \`${CLI_COMMAND} setup --target claude --yes\`.`);
713
- }
714
- return out;
715
- }
716
- function parseDoctorFlags(argv) {
717
- const out = {};
718
- for (let i = 0; i < argv.length; i++) {
719
- const a = argv[i] ?? "";
720
- if (a === "--target" || a.startsWith("--target=")) {
721
- const { value, nextIndex } = readFlagValue(argv, i, "--target");
722
- out.target = parseTargetFlag(value);
723
- i = nextIndex;
724
- }
725
- else if (a.startsWith("--")) {
726
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} doctor --target codex\`.`);
727
- }
728
- else {
729
- throw new FloomError(`Unexpected argument: ${a}`, `Try \`${CLI_COMMAND} doctor --target claude\`.`);
730
- }
731
- }
732
- return out;
733
- }
734
- function parseStatusFlags(argv) {
735
- const out = { json: false };
736
- for (const a of argv) {
737
- if (a === "--json")
738
- out.json = true;
739
- else if (a.startsWith("--"))
740
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} status --json\`.`);
741
- else
742
- throw new FloomError(`Unexpected argument: ${a}`, `Try \`${CLI_COMMAND} status --json\`.`);
743
- }
744
- return out;
745
- }
746
- function parseAuditFlags(argv) {
747
- const [subcommand, ...rest] = argv;
748
- if (subcommand !== "skills") {
749
- throw new FloomError("Unknown audit command.", `Try \`${CLI_COMMAND} audit skills --json\`.`);
750
- }
751
- const out = { json: false, fixPlan: false, yes: false };
752
- for (let i = 0; i < rest.length; i++) {
753
- const a = rest[i] ?? "";
754
- if (a === "--json")
755
- out.json = true;
756
- else if (a === "--fix-plan")
757
- out.fixPlan = true;
758
- else if (a === "--archive-plan" || a.startsWith("--archive-plan=")) {
759
- const { value, nextIndex } = readFlagValue(rest, i, "--archive-plan");
760
- out.archivePlan = value;
761
- i = nextIndex;
762
- }
763
- else if (a === "--yes")
764
- out.yes = true;
765
- else if (a.startsWith("--"))
766
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} audit skills --json\`.`);
767
- else
768
- throw new FloomError(`Unexpected argument: ${a}`, `Try \`${CLI_COMMAND} audit skills --json\`.`);
769
- }
770
- return out;
771
- }
772
- function parseDaemonFlags(argv) {
773
- const [command, ...rest] = argv;
774
- const allowedCommands = new Set(["install", "uninstall", "status", "logs", "restart", "run"]);
775
- if (!command || !allowedCommands.has(command)) {
776
- throw new FloomError("Missing daemon command.", `Try \`${CLI_COMMAND} daemon install --target all\`.`);
777
- }
778
- const out = {
779
- command: command,
780
- target: "all",
781
- intervalSeconds: 300,
782
- timeoutSeconds: 180,
783
- push: true,
784
- yolo: true,
785
- foreground: false,
786
- dryRun: false,
787
- json: false,
788
- tail: 100,
789
- };
790
- for (let i = 0; i < rest.length; i++) {
791
- const a = rest[i] ?? "";
792
- if (a === "--target" || a.startsWith("--target=")) {
793
- const { value, nextIndex } = readFlagValue(rest, i, "--target");
794
- out.target = parseDaemonTarget(value);
795
- i = nextIndex;
796
- }
797
- else if (a === "--interval" || a.startsWith("--interval=")) {
798
- const { value, nextIndex } = readFlagValue(rest, i, "--interval");
799
- const parsed = Number(value);
800
- if (!Number.isInteger(parsed))
801
- throw new FloomError("Invalid --interval.", "Use an integer number of seconds.");
802
- out.intervalSeconds = parsed;
803
- i = nextIndex;
804
- }
805
- else if (a === "--timeout" || a.startsWith("--timeout=")) {
806
- const { value, nextIndex } = readFlagValue(rest, i, "--timeout");
807
- const parsed = Number(value);
808
- if (!Number.isInteger(parsed))
809
- throw new FloomError("Invalid --timeout.", "Use an integer number of seconds.");
810
- out.timeoutSeconds = parsed;
811
- i = nextIndex;
812
- }
813
- else if (a === "--push") {
814
- out.push = true;
815
- }
816
- else if (a === "--no-push") {
817
- out.push = false;
818
- }
819
- else if (a === "--yolo") {
820
- out.yolo = true;
821
- }
822
- else if (a === "--no-yolo") {
823
- out.yolo = false;
824
- }
825
- else if (a === "--foreground") {
826
- out.foreground = true;
827
- }
828
- else if (a === "--dry-run") {
829
- out.dryRun = true;
830
- }
831
- else if (a === "--json") {
832
- out.json = true;
833
- }
834
- else if (a === "--tail" || a.startsWith("--tail=")) {
835
- const { value, nextIndex } = readFlagValue(rest, i, "--tail");
836
- const parsed = Number(value);
837
- if (!Number.isInteger(parsed) || parsed < 1)
838
- throw new FloomError("Invalid --tail.", "Use a positive integer.");
839
- out.tail = parsed;
840
- i = nextIndex;
841
- }
842
- else if (a.startsWith("--")) {
843
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} daemon --help\`.`);
844
- }
845
- else {
846
- throw new FloomError(`Unexpected argument: ${a}`, `Try \`${CLI_COMMAND} daemon --help\`.`);
847
- }
848
- }
849
- return normalizeDaemonOptions(out);
850
- }
851
- function normalizeFolder(value) {
852
- const normalized = value.trim().replace(/\\/g, "/").replace(/\/+/g, "/");
853
- if (normalized === "root" || normalized === "/" || normalized === ".")
854
- return null;
855
- if (normalized.startsWith("/")) {
856
- throw new FloomError("Invalid --folder: use a relative sync folder.", "Floom folders are portable library paths like `support/tone`, not absolute filesystem paths like `/tmp/floom-move-target`.");
857
- }
858
- if (normalized === ".." ||
859
- normalized.startsWith("../") ||
860
- normalized.includes("/../") ||
861
- normalized.endsWith("/..")) {
862
- throw new FloomError("Invalid --folder: path traversal is not allowed.", "Use a relative sync folder like `support/tone`, or use `--root`.");
863
- }
864
- const relative = normalized.startsWith("./") ? normalized.slice(2) : normalized;
865
- return relative || null;
866
- }
867
- function parseFolderTagFlags(argv) {
868
- const out = { tags: [], rest: [] };
869
- for (let i = 0; i < argv.length; i++) {
870
- const a = argv[i] ?? "";
871
- if (a === "--folder" || a.startsWith("--folder=")) {
872
- const { value, nextIndex } = readFlagValue(argv, i, "--folder");
873
- out.folder = normalizeFolder(value);
874
- i = nextIndex;
875
- }
876
- else if (a === "--root") {
877
- out.folder = null;
878
- }
879
- else if (a === "--tag" || a.startsWith("--tag=")) {
880
- const { value, nextIndex } = readFlagValue(argv, i, "--tag");
881
- out.tags.push(value);
882
- i = nextIndex;
883
- }
884
- else if (a === "--tags" || a.startsWith("--tags=")) {
885
- const { value, nextIndex } = readFlagValue(argv, i, "--tags");
886
- out.tags.push(...value.split(",").map((tag) => tag.trim()).filter(Boolean));
887
- i = nextIndex;
888
- }
889
- else if (a.startsWith("--")) {
890
- throw new FloomError(`Unknown flag: ${a}`, "Use --folder <path>, --root, --tag <tag>, or --tags a,b.");
891
- }
892
- else {
893
- out.rest.push(a);
894
- }
895
- }
896
- return out;
897
- }
898
- function parseLibraryCreateFlags(argv) {
899
- const out = { visibility: "unlisted" };
900
- for (let i = 0; i < argv.length; i++) {
901
- const a = argv[i] ?? "";
902
- if (a === "--name" || a.startsWith("--name=")) {
903
- const { value, nextIndex } = readFlagValue(argv, i, "--name");
904
- out.name = value;
905
- i = nextIndex;
906
- }
907
- else if (a === "--description" || a.startsWith("--description=")) {
908
- const { value, nextIndex } = readFlagValue(argv, i, "--description");
909
- out.description = value;
910
- i = nextIndex;
911
- }
912
- else if (a === "--public")
913
- out.visibility = "public";
914
- else if (a === "--private")
915
- out.visibility = "private";
916
- else if (a === "--unlisted")
917
- out.visibility = "unlisted";
918
- else if (a.startsWith("--")) {
919
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} library create team-onboarding --name "Team onboarding"\`.`);
920
- }
921
- else if (!out.slug)
922
- out.slug = a;
923
- else
924
- throw new FloomError(`Unexpected argument: ${a}`, `Try \`${CLI_COMMAND} library create <slug> --name <name>\`.`);
925
- }
926
- return out;
927
- }
928
- async function runLibrary(argv) {
929
- const [subcommand, ...rest] = argv;
930
- switch (subcommand ?? "list") {
931
- case "list": {
932
- const flags = parseListFlags(rest);
933
- await libraryList(flags);
934
- return;
935
- }
936
- case "create": {
937
- const flags = parseLibraryCreateFlags(rest);
938
- if (!flags.slug)
939
- throw new FloomError("Missing library slug.", `Try \`${CLI_COMMAND} library create team-onboarding --name "Team onboarding"\`.`);
940
- if (!flags.name)
941
- throw new FloomError("Missing --name.", `Try \`${CLI_COMMAND} library create team-onboarding --name "Team onboarding"\`.`);
942
- await libraryCreate({
943
- slug: flags.slug,
944
- name: flags.name,
945
- ...(flags.description !== undefined ? { description: flags.description } : {}),
946
- visibility: flags.visibility,
947
- });
948
- return;
949
- }
950
- case "add": {
951
- const flags = parseFolderTagFlags(rest);
952
- const [librarySlug, skillSlug] = flags.rest;
953
- if (!librarySlug || !skillSlug) {
954
- throw new FloomError("Missing library or skill slug.", `Try \`${CLI_COMMAND} library add team-onboarding support-tone --folder support\`.`);
955
- }
956
- if (flags.rest.length > 2) {
957
- throw new FloomError(`Unexpected argument: ${flags.rest[2]}`, `Try \`${CLI_COMMAND} library add team-onboarding support-tone --folder support\`.`);
958
- }
959
- await libraryAddSkill({
960
- librarySlug,
961
- skillSlug,
962
- ...(flags.folder !== undefined ? { folder: flags.folder } : {}),
963
- tags: flags.tags,
964
- });
965
- return;
966
- }
967
- case "remove":
968
- case "rm": {
969
- const [librarySlug, skillSlug] = rest;
970
- if (!librarySlug || !skillSlug) {
971
- throw new FloomError("Missing library or skill slug.", `Try \`${CLI_COMMAND} library remove team-onboarding support-tone\`.`);
972
- }
973
- if (rest.length > 2) {
974
- throw new FloomError(`Unexpected argument: ${rest[2]}`, `Try \`${CLI_COMMAND} library remove team-onboarding support-tone\`.`);
975
- }
976
- await libraryRemoveSkill(librarySlug, skillSlug);
977
- return;
978
- }
979
- case "subscribe": {
980
- const slug = rest[0];
981
- if (!slug)
982
- throw new FloomError("Missing library slug.", `Try \`${CLI_COMMAND} library subscribe superpowers\`.`);
983
- if (rest.length > 1) {
984
- throw new FloomError(`Unexpected argument: ${rest[1]}`, `Try \`${CLI_COMMAND} library subscribe superpowers\`.`);
985
- }
986
- await librarySubscribe(slug);
987
- return;
988
- }
989
- case "unsubscribe": {
990
- const slug = rest[0];
991
- if (!slug)
992
- throw new FloomError("Missing library slug.", `Try \`${CLI_COMMAND} library unsubscribe superpowers\`.`);
993
- if (rest.length > 1) {
994
- throw new FloomError(`Unexpected argument: ${rest[1]}`, `Try \`${CLI_COMMAND} library unsubscribe superpowers\`.`);
995
- }
996
- await libraryUnsubscribe(slug);
997
- return;
998
- }
999
- default:
1000
- throw new FloomError(`Unknown library command: ${subcommand}`, "Use: list, create, add, remove, subscribe, unsubscribe.");
1001
- }
1002
- }
1003
- function parseWatchFlags(argv) {
1004
- const out = { intervalSeconds: 60, push: false, yolo: true, once: false, target: "claude" };
1005
- for (let i = 0; i < argv.length; i++) {
1006
- const a = argv[i] ?? "";
1007
- if (a === "--interval" || a.startsWith("--interval=")) {
1008
- const { value, nextIndex } = readFlagValue(argv, i, "--interval");
1009
- const interval = Number(value);
1010
- if (!Number.isInteger(interval) || interval < 10) {
1011
- throw new FloomError("Invalid --interval.", "Use an integer number of seconds, minimum 10.");
1012
- }
1013
- out.intervalSeconds = interval;
1014
- i = nextIndex;
1015
- }
1016
- else if (a === "--push") {
1017
- out.push = true;
1018
- }
1019
- else if (a === "--no-yolo") {
1020
- out.yolo = false;
1021
- }
1022
- else if (a === "--once") {
1023
- out.once = true;
1024
- }
1025
- else if (a === "--target" || a.startsWith("--target=")) {
1026
- const { value, nextIndex } = readFlagValue(argv, i, "--target");
1027
- out.target = parseTargetFlag(value);
1028
- i = nextIndex;
1029
- }
1030
- else if (a.startsWith("--")) {
1031
- throw new FloomError(`Unknown flag: ${a}`, `Try \`${CLI_COMMAND} watch --push --target claude\`.`);
1032
- }
1033
- else {
1034
- throw new FloomError(`Unexpected argument: ${a}`, `Try \`${CLI_COMMAND} watch --push --target claude\`.`);
1035
- }
1036
- }
1037
- return out;
1038
- }
1039
- function notAvailable(feature) {
1040
- throw new FloomError(V1_NOT_AVAILABLE, `${feature} is planned for a later Floom release.`);
1041
- }
1042
- function parseSingleFileArg(argv, usageHint) {
1043
- let file;
1044
- for (const a of argv) {
1045
- if (a.startsWith("--"))
1046
- throw new FloomError(`Unknown flag: ${a}`, usageHint);
1047
- if (file)
1048
- throw new FloomError(`Unexpected argument: ${a}`, usageHint);
1049
- file = a;
1050
- }
1051
- if (!file)
1052
- throw new FloomError("Missing file argument.", usageHint);
1053
- return file;
1054
- }
1055
- function sleep(ms, signal) {
1056
- if (signal.aborted)
1057
- return Promise.resolve();
1058
- return new Promise((resolve) => {
1059
- const timer = setTimeout(resolve, ms);
1060
- signal.addEventListener("abort", () => {
1061
- clearTimeout(timer);
1062
- resolve();
1063
- }, { once: true });
1064
- });
1065
- }
1066
- async function watch(intervalSeconds, target, once) {
1067
- const cfg = await readConfig();
1068
- if (!cfg) {
1069
- throw new FloomError("Not signed in.", `Run \`${CLI_COMMAND} login\` before \`${CLI_COMMAND} watch\`, or use \`${CLI_COMMAND} add <link>\` without an account.`);
1070
- }
1071
- const controller = new AbortController();
1072
- let stopping = false;
1073
- const stop = () => {
1074
- if (stopping)
1075
- return;
1076
- stopping = true;
1077
- controller.abort();
1078
- process.stdout.write(`\n${symbols.bullet} Stopping floom watch\n`);
1079
- process.exit(0);
1080
- };
1081
- process.on("SIGINT", stop);
1082
- process.on("SIGTERM", stop);
1083
- process.stdout.write(`${symbols.bullet} Watching Floom sync for ${target} every ${intervalSeconds}s. Press Ctrl-C to stop.\n`);
1084
- while (!controller.signal.aborted) {
1085
- await sync({ spinner: false, quietUnchanged: true, target });
1086
- if (once)
1087
- return;
1088
- await sleep(intervalSeconds * 1000, controller.signal);
1089
- }
1090
- }
1091
- async function main() {
1092
- const [, , cmd, ...rest] = process.argv;
1093
- // Update notifier — runs in background, prints at process exit if a newer
1094
- // version is available. Disabled in CI / non-TTY by default.
1095
- if (cmd !== "watch") {
1096
- try {
1097
- updateNotifier({ pkg: PKG, updateCheckInterval: 1000 * 60 * 60 * 24 }).notify({
1098
- defer: true,
1099
- isGlobal: true,
1100
- message: `${symbols.bullet} ${c.bold("floom")} v{latestVersion} available — run \`npm i -g {packageName}\` to update.`,
1101
- });
1102
- }
1103
- catch {
1104
- // never block on update-notifier
1105
- }
1106
- }
1107
- if (rest.some(isHelpArg)) {
1108
- subcommandUsage(cmd);
1109
- return;
1110
- }
1111
- try {
1112
- switch (cmd) {
1113
- case undefined:
1114
- usage();
1115
- return;
1116
- case "--help":
1117
- case "-h":
1118
- case "help":
1119
- commandUsage();
1120
- return;
1121
- case "commands":
1122
- rejectArgs(rest, `Try \`${CLI_COMMAND} commands\`.`);
1123
- commandUsage();
1124
- return;
1125
- case "--version":
1126
- case "-v":
1127
- process.stdout.write(`${CLI_VERSION}\n`);
1128
- return;
1129
- case "login":
1130
- rejectArgs(rest, `Try \`${CLI_COMMAND} login\`.`);
1131
- await login();
1132
- return;
1133
- case "logout":
1134
- rejectArgs(rest, `Try \`${CLI_COMMAND} logout\`.`);
1135
- await deleteConfig();
1136
- process.stdout.write(`\n${symbols.ok} Signed out\n\n`);
1137
- return;
1138
- case "whoami":
1139
- rejectArgs(rest, `Try \`${CLI_COMMAND} whoami\`.`);
1140
- await whoami();
1141
- return;
1142
- case "init": {
1143
- const flags = parseInitArgs(rest);
1144
- await init(flags.file, flags.template ? { template: flags.template } : {});
1145
- return;
1146
- }
1147
- case "publish": {
1148
- const flags = parseFlags(rest);
1149
- const file = flags.rest[0];
1150
- if (!file) {
1151
- throw new FloomError("Missing file argument.", `Try: \`${CLI_COMMAND} publish my-skill\``);
1152
- }
1153
- if (flags.rest.length > 1) {
1154
- throw new FloomError(`Unexpected argument: ${flags.rest[1]}`, `Try: \`${CLI_COMMAND} publish my-skill\``);
1155
- }
1156
- await publish({
1157
- file,
1158
- update: flags.update,
1159
- ...(flags.visibility ? { visibility: flags.visibility } : {}),
1160
- ...(flags.updateSlug ? { updateSlug: flags.updateSlug } : {}),
1161
- ...(flags.assetType ? { assetType: flags.assetType } : {}),
1162
- ...(flags.installsAs ? { installsAs: flags.installsAs } : {}),
1163
- ...(flags.version ? { version: flags.version } : {}),
1164
- });
1165
- return;
1166
- }
1167
- case "scan": {
1168
- const file = parseSingleFileArg(rest, `Try \`${CLI_COMMAND} scan my-skill\`.`);
1169
- await scanSkill(file);
1170
- return;
1171
- }
1172
- case "share":
1173
- {
1174
- const flags = parseShareFlags(rest);
1175
- if (flags.list) {
1176
- await share({ slug: flags.slug ?? "", kind: "list" });
1177
- }
1178
- else {
1179
- await share({ slug: flags.slug ?? "", kind: "patch", add: flags.add, remove: flags.remove });
1180
- }
1181
- }
1182
- return;
1183
- case "list": {
1184
- const flags = parseListFlags(rest);
1185
- await list(flags);
1186
- return;
1187
- }
1188
- case "info":
1189
- {
1190
- const flags = parseInfoFlags(rest);
1191
- await info({ slug: flags.slug ?? "", json: flags.json });
1192
- }
1193
- return;
1194
- case "search": {
1195
- const flags = parseSearchFlags(rest);
1196
- if (!flags.query) {
1197
- throw new FloomError("Missing search query.", `Try: \`${CLI_COMMAND} search "support tone"\`.`);
1198
- }
1199
- await search({
1200
- query: flags.query,
1201
- ...(flags.library ? { library: flags.library } : {}),
1202
- ...(flags.type ? { type: flags.type } : {}),
1203
- json: flags.json,
1204
- });
1205
- return;
1206
- }
1207
- case "feedback": {
1208
- const flags = parseFeedbackFlags(rest);
1209
- await feedback({
1210
- kind: flags.kind ?? "",
1211
- message: flags.message ?? "",
1212
- ...(flags.target ? { target: flags.target } : {}),
1213
- ...(flags.skill ? { skill: flags.skill } : {}),
1214
- json: flags.json,
1215
- });
1216
- return;
1217
- }
1218
- case "launch": {
1219
- const sub = rest[0];
1220
- if (sub !== "gate")
1221
- throw new FloomError("Unknown launch command.", `Try \`${CLI_COMMAND} launch gate --json\`.`);
1222
- const args = rest.slice(1);
1223
- const json = args.includes("--json");
1224
- rejectArgs(args.filter((arg) => arg !== "--json"), `Try \`${CLI_COMMAND} launch gate --json\`.`);
1225
- await launchGate({ json });
1226
- return;
1227
- }
1228
- case "status":
1229
- await status(parseStatusFlags(rest));
1230
- return;
1231
- case "add":
1232
- case "install": {
1233
- const flags = parseAddArgs(rest);
1234
- await install(flags.slug, {
1235
- ...(flags.target ? { target: flags.target } : {}),
1236
- setup: flags.setup,
1237
- force: flags.force,
1238
- });
1239
- if (flags.setup) {
1240
- await setupAgent({ target: flags.target ?? "claude", dryRun: false, yes: true });
1241
- }
1242
- return;
1243
- }
1244
- case "sync":
1245
- await sync(parseSyncFlags(rest));
1246
- return;
1247
- case "setup":
1248
- case "connect": {
1249
- const flags = parseSetupFlags(rest);
1250
- await setupAgent(flags);
1251
- return;
1252
- }
1253
- case "watch": {
1254
- const flags = parseWatchFlags(rest);
1255
- if (flags.push) {
1256
- await watchPush(flags.intervalSeconds, {
1257
- target: flags.target,
1258
- yolo: flags.yolo,
1259
- once: flags.once,
1260
- });
1261
- return;
1262
- }
1263
- await watch(flags.intervalSeconds, flags.target, flags.once);
1264
- return;
1265
- }
1266
- case "daemon":
1267
- await daemon(parseDaemonFlags(rest));
1268
- return;
1269
- case "delete":
1270
- case "rm": {
1271
- const flags = parseDeleteFlags(rest);
1272
- await deleteSkill({ slug: flags.slug ?? "", yes: flags.yes });
1273
- return;
1274
- }
1275
- case "library":
1276
- case "lib":
1277
- await runLibrary(rest);
1278
- return;
1279
- case "audit":
1280
- await auditSkills(parseAuditFlags(rest));
1281
- return;
1282
- case "move": {
1283
- const flags = parseFolderTagFlags(rest);
1284
- const slug = flags.rest[0];
1285
- if (!slug) {
1286
- throw new FloomError("Missing skill slug.", `Try \`${CLI_COMMAND} move support-tone --folder support/tone\`.`);
1287
- }
1288
- if (flags.folder === undefined) {
1289
- throw new FloomError("Missing --folder.", "Use --folder <path> or --root. Add --tag or --tags when useful.");
1290
- }
1291
- if (flags.rest.length > 1) {
1292
- throw new FloomError(`Unexpected argument: ${flags.rest[1]}`, `Try \`${CLI_COMMAND} move support-tone --folder support/tone\`.`);
1293
- }
1294
- await moveSkill({ slug, folder: flags.folder, tags: flags.tags });
1295
- return;
1296
- }
1297
- case "mcp":
1298
- rejectArgs(rest, `Try \`${CLI_COMMAND} mcp\`.`);
1299
- printMcpSetup();
1300
- return;
1301
- case "doctor":
1302
- await doctor(parseDoctorFlags(rest));
1303
- return;
1304
- default:
1305
- throw new FloomError(`Unknown command: ${cmd}`, `Run \`${CLI_COMMAND} --help\` to see available commands.`);
1306
- }
1307
- }
1308
- catch (e) {
1309
- printError(e, { json: rest.includes("--json") });
1310
- process.exit(1);
1311
- }
1312
- }
1313
- void main();