@firenet-designs/fnd-cli 2.4.0 → 2.7.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.
Files changed (60) hide show
  1. package/README.md +194 -57
  2. package/bin/dev.js +1 -1
  3. package/dist/commands/alt-text.d.ts +105 -0
  4. package/dist/commands/alt-text.js +616 -0
  5. package/dist/commands/backfill-project.js +1 -1
  6. package/dist/commands/create-project.js +48 -5
  7. package/dist/commands/workspace/index.d.ts +19 -2
  8. package/dist/commands/workspace/index.js +171 -56
  9. package/dist/lib/alt-text.d.ts +87 -0
  10. package/dist/lib/alt-text.js +196 -0
  11. package/dist/lib/image-filter.d.ts +43 -0
  12. package/dist/lib/image-filter.js +71 -0
  13. package/dist/lib/mcp/bracket-args.d.ts +37 -0
  14. package/dist/lib/mcp/bracket-args.js +65 -0
  15. package/dist/lib/mcp/define-tool.d.ts +52 -0
  16. package/dist/lib/mcp/define-tool.js +2 -0
  17. package/dist/lib/mcp/registry.d.ts +38 -0
  18. package/dist/lib/mcp/registry.js +98 -0
  19. package/dist/lib/mcp/server.d.ts +66 -0
  20. package/dist/lib/mcp/server.js +176 -0
  21. package/dist/lib/mcp/tools/shopify-common.d.ts +139 -0
  22. package/dist/lib/mcp/tools/shopify-common.js +167 -0
  23. package/dist/lib/mcp/tools/shopify-execute.d.ts +2 -0
  24. package/dist/lib/mcp/tools/shopify-execute.js +105 -0
  25. package/dist/lib/mcp/tools/shopify-file-delete.d.ts +2 -0
  26. package/dist/lib/mcp/tools/shopify-file-delete.js +49 -0
  27. package/dist/lib/mcp/tools/shopify-file-replace.d.ts +2 -0
  28. package/dist/lib/mcp/tools/shopify-file-replace.js +79 -0
  29. package/dist/lib/mcp/tools/shopify-file-search.d.ts +2 -0
  30. package/dist/lib/mcp/tools/shopify-file-search.js +199 -0
  31. package/dist/lib/mcp/tools/shopify-file-upload.d.ts +2 -0
  32. package/dist/lib/mcp/tools/shopify-file-upload.js +76 -0
  33. package/dist/lib/shopify/graphql/AccessScopes.graphql +7 -0
  34. package/dist/lib/shopify/graphql/CurrentBulkOperation.graphql +8 -0
  35. package/dist/lib/shopify/graphql/FileCreate.graphql +25 -0
  36. package/dist/lib/shopify/graphql/FileDelete.graphql +11 -0
  37. package/dist/lib/shopify/graphql/FileReplace.graphql +26 -0
  38. package/dist/lib/shopify/graphql/FileStatus.graphql +19 -0
  39. package/dist/lib/shopify/graphql/FilesBulkQuery.graphql +27 -0
  40. package/dist/lib/shopify/graphql/ProductsBulkQuery.graphql +27 -0
  41. package/dist/lib/shopify/graphql/SearchFiles.graphql +36 -0
  42. package/dist/lib/shopify/graphql/StagedUploadsCreate.graphql +20 -0
  43. package/dist/lib/shopify/graphql/StartBulkQuery.graphql +16 -0
  44. package/dist/lib/shopify/graphql/UpdateFileAlt.graphql +9 -0
  45. package/dist/lib/shopify/shopify.d.ts +228 -0
  46. package/dist/lib/shopify/shopify.js +662 -0
  47. package/dist/lib/webflow.d.ts +80 -0
  48. package/dist/lib/webflow.js +122 -0
  49. package/dist/lib/workspace.d.ts +29 -10
  50. package/dist/lib/workspace.js +74 -39
  51. package/oclif.manifest.json +162 -78
  52. package/package.json +21 -10
  53. package/dist/commands/workspace/cleanup.d.ts +0 -14
  54. package/dist/commands/workspace/cleanup.js +0 -84
  55. package/dist/hooks/init/check-for-updates.d.ts +0 -3
  56. package/dist/hooks/init/check-for-updates.js +0 -15
  57. package/dist/lib/kv-flag.d.ts +0 -15
  58. package/dist/lib/kv-flag.js +0 -75
  59. package/dist/lib/rpc.d.ts +0 -69
  60. package/dist/lib/rpc.js +0 -313
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.4.0 linux-x64 node-v24.18.0
23
+ @firenet-designs/fnd-cli/2.7.0 node-v24.18.0
24
24
  $ fnd --help [COMMAND]
25
25
  USAGE
26
26
  $ fnd COMMAND
@@ -29,13 +29,90 @@ 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>] [--dry] [--filter <value>] [--limit <value>] [--ollama-host <value>]
48
+ [--ollama-model <value>] [--only <value>... --cms] [--output-stats <value>] [--platform webflow|shopify] [--select ]
49
+ [--site-id <value>] [--skip]
50
+
51
+ FLAGS
52
+ --api-key=<value> Webflow API token (site-scoped). Webflow only — Shopify auth comes from the Shopify CLI.
53
+ Prompted for if omitted.
54
+ --cms also add alt text to the images in CMS collection items. Webflow only. Off by default — only
55
+ the site asset library is walked.
56
+ --dry report how many images the run would caption without describing or writing anything. Every
57
+ image is still downloaded and run through --filter so the count is accurate, but nothing
58
+ reaches the model. The --ollama-host/--ollama-model and --output-stats flags are ignored in
59
+ this mode, so --dry can be added to (and removed from) a full command without changing the
60
+ rest.
61
+ --filter=<value> a JavaScript expression deciding which images are worth describing, e.g.
62
+ "fileSize>=sizes.KB(100) && width>=100 && height>=100". Available: fileSize (bytes), width,
63
+ height (pixels), url, type ("webp", "png", "jpeg", "svg", …), and sizes.KB/MB/GB helpers.
64
+ Images that fail it are skipped and reported separately. Evaluated after the download, since
65
+ dimensions can't be known before it.
66
+ --limit=<value> stop after this many images have been transcribed (counted across assets and CMS together)
67
+ --ollama-host=<value> base URL of the Ollama server. Prompted for if omitted.
68
+ --ollama-model=<value> the model used to describe the images. Omit it to pick from the vision-capable models pulled
69
+ on the host.
70
+ --only=<value>... only touch these CMS collections, matched case-insensitively against a collection's name or
71
+ slug. Repeat the flag for more than one.
72
+ --output-stats=<value> also write the run stats to this file as Markdown: totals, a row per image (url, description,
73
+ tokens, time, size), and the failures. Parent directories are created.
74
+ --platform=<option> which platform to run against. Prompted for if omitted.
75
+ <options: webflow|shopify>
76
+ --select fetch the CMS collections and pick which ones to process interactively
77
+ --site-id=<value> Webflow site ID, or the Shopify store (mystore or mystore.myshopify.com). Prompted for if
78
+ omitted.
79
+ --skip leave images that already have alt text alone instead of overwriting them
80
+
81
+ DESCRIPTION
82
+ Generate alt text for a site's images with a local Ollama vision model and write it back.
83
+
84
+ Pick a platform with --platform (webflow or shopify). For Webflow, walks the site asset library and (with --cms) the
85
+ image fields of CMS collection items and PATCHes each description back through the Data API. For Shopify, walks the
86
+ store's file library and writes alt text back through the Shopify CLI (which owns the auth — no --api-key). Images are
87
+ fetched and described one at a time; nothing but the platform API calls leaves your network.
88
+
89
+ Webflow CMS writes go to STAGING, so publish the site in Webflow to make them live. Any required flag you omit is
90
+ prompted for.
91
+
92
+ ALIASES
93
+ $ fnd caption
94
+
95
+ EXAMPLES
96
+ $ fnd alt-text --platform webflow --api-key <key> --site-id <id> --ollama-host http://localhost:11434 --ollama-model qwen3-vl:8b
97
+
98
+ $ fnd alt-text --platform shopify --site-id mystore
99
+
100
+ $ fnd alt-text --platform shopify --site-id mystore --dry
101
+
102
+ $ fnd alt-text --platform webflow --skip --limit 20
103
+
104
+ $ fnd alt-text --platform webflow --cms --select
105
+
106
+ $ fnd alt-text --platform webflow --cms --only products --only sku
107
+
108
+ $ fnd alt-text --platform webflow --output-stats ./alt-text-run.md
109
+
110
+ $ fnd alt-text --platform webflow --filter "fileSize>=sizes.KB(100) && width>=100 && height>=100"
111
+
112
+ $ fnd alt-text --platform webflow --filter "type !== 'svg' && !url.includes('/icons/')"
113
+ ```
114
+
115
+ _See code: [src/commands/alt-text.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.7.0/src/commands/alt-text.ts)_
39
116
 
40
117
  ## `fnd backfill-project [SHOP]`
41
118
 
@@ -62,7 +139,81 @@ EXAMPLES
62
139
  $ fnd backfill-project my-store "extra hints"
63
140
  ```
64
141
 
65
- _See code: [src/commands/backfill-project.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.4.0/src/commands/backfill-project.ts)_
142
+ _See code: [src/commands/backfill-project.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.7.0/src/commands/backfill-project.ts)_
143
+
144
+ ## `fnd caption`
145
+
146
+ Generate alt text for a site's images with a local Ollama vision model and write it back.
147
+
148
+ ```
149
+ USAGE
150
+ $ fnd caption [--api-key <value>] [--dry] [--filter <value>] [--limit <value>] [--ollama-host <value>]
151
+ [--ollama-model <value>] [--only <value>... --cms] [--output-stats <value>] [--platform webflow|shopify] [--select ]
152
+ [--site-id <value>] [--skip]
153
+
154
+ FLAGS
155
+ --api-key=<value> Webflow API token (site-scoped). Webflow only — Shopify auth comes from the Shopify CLI.
156
+ Prompted for if omitted.
157
+ --cms also add alt text to the images in CMS collection items. Webflow only. Off by default — only
158
+ the site asset library is walked.
159
+ --dry report how many images the run would caption without describing or writing anything. Every
160
+ image is still downloaded and run through --filter so the count is accurate, but nothing
161
+ reaches the model. The --ollama-host/--ollama-model and --output-stats flags are ignored in
162
+ this mode, so --dry can be added to (and removed from) a full command without changing the
163
+ rest.
164
+ --filter=<value> a JavaScript expression deciding which images are worth describing, e.g.
165
+ "fileSize>=sizes.KB(100) && width>=100 && height>=100". Available: fileSize (bytes), width,
166
+ height (pixels), url, type ("webp", "png", "jpeg", "svg", …), and sizes.KB/MB/GB helpers.
167
+ Images that fail it are skipped and reported separately. Evaluated after the download, since
168
+ dimensions can't be known before it.
169
+ --limit=<value> stop after this many images have been transcribed (counted across assets and CMS together)
170
+ --ollama-host=<value> base URL of the Ollama server. Prompted for if omitted.
171
+ --ollama-model=<value> the model used to describe the images. Omit it to pick from the vision-capable models pulled
172
+ on the host.
173
+ --only=<value>... only touch these CMS collections, matched case-insensitively against a collection's name or
174
+ slug. Repeat the flag for more than one.
175
+ --output-stats=<value> also write the run stats to this file as Markdown: totals, a row per image (url, description,
176
+ tokens, time, size), and the failures. Parent directories are created.
177
+ --platform=<option> which platform to run against. Prompted for if omitted.
178
+ <options: webflow|shopify>
179
+ --select fetch the CMS collections and pick which ones to process interactively
180
+ --site-id=<value> Webflow site ID, or the Shopify store (mystore or mystore.myshopify.com). Prompted for if
181
+ omitted.
182
+ --skip leave images that already have alt text alone instead of overwriting them
183
+
184
+ DESCRIPTION
185
+ Generate alt text for a site's images with a local Ollama vision model and write it back.
186
+
187
+ Pick a platform with --platform (webflow or shopify). For Webflow, walks the site asset library and (with --cms) the
188
+ image fields of CMS collection items and PATCHes each description back through the Data API. For Shopify, walks the
189
+ store's file library and writes alt text back through the Shopify CLI (which owns the auth — no --api-key). Images are
190
+ fetched and described one at a time; nothing but the platform API calls leaves your network.
191
+
192
+ Webflow CMS writes go to STAGING, so publish the site in Webflow to make them live. Any required flag you omit is
193
+ prompted for.
194
+
195
+ ALIASES
196
+ $ fnd caption
197
+
198
+ EXAMPLES
199
+ $ fnd caption --platform webflow --api-key <key> --site-id <id> --ollama-host http://localhost:11434 --ollama-model qwen3-vl:8b
200
+
201
+ $ fnd caption --platform shopify --site-id mystore
202
+
203
+ $ fnd caption --platform shopify --site-id mystore --dry
204
+
205
+ $ fnd caption --platform webflow --skip --limit 20
206
+
207
+ $ fnd caption --platform webflow --cms --select
208
+
209
+ $ fnd caption --platform webflow --cms --only products --only sku
210
+
211
+ $ fnd caption --platform webflow --output-stats ./alt-text-run.md
212
+
213
+ $ fnd caption --platform webflow --filter "fileSize>=sizes.KB(100) && width>=100 && height>=100"
214
+
215
+ $ fnd caption --platform webflow --filter "type !== 'svg' && !url.includes('/icons/')"
216
+ ```
66
217
 
67
218
  ## `fnd create-app`
68
219
 
@@ -83,11 +234,11 @@ EXAMPLES
83
234
  $ fnd create-app
84
235
  ```
85
236
 
86
- _See code: [src/commands/create-app.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.4.0/src/commands/create-app.ts)_
237
+ _See code: [src/commands/create-app.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.7.0/src/commands/create-app.ts)_
87
238
 
88
239
  ## `fnd create-project [SHOP]`
89
240
 
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.
241
+ 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
242
 
92
243
  ```
93
244
  USAGE
@@ -98,7 +249,7 @@ ARGUMENTS
98
249
 
99
250
  DESCRIPTION
100
251
  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.
252
+ CLAUDE.md, then a private GitHub repo under the FireNet-Designs org with production and staging branches pushed.
102
253
 
103
254
  Requires the claude CLI (npm install -g @anthropic-ai/claude-code) for the CLAUDE.md step.
104
255
 
@@ -113,7 +264,7 @@ EXAMPLES
113
264
  $ fnd create-project my-store "Acme storefront, Klaviyo"
114
265
  ```
115
266
 
116
- _See code: [src/commands/create-project.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.4.0/src/commands/create-project.ts)_
267
+ _See code: [src/commands/create-project.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.7.0/src/commands/create-project.ts)_
117
268
 
118
269
  ## `fnd help [COMMAND]`
119
270
 
@@ -165,7 +316,7 @@ EXAMPLES
165
316
  $ fnd token -s mystore -i <id> -c <secret> --write ./.mcp.json
166
317
  ```
167
318
 
168
- _See code: [src/commands/token.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.4.0/src/commands/token.ts)_
319
+ _See code: [src/commands/token.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.7.0/src/commands/token.ts)_
169
320
 
170
321
  ## `fnd workspace`
171
322
 
@@ -173,10 +324,16 @@ Open a remote workspace: two-way sync the current directory to a remote Linux bo
173
324
 
174
325
  ```
175
326
  USAGE
176
- $ fnd workspace --ssh <value> [--delete-remote-dir] [--devtools <value>] [--ignore-vcs] [--remote-base
177
- <value>] [--rpc <value>] [--source remote|local]
327
+ $ fnd workspace --ssh <value> [--cleanup] [--delete-remote-dir] [--devtools <value>] [--ignore-vcs]
328
+ [--remote-base <value>] [--site-id <value>] [--source remote|local] [--with-tool <value>...]
178
329
 
179
330
  FLAGS
331
+ --cleanup
332
+ tear down a leftover session instead of opening one — don't connect, just terminate this directory's Mutagen sync
333
+ and (using whatever other flags are set) strip the remote --devtools/--with-tool MCP entries and, with
334
+ --delete-remote-dir, remove the remote dir. Re-run your original command with --cleanup appended after a session
335
+ that dropped without cleaning up.
336
+
180
337
  --delete-remote-dir
181
338
  on exit, delete the remote workspace directory instead of leaving the synced copy in place
182
339
 
@@ -193,13 +350,10 @@ FLAGS
193
350
  --remote-base=<value>
194
351
  [default: /home/fnd] base dir on the remote; the workspace lands at <base>/<local-user>/<dir-name>
195
352
 
196
- --rpc=<value>
197
- expose a run_local_command MCP tool to Claude on the remote that executes commands back on THIS machine (the one
198
- running fnd workspace). Value is
199
- port=<port|remote:local>[,profile=<true|1|false|0>][,shell=<bash|batch|powershell|sh|zsh>] port opens a reverse
200
- tunnel (ssh -R <remote>:localhost:<local>) to a command server started here; shell defaults to the shell fnd
201
- workspace was called from; profile (default true) controls whether the shell loads its startup files — with it on,
202
- POSIX shells run interactively (-i) so rc files like ~/.bashrc or ~/.zshrc are sourced and tools such as nvm work.
353
+ --site-id=<value>
354
+ the Shopify store (mystore or mystore.myshopify.com) the Shopify --with-tool tools operate on. Required when any
355
+ Shopify tool (shopify-file-* or shopify-execute) is selected; fixing the store here is a safety boundary — the
356
+ remote AI cannot target another store.
203
357
 
204
358
  --source=<option>
205
359
  which side wins on conflict: "remote" = this server (where the workspace shell runs), "local" = the machine you ran
@@ -209,6 +363,21 @@ FLAGS
209
363
  --ssh=<value>
210
364
  (required) remote to connect to, as user@host
211
365
 
366
+ --with-tool=<value>...
367
+ expose an fnd tool to Claude on the remote via a loopback MCP server on THIS machine (the one running fnd
368
+ workspace), reached through a reverse tunnel. Repeatable. Value is <name> or <name>[options]; available:
369
+ shopify-execute[<scopes=all|<list>[,ask]>], shopify-file-delete[[<ask>]], shopify-file-replace[[<ask>]],
370
+ shopify-file-search[[<ask>]], shopify-file-upload[[<ask>]]. e.g. shopify-file-upload (with --site-id mystore) lets
371
+ the remote AI upload files from the workspace into that Shopify store using your local Shopify CLI;
372
+ shopify-file-replace and shopify-file-delete add in-place replace and delete; shopify-file-search looks up files by
373
+ name/size/type/url (metadata only, no image bytes) to dedupe before upload or find oversized images; shopify-execute
374
+ runs arbitrary Admin GraphQL and REQUIRES a scopes option — shopify-execute[scopes=all] for broad access or
375
+ shopify-execute[scopes=read_products+write_orders] for an explicit set (which the store is then trimmed to, revoking
376
+ anything extra, so the AI is held to least privilege). Append ,ask (e.g. shopify-file-delete[ask],
377
+ shopify-execute[ask,scopes=all]) to make that tool prompt for confirmation before EVERY call, even under the
378
+ remote's auto-accept/bypass permissions — use it to gate the powerful/destructive tools. Prerequisites (auth,
379
+ scopes) are checked and set up before connecting.
380
+
212
381
  DESCRIPTION
213
382
  Open a remote workspace: two-way sync the current directory to a remote Linux box with Mutagen and drop into a shell
214
383
  there, tearing the sync down on exit.
@@ -232,52 +401,20 @@ EXAMPLES
232
401
 
233
402
  $ fnd workspace --ssh user@host --devtools 9333:9222
234
403
 
235
- $ fnd workspace --ssh user@host --rpc port=7777:7700
404
+ $ fnd workspace --ssh user@host --with-tool shopify-file-upload --site-id mystore
236
405
 
237
- $ fnd workspace --ssh user@host --rpc port=7777:7700,profile=false,shell=zsh
406
+ $ fnd workspace --ssh user@host --with-tool shopify-file-upload --with-tool shopify-file-search --with-tool "shopify-file-delete[ask]" --site-id mystore
238
407
 
239
- $ fnd workspace --ssh user@host --delete-remote-dir
240
- ```
241
-
242
- _See code: [src/commands/workspace/index.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.4.0/src/commands/workspace/index.ts)_
243
-
244
- ## `fnd workspace cleanup`
245
-
246
- Tear down a leftover workspace — use this if a `workspace` session dropped before it could clean up after itself.
247
-
248
- ```
249
- USAGE
250
- $ fnd workspace cleanup --ssh <value> [--delete-remote-dir] [--devtools] [--remote-base <value>] [--remote-dir
251
- <value>] [--rpc]
408
+ $ fnd workspace --ssh user@host --with-tool shopify-file-upload --with-tool shopify-file-replace --with-tool shopify-file-delete --site-id mystore.myshopify.com
252
409
 
253
- FLAGS
254
- --delete-remote-dir also delete the remote workspace directory (by default the synced files are left in place)
255
- --devtools also strip the chrome-devtools MCP entry from the remote (only if the dropped session used
256
- --devtools)
257
- --remote-base=<value> [default: /home/fnd] base dir on the remote, used to derive the default remote directory path
258
- --remote-dir=<value> exact remote directory to target (defaults to the current dir mapping)
259
- --rpc also strip the local-shell MCP entry from the remote (only if the dropped session used --rpc)
260
- --ssh=<value> (required) remote to connect to, as user@host
261
-
262
- DESCRIPTION
263
- Tear down a leftover workspace — use this if a `workspace` session dropped before it could clean up after itself.
264
-
265
- Terminates any Mutagen sync sessions this machine started for the directory. Pass --devtools or --rpc to also strip
266
- the matching MCP entries from the remote (only do this if the dropped session used those flags). With no --remote-dir,
267
- it targets the same path `workspace` would use for the current directory. The synced files themselves are left in
268
- place unless you pass --delete-remote-dir.
410
+ $ fnd workspace --ssh user@host --with-tool "shopify-execute[scopes=read_products+read_orders]" --site-id mystore
269
411
 
270
- EXAMPLES
271
- $ fnd workspace cleanup --ssh user@host
272
-
273
- $ fnd workspace cleanup --ssh user@host --remote-dir /home/fnd/cole/fnd-cli
412
+ $ fnd workspace --ssh user@host --with-tool "shopify-execute[ask,scopes=all]" --site-id mystore
274
413
 
275
- $ fnd workspace cleanup --ssh user@host --devtools
276
-
277
- $ fnd workspace cleanup --ssh user@host --rpc
414
+ $ fnd workspace --ssh user@host --delete-remote-dir
278
415
 
279
- $ fnd workspace cleanup --ssh user@host --delete-remote-dir
416
+ $ fnd workspace --ssh user@host --with-tool shopify-file-upload --site-id mystore --cleanup
280
417
  ```
281
418
 
282
- _See code: [src/commands/workspace/cleanup.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.4.0/src/commands/workspace/cleanup.ts)_
419
+ _See code: [src/commands/workspace/index.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.7.0/src/commands/workspace/index.ts)_
283
420
  <!-- commandsstop -->
package/bin/dev.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env -S node --loader ts-node/esm --disable-warning=ExperimentalWarning
1
+ #!/usr/bin/env -S node --loader ts-node/esm --conditions=development --disable-warning=ExperimentalWarning
2
2
 
3
3
  import {execute} from '@oclif/core'
4
4
 
@@ -0,0 +1,105 @@
1
+ import { Command, Interfaces } 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': Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
8
+ cms: Interfaces.BooleanFlag<boolean>;
9
+ dry: Interfaces.BooleanFlag<boolean>;
10
+ filter: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
11
+ limit: Interfaces.OptionFlag<number | undefined, Interfaces.CustomOptions>;
12
+ 'ollama-host': Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
13
+ 'ollama-model': Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
14
+ only: Interfaces.OptionFlag<string[] | undefined, Interfaces.CustomOptions>;
15
+ 'output-stats': Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
16
+ platform: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
17
+ select: Interfaces.BooleanFlag<boolean>;
18
+ 'site-id': Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
19
+ skip: 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
+ * Also the fallback for a --dry run: when an image's metadata wasn't known up
28
+ * front (so the caller couldn't filter it cheaply), the describer downloads it
29
+ * just far enough to measure it, and this counts it via recordDry.
30
+ */
31
+ private describe;
32
+ /**
33
+ * Guarantee the store's app installation has the scopes this run needs before
34
+ * any captioning. `required` is the minimal set for the run — the full
35
+ * REQUIRED_SCOPES for a real run, just DRY_RUN_SCOPES (read_files) for a --dry
36
+ * one, which never writes or maps product context. A non-zero exit from the
37
+ * scope probe is the CLI's way of saying the store isn't authenticated at all;
38
+ * a clean exit with a scope missing means it's authenticated but under-scoped.
39
+ * Either way `shopify store auth` with `required` fixes it — it adds only
40
+ * what's missing, and a write scope already covers its read counterpart.
41
+ */
42
+ private ensureShopifyScopes;
43
+ /** The `[n/limit] label — file` progress line, shared by real and dry counting. */
44
+ private logImageLine;
45
+ /** Which CMS collections this run should walk. */
46
+ private pickCollections;
47
+ /**
48
+ * Ask which model to use, offering only the vision-capable models the host has
49
+ * already pulled — a text-only model would happily accept the request and
50
+ * describe nothing, and a model that isn't pulled would stall the run behind a
51
+ * silent download.
52
+ */
53
+ private pickModel;
54
+ /** The site asset library pass. Always runs; --cms only adds work on top of it. */
55
+ private processAssets;
56
+ private processCollection;
57
+ /**
58
+ * Fill in every image field on one item, then PATCH the item once. Fields that
59
+ * didn't change are left out of the payload entirely, and an item with no
60
+ * changed fields is never PATCHed.
61
+ */
62
+ private processItem;
63
+ /**
64
+ * The Shopify path: make sure the CLI is present and the store is authenticated
65
+ * with the file scopes, then caption every image in the file library. Always
66
+ * returns false — there's no separate staging step to warn about.
67
+ */
68
+ private processShopify;
69
+ /**
70
+ * The Webflow path: the site asset library, then (with --cms) whichever CMS
71
+ * collections the flags select. Returns whether any CMS work was done, so the
72
+ * summary can remind the user to publish staging.
73
+ */
74
+ private processWebflow;
75
+ /**
76
+ * Count one image toward a --dry total from metadata already in hand — no
77
+ * download, no model. Applies --filter to decide caption-vs-skip; the caller
78
+ * has already logged the image line.
79
+ */
80
+ private recordDry;
81
+ /** Totals and averages only — the per-image rows live in the --output-stats file. */
82
+ private report;
83
+ /**
84
+ * The --dry summary: how many images a real run would caption, the ones the
85
+ * --filter would skip, and any that couldn't even be downloaded. No tokens,
86
+ * time, or descriptions to report — nothing was described.
87
+ */
88
+ private reportDry;
89
+ /** The --platform value, or ask when it was omitted. */
90
+ private resolvePlatform;
91
+ /**
92
+ * Stop a spinner and undo ora's parting cursor move. On stop, ora's clear()
93
+ * runs cursorTo(indent) on its stream (stderr) — and since stdout shares the
94
+ * terminal's cursor, the very next this.log would start at that column and be
95
+ * pushed right by `indent` spaces. Resetting to column 0 keeps the following
96
+ * line at exactly the indent it asks for.
97
+ */
98
+ private stopSpinner;
99
+ /**
100
+ * Write the Markdown report. This runs after everything else, so a run that
101
+ * gets this far has already written its alt text back to Webflow — failing to
102
+ * save the report loses the record, not the work, and the message says so.
103
+ */
104
+ private writeStats;
105
+ }