@firenet-designs/fnd-cli 2.3.3 → 2.6.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/README.md CHANGED
@@ -20,7 +20,7 @@ $ npm install -g @firenet-designs/fnd-cli
20
20
  $ fnd COMMAND
21
21
  running command...
22
22
  $ fnd (--version)
23
- @firenet-designs/fnd-cli/2.3.3 linux-x64 node-v24.18.0
23
+ @firenet-designs/fnd-cli/2.6.0 linux-x64 node-v24.18.0
24
24
  $ fnd --help [COMMAND]
25
25
  USAGE
26
26
  $ fnd COMMAND
@@ -29,13 +29,79 @@ USAGE
29
29
  <!-- usagestop -->
30
30
  # Commands
31
31
  <!-- commands -->
32
+ * [`fnd alt-text`](#fnd-alt-text)
32
33
  * [`fnd backfill-project [SHOP]`](#fnd-backfill-project-shop)
34
+ * [`fnd caption`](#fnd-caption)
33
35
  * [`fnd create-app`](#fnd-create-app)
34
36
  * [`fnd create-project [SHOP]`](#fnd-create-project-shop)
35
37
  * [`fnd help [COMMAND]`](#fnd-help-command)
36
38
  * [`fnd token`](#fnd-token)
37
39
  * [`fnd workspace`](#fnd-workspace)
38
- * [`fnd workspace cleanup`](#fnd-workspace-cleanup)
40
+
41
+ ## `fnd alt-text`
42
+
43
+ Generate alt text for a site's images with a local Ollama vision model and write it back.
44
+
45
+ ```
46
+ USAGE
47
+ $ fnd alt-text [--api-key <value> --webflow] [--filter <value>] [--limit <value>] [--ollama-host <value>]
48
+ [--ollama-model <value>] [--only <value>... [--cms ]] [--output-stats <value>] [--select ] [--shopify | ] [--site-id
49
+ <value> ] [--skip]
50
+
51
+ FLAGS
52
+ --api-key=<value> Webflow API token (site-scoped). Prompted for if omitted.
53
+ --cms also add alt text to the images in CMS collection items. Off by default — only the site asset
54
+ library is walked.
55
+ --filter=<value> a JavaScript expression deciding which images are worth describing, e.g.
56
+ "fileSize>=sizes.KB(100) && width>=100 && height>=100". Available: fileSize (bytes), width,
57
+ height (pixels), url, type ("webp", "png", "jpeg", "svg", …), and sizes.KB/MB/GB helpers.
58
+ Images that fail it are skipped and reported separately. Evaluated after the download, since
59
+ dimensions can't be known before it.
60
+ --limit=<value> stop after this many images have been transcribed (counted across assets and CMS together)
61
+ --ollama-host=<value> base URL of the Ollama server. Prompted for if omitted.
62
+ --ollama-model=<value> the model used to describe the images. Omit it to pick from the vision-capable models pulled
63
+ on the host.
64
+ --only=<value>... only touch these CMS collections, matched case-insensitively against a collection's name or
65
+ slug. Repeat the flag for more than one.
66
+ --output-stats=<value> also write the run stats to this file as Markdown: totals, a row per image (url, description,
67
+ tokens, time, size), and the failures. Parent directories are created.
68
+ --select fetch the CMS collections and pick which ones to process interactively
69
+ --shopify run against a Shopify store (not implemented yet)
70
+ --site-id=<value> Webflow site ID. Prompted for if omitted.
71
+ --skip leave images that already have alt text alone instead of overwriting them
72
+ --webflow run against a Webflow site
73
+
74
+ DESCRIPTION
75
+ Generate alt text for a site's images with a local Ollama vision model and write it back.
76
+
77
+ Walks the Webflow site asset library and (with --cms) the image fields of CMS collection items, describes every image
78
+ that needs alt text, and PATCHes the description back. Images are fetched and described one at a time — a single local
79
+ model gains nothing from concurrency, and Webflow rate-limits. Nothing leaves your network except the Webflow API
80
+ calls.
81
+
82
+ CMS writes go to STAGING, so publish the site in Webflow to make them live. Any required flag you omit is prompted
83
+ for.
84
+
85
+ ALIASES
86
+ $ fnd caption
87
+
88
+ EXAMPLES
89
+ $ fnd alt-text --webflow --api-key <key> --site-id <id> --ollama-host http://localhost:11434 --ollama-model qwen3-vl:8b
90
+
91
+ $ fnd alt-text --webflow --skip --limit 20
92
+
93
+ $ fnd alt-text --webflow --cms --select
94
+
95
+ $ fnd alt-text --webflow --cms --only products --only sku
96
+
97
+ $ fnd alt-text --webflow --output-stats ./alt-text-run.md
98
+
99
+ $ fnd alt-text --webflow --filter "fileSize>=sizes.KB(100) && width>=100 && height>=100"
100
+
101
+ $ fnd alt-text --webflow --filter "type !== 'svg' && !url.includes('/icons/')"
102
+ ```
103
+
104
+ _See code: [src/commands/alt-text.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.6.0/src/commands/alt-text.ts)_
39
105
 
40
106
  ## `fnd backfill-project [SHOP]`
41
107
 
@@ -62,7 +128,70 @@ EXAMPLES
62
128
  $ fnd backfill-project my-store "extra hints"
63
129
  ```
64
130
 
65
- _See code: [src/commands/backfill-project.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.3/src/commands/backfill-project.ts)_
131
+ _See code: [src/commands/backfill-project.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.6.0/src/commands/backfill-project.ts)_
132
+
133
+ ## `fnd caption`
134
+
135
+ Generate alt text for a site's images with a local Ollama vision model and write it back.
136
+
137
+ ```
138
+ USAGE
139
+ $ fnd caption [--api-key <value> --webflow] [--filter <value>] [--limit <value>] [--ollama-host <value>]
140
+ [--ollama-model <value>] [--only <value>... [--cms ]] [--output-stats <value>] [--select ] [--shopify | ] [--site-id
141
+ <value> ] [--skip]
142
+
143
+ FLAGS
144
+ --api-key=<value> Webflow API token (site-scoped). Prompted for if omitted.
145
+ --cms also add alt text to the images in CMS collection items. Off by default — only the site asset
146
+ library is walked.
147
+ --filter=<value> a JavaScript expression deciding which images are worth describing, e.g.
148
+ "fileSize>=sizes.KB(100) && width>=100 && height>=100". Available: fileSize (bytes), width,
149
+ height (pixels), url, type ("webp", "png", "jpeg", "svg", …), and sizes.KB/MB/GB helpers.
150
+ Images that fail it are skipped and reported separately. Evaluated after the download, since
151
+ dimensions can't be known before it.
152
+ --limit=<value> stop after this many images have been transcribed (counted across assets and CMS together)
153
+ --ollama-host=<value> base URL of the Ollama server. Prompted for if omitted.
154
+ --ollama-model=<value> the model used to describe the images. Omit it to pick from the vision-capable models pulled
155
+ on the host.
156
+ --only=<value>... only touch these CMS collections, matched case-insensitively against a collection's name or
157
+ slug. Repeat the flag for more than one.
158
+ --output-stats=<value> also write the run stats to this file as Markdown: totals, a row per image (url, description,
159
+ tokens, time, size), and the failures. Parent directories are created.
160
+ --select fetch the CMS collections and pick which ones to process interactively
161
+ --shopify run against a Shopify store (not implemented yet)
162
+ --site-id=<value> Webflow site ID. Prompted for if omitted.
163
+ --skip leave images that already have alt text alone instead of overwriting them
164
+ --webflow run against a Webflow site
165
+
166
+ DESCRIPTION
167
+ Generate alt text for a site's images with a local Ollama vision model and write it back.
168
+
169
+ Walks the Webflow site asset library and (with --cms) the image fields of CMS collection items, describes every image
170
+ that needs alt text, and PATCHes the description back. Images are fetched and described one at a time — a single local
171
+ model gains nothing from concurrency, and Webflow rate-limits. Nothing leaves your network except the Webflow API
172
+ calls.
173
+
174
+ CMS writes go to STAGING, so publish the site in Webflow to make them live. Any required flag you omit is prompted
175
+ for.
176
+
177
+ ALIASES
178
+ $ fnd caption
179
+
180
+ EXAMPLES
181
+ $ fnd caption --webflow --api-key <key> --site-id <id> --ollama-host http://localhost:11434 --ollama-model qwen3-vl:8b
182
+
183
+ $ fnd caption --webflow --skip --limit 20
184
+
185
+ $ fnd caption --webflow --cms --select
186
+
187
+ $ fnd caption --webflow --cms --only products --only sku
188
+
189
+ $ fnd caption --webflow --output-stats ./alt-text-run.md
190
+
191
+ $ fnd caption --webflow --filter "fileSize>=sizes.KB(100) && width>=100 && height>=100"
192
+
193
+ $ fnd caption --webflow --filter "type !== 'svg' && !url.includes('/icons/')"
194
+ ```
66
195
 
67
196
  ## `fnd create-app`
68
197
 
@@ -83,11 +212,11 @@ EXAMPLES
83
212
  $ fnd create-app
84
213
  ```
85
214
 
86
- _See code: [src/commands/create-app.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.3/src/commands/create-app.ts)_
215
+ _See code: [src/commands/create-app.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.6.0/src/commands/create-app.ts)_
87
216
 
88
217
  ## `fnd create-project [SHOP]`
89
218
 
90
- Scaffold a new client project: git on branch production, ignore files, Shopify theme pull, a Claude-generated CLAUDE.md, then a private GitHub repo under the FireNet-Designs org.
219
+ Scaffold a new client project: git on branch production, ignore files, Shopify theme pull, a Claude-generated CLAUDE.md, then a private GitHub repo under the FireNet-Designs org with production and staging branches pushed.
91
220
 
92
221
  ```
93
222
  USAGE
@@ -98,7 +227,7 @@ ARGUMENTS
98
227
 
99
228
  DESCRIPTION
100
229
  Scaffold a new client project: git on branch production, ignore files, Shopify theme pull, a Claude-generated
101
- CLAUDE.md, then a private GitHub repo under the FireNet-Designs org.
230
+ CLAUDE.md, then a private GitHub repo under the FireNet-Designs org with production and staging branches pushed.
102
231
 
103
232
  Requires the claude CLI (npm install -g @anthropic-ai/claude-code) for the CLAUDE.md step.
104
233
 
@@ -113,7 +242,7 @@ EXAMPLES
113
242
  $ fnd create-project my-store "Acme storefront, Klaviyo"
114
243
  ```
115
244
 
116
- _See code: [src/commands/create-project.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.3/src/commands/create-project.ts)_
245
+ _See code: [src/commands/create-project.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.6.0/src/commands/create-project.ts)_
117
246
 
118
247
  ## `fnd help [COMMAND]`
119
248
 
@@ -165,7 +294,7 @@ EXAMPLES
165
294
  $ fnd token -s mystore -i <id> -c <secret> --write ./.mcp.json
166
295
  ```
167
296
 
168
- _See code: [src/commands/token.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.3/src/commands/token.ts)_
297
+ _See code: [src/commands/token.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.6.0/src/commands/token.ts)_
169
298
 
170
299
  ## `fnd workspace`
171
300
 
@@ -173,20 +302,47 @@ Open a remote workspace: two-way sync the current directory to a remote Linux bo
173
302
 
174
303
  ```
175
304
  USAGE
176
- $ fnd workspace --ssh <value> [--delete-remote-dir] [--devtools <value>] [--remote-base <value>] [--source
177
- remote|local]
305
+ $ fnd workspace --ssh <value> [--cleanup] [--delete-remote-dir] [--devtools <value>] [--ignore-vcs]
306
+ [--remote-base <value>] [--rpc <value>] [--source remote|local]
178
307
 
179
308
  FLAGS
180
- --delete-remote-dir on exit, delete the remote workspace directory instead of leaving the synced copy in place
181
- --devtools=<value> expose your LOCAL browser to Claude on the remote via the chrome-devtools MCP. Value is "port"
182
- (same port both ends) or "remote:local" (local = this machine, where the browser runs). Your
183
- browser must already be listening with --remote-debugging-port=<local>.
184
- --remote-base=<value> [default: /home/fnd] base dir on the remote; the workspace lands at
185
- <base>/<local-user>/<dir-name>
186
- --source=<option> which side wins on conflict: "remote" = this server (where the workspace shell runs), "local" =
187
- the machine you ran fnd workspace from. Omit to flag conflicts instead of auto-resolving them.
188
- <options: remote|local>
189
- --ssh=<value> (required) remote to connect to, as user@host
309
+ --cleanup
310
+ tear down a leftover session instead of opening one don't connect, just terminate this directory's Mutagen sync
311
+ and (using whatever other flags are set) strip the remote --devtools/--rpc MCP entries and, with
312
+ --delete-remote-dir, remove the remote dir. Re-run your original command with --cleanup appended after a session
313
+ that dropped without cleaning up.
314
+
315
+ --delete-remote-dir
316
+ on exit, delete the remote workspace directory instead of leaving the synced copy in place
317
+
318
+ --devtools=<value>
319
+ expose your LOCAL browser to Claude on the remote via the chrome-devtools MCP. Value is "port" (same port both ends)
320
+ or "remote:local" (local = this machine, where the browser runs). Your browser must already be listening with
321
+ --remote-debugging-port=<local>.
322
+
323
+ --ignore-vcs
324
+ don't sync paths matched by the project's .gitignore files (node_modules, build output, …) so each side keeps its
325
+ own platform-specific artifacts. Every .gitignore in the tree is honoured relative to its directory, like git does.
326
+ The .git directory itself still syncs, so the remote stays a working repo.
327
+
328
+ --remote-base=<value>
329
+ [default: /home/fnd] base dir on the remote; the workspace lands at <base>/<local-user>/<dir-name>
330
+
331
+ --rpc=<value>
332
+ expose a run_local_command MCP tool to Claude on the remote that executes commands back on THIS machine (the one
333
+ running fnd workspace). Value is
334
+ port=<port|remote:local>[,profile=<true|1|false|0>][,shell=<bash|batch|powershell|sh|zsh>] — port opens a reverse
335
+ tunnel (ssh -R <remote>:localhost:<local>) to a command server started here; shell defaults to the shell fnd
336
+ workspace was called from; profile (default true) controls whether the shell loads its startup files — with it on,
337
+ POSIX shells run interactively (-i) so rc files like ~/.bashrc or ~/.zshrc are sourced and tools such as nvm work.
338
+
339
+ --source=<option>
340
+ which side wins on conflict: "remote" = this server (where the workspace shell runs), "local" = the machine you ran
341
+ fnd workspace from. Omit to flag conflicts instead of auto-resolving them.
342
+ <options: remote|local>
343
+
344
+ --ssh=<value>
345
+ (required) remote to connect to, as user@host
190
346
 
191
347
  DESCRIPTION
192
348
  Open a remote workspace: two-way sync the current directory to a remote Linux box with Mutagen and drop into a shell
@@ -205,49 +361,20 @@ EXAMPLES
205
361
 
206
362
  $ fnd workspace --ssh user@host --remote-base /home/fnd
207
363
 
364
+ $ fnd workspace --ssh user@host --ignore-vcs
365
+
208
366
  $ fnd workspace --ssh user@host --devtools 9222
209
367
 
210
368
  $ fnd workspace --ssh user@host --devtools 9333:9222
211
369
 
212
- $ fnd workspace --ssh user@host --delete-remote-dir
213
- ```
214
-
215
- _See code: [src/commands/workspace/index.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.3/src/commands/workspace/index.ts)_
216
-
217
- ## `fnd workspace cleanup`
218
-
219
- Tear down a leftover workspace — use this if a `workspace` session dropped before it could clean up after itself.
220
-
221
- ```
222
- USAGE
223
- $ fnd workspace cleanup --ssh <value> [--delete-remote-dir] [--devtools] [--remote-base <value>] [--remote-dir
224
- <value>]
370
+ $ fnd workspace --ssh user@host --rpc port=7777:7700
225
371
 
226
- FLAGS
227
- --delete-remote-dir also delete the remote workspace directory (by default the synced files are left in place)
228
- --devtools also strip the chrome-devtools MCP entry from the remote (only if the dropped session used
229
- --devtools)
230
- --remote-base=<value> [default: /home/fnd] base dir on the remote, used to derive the default remote directory path
231
- --remote-dir=<value> exact remote directory to target (defaults to the current dir mapping)
232
- --ssh=<value> (required) remote to connect to, as user@host
372
+ $ fnd workspace --ssh user@host --rpc port=7777:7700,profile=false,shell=zsh
233
373
 
234
- DESCRIPTION
235
- Tear down a leftover workspace — use this if a `workspace` session dropped before it could clean up after itself.
236
-
237
- Terminates any Mutagen sync sessions this machine started for the directory. Pass --devtools to also strip the
238
- matching chrome-devtools MCP entry from the remote (only do this if the dropped session used --devtools). With no
239
- --remote-dir, it targets the same path `workspace` would use for the current directory. The synced files themselves
240
- are left in place unless you pass --delete-remote-dir.
241
-
242
- EXAMPLES
243
- $ fnd workspace cleanup --ssh user@host
244
-
245
- $ fnd workspace cleanup --ssh user@host --remote-dir /home/fnd/cole/fnd-cli
246
-
247
- $ fnd workspace cleanup --ssh user@host --devtools
374
+ $ fnd workspace --ssh user@host --delete-remote-dir
248
375
 
249
- $ fnd workspace cleanup --ssh user@host --delete-remote-dir
376
+ $ fnd workspace --ssh user@host --rpc 7700 --cleanup
250
377
  ```
251
378
 
252
- _See code: [src/commands/workspace/cleanup.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.3/src/commands/workspace/cleanup.ts)_
379
+ _See code: [src/commands/workspace/index.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.6.0/src/commands/workspace/index.ts)_
253
380
  <!-- commandsstop -->
@@ -0,0 +1,56 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class AltText extends Command {
3
+ static aliases: string[];
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ 'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ cms: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ filter: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ limit: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ 'ollama-host': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ 'ollama-model': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ only: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ 'output-stats': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ select: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ shopify: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ 'site-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ skip: import("@oclif/core/interfaces").BooleanFlag<boolean>;
19
+ webflow: import("@oclif/core/interfaces").BooleanFlag<boolean>;
20
+ };
21
+ run(): Promise<void>;
22
+ /**
23
+ * Describe one image and account for it. Returns undefined when the model or
24
+ * the download failed — one bad image must not abort a run that may have
25
+ * hundreds of good ones behind it.
26
+ */
27
+ private describe;
28
+ /** Which CMS collections this run should walk. */
29
+ private pickCollections;
30
+ /**
31
+ * Ask which model to use, offering only the vision-capable models the host has
32
+ * already pulled — a text-only model would happily accept the request and
33
+ * describe nothing, and a model that isn't pulled would stall the run behind a
34
+ * silent download.
35
+ */
36
+ private pickModel;
37
+ /** The site asset library pass. Always runs; --cms only adds work on top of it. */
38
+ private processAssets;
39
+ private processCollection;
40
+ /**
41
+ * Fill in every image field on one item, then PATCH the item once. Fields that
42
+ * didn't change are left out of the payload entirely, and an item with no
43
+ * changed fields is never PATCHed.
44
+ */
45
+ private processItem;
46
+ /** Totals and averages only — the per-image rows live in the --output-stats file. */
47
+ private report;
48
+ /** --webflow / --shopify, or ask when neither was given. */
49
+ private resolvePlatform;
50
+ /**
51
+ * Write the Markdown report. This runs after everything else, so a run that
52
+ * gets this far has already written its alt text back to Webflow — failing to
53
+ * save the report loses the record, not the work, and the message says so.
54
+ */
55
+ private writeStats;
56
+ }