@floomhq/floom 1.0.64 → 2.0.0
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/index.d.ts +1 -0
- package/dist/index.js +3663 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -25
- package/package.json +37 -45
- package/LICENSE +0 -21
- package/README.md +0 -90
- package/bin/floom.js +0 -2
- package/dist/audit.js +0 -236
- package/dist/cli.js +0 -1313
- package/dist/config.js +0 -85
- package/dist/daemon.js +0 -450
- package/dist/delete.js +0 -55
- package/dist/doctor.js +0 -381
- package/dist/errors.js +0 -71
- package/dist/feedback.js +0 -34
- package/dist/info.js +0 -78
- package/dist/init.js +0 -221
- package/dist/install.js +0 -305
- package/dist/launch.js +0 -110
- package/dist/lib/api.js +0 -142
- package/dist/lib/skill-labels.js +0 -140
- package/dist/library.js +0 -102
- package/dist/list.js +0 -79
- package/dist/login.js +0 -259
- package/dist/mcp.js +0 -20
- package/dist/package.js +0 -507
- package/dist/publish.js +0 -240
- package/dist/push-watch.js +0 -372
- package/dist/scan.js +0 -24
- package/dist/search.js +0 -54
- package/dist/secrets.js +0 -119
- package/dist/setup.js +0 -301
- package/dist/share.js +0 -70
- package/dist/status.js +0 -181
- package/dist/sync-manifest.js +0 -314
- package/dist/sync.js +0 -581
- package/dist/targets.js +0 -49
- package/dist/ui.js +0 -28
- package/dist/whoami.js +0 -64
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();
|