npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mboucher/ffe-cli-ps-plugin

v0.1.3

Published

Photoshop API commands for the Firefly Enterprise CLI

Readme

Photoshop plugin for FFE CLI

An oclif plugin for the Photoshop APIs, using @mboucher/ffe-cli for OAuth, credential profiles, and authenticated HTTP. Manifest generation implements Photoshop v2 generateManifest: POST https://photoshop-api.adobe.io/v2/generate-manifest. Use the resulting manifest to extract tagged layers, check text fonts, and generate sample data for templates containing {{key}} placeholders. Layer extraction and font inspection use JSONata.

Commands

All commands run through the host CLI as ffe photoshop <command>.

| Command | Purpose | Stdout | | --- | --- | --- | | generate-manifest | Generate a Photoshop v2 manifest from a local PSD, document URL, or request JSON. | Absolute path to a temporary JSON file containing the complete successful job response. --no-wait prints accepted job JSON instead. | | get-tags | Extract Smart Objects named {{tag}} and text layers tagged in their name or contents, at any depth. | Absolute path to a separate temporary JSON file containing the matching layer array. | | supported-fonts | List saved supported PostScript fonts; use --update to refresh the reference list from Adobe. | Font-name JSON array by default or with --list; saved file path with --update alone. | | check-fonts | Compare fonts in extracted layers or a full manifest against the saved supported-font list. | JSON report; custom-font and incomplete-check warnings go to stderr. | | sample-csv | Create one CSV column per unique {{key}} in extracted layer names and text content, plus one example-data row. | Absolute path to the new CSV file; defaults to sample.csv in the current directory. |

Use ffe photoshop <command> --help for flags and examples:

ffe photoshop generate-manifest --help
ffe photoshop get-tags --help
ffe photoshop supported-fonts --help
ffe photoshop check-fonts --help
ffe photoshop sample-csv --help

Example workflow

After configuring Adobe credentials and the host's local-file upload support (see Generate a manifest), inspect a template and prepare its sample data:

# Initialize or refresh the shared supported-font reference list.
ffe photoshop supported-fonts --update

# Generate the manifest, then extract tagged text and Smart Object layers.
manifest_path="$(ffe photoshop generate-manifest \
  --input-file ./document.psd --profile production)"
layers_path="$(ffe photoshop get-tags --manifest "$manifest_path")"

# Inspect all text fonts, including untagged layers.
ffe photoshop check-fonts --input "$manifest_path"

# Generate CSV headers and one example row from {{key}} placeholders.
csv_path="$(ffe photoshop sample-csv \
  --input "$layers_path" --output ./data/sample.csv)"
cat "$csv_path"

The font reference is persistent and shared across projects. Manifest and layer files are temporary but remain available after the commands exit. The CSV is a persistent file that will not overwrite an existing output; replace its example values before using it in production. CSV generation fails if no placeholders are found.

Only manifest generation requires Adobe API credentials. supported-fonts --update accesses public Adobe documentation. Font listing and checks use a saved reference offline, but fetch and cache it when the file is missing. Layer extraction and CSV generation are offline.

Storage provider selection

For local-file manifest generation, the host also supports AWS S3:

ffe photoshop generate-manifest --input-file ./document.psd --provider aws --bucket inputs --region us-east-1 --aws-profile storage-production --profile adobe-production

This forwards storage options to ffe storage upload. Install and authenticate AWS CLI separately; --aws-profile selects AWS credentials while --profile selects Adobe credentials. Alternatively set FFE_STORAGE_PROVIDER=aws and AWS_S3_BUCKET for host defaults. Azure remains the default provider. S3 signed URLs request 12 hours but can expire sooner with temporary AWS credentials.

The CLI does not prompt for a provider. The template-validation skill asks the user to choose Azure Blob Storage or AWS S3 before a local PSD upload; the MCP ffe_storage_providers tool supplies guidance without uploading or choosing. Pass the chosen provider explicitly and authorize uploading and Adobe processing before running a command. No storage choice is needed for a supplied manifest or an existing document URL.

Local development

Requires Node.js 22.13+, npm, and the sibling ../ffe-cli checkout. The host is a peer dependency; the local development dependency links that checkout rather than bundling a second SDK.

cd ../ffe-cli
npm install
npm --prefix ../ffe-cli-ps-plugin install
npm run build
node bin/run.js photoshop generate-manifest --help

The host registers this package as a bundled plugin through a versioned npm dependency and oclif.plugins. When testing unpublished sibling changes, ensure the host resolves this checkout rather than a registry-installed copy. Its build compiles the host SDK first, then builds this plugin and generates its manifest. If ffe is on your PATH, use it in place of node bin/run.js from the host checkout. Rebuild after editing TypeScript. The plugin build also refreshes its generated command manifest so newly added commands are discoverable by the host.

From the plugin checkout:

npm run typecheck
npm test
npm pack

Tests use synthetic credentials and mocked HTTP/uploads, plus isolated host/plugin integration tests. They do not access the keyring, upload to Azure or AWS, or call Adobe APIs.

Publish to npm

From the plugin checkout, use an npm account authorized to publish @mboucher/ffe-cli-ps-plugin. For subsequent releases, update the package version first; npm does not allow overwriting a published version.

npm login
npm run deploy -- --dry-run
npm run deploy

deploy publishes the plugin with public access. Both the dry run and actual publish run typechecking and tests through prepublishOnly, then build the release files through prepack. The dry run previews the package without publishing it. Complete any npm authentication or two-factor prompts locally; do not put tokens in scripts or source control.

The plugin retains its @mboucher/ffe-cli peer dependency; publish a compatible host version separately before expecting registry-only consumer installation to work.

Update or list supported fonts

Fetch the current built-in Photoshop API font list from Adobe's Technical Usage Notes:

fonts_path="$(ffe photoshop supported-fonts --update)"

# List the saved font names without network access (both forms are equivalent).
ffe photoshop supported-fonts
ffe photoshop supported-fonts --list

# Refresh the file, then print the updated font names.
ffe photoshop supported-fonts --update --list

# Optional: choose a different file for either operation.
ffe photoshop supported-fonts --update --output ./supported-fonts.json
ffe photoshop supported-fonts --list --output ./supported-fonts.json

The default file is ~/.ffe/photoshop/supported-fonts.json, a predictable home-directory location (FFE_DATA_DIR replaces ~/.ffe). Harness access still depends on sandbox permissions; use --output with an accessible location and the same file with check-fonts --supported-fonts when needed. It is shared across projects, not stored in the current working directory or OS temp directory. --update fetches the documentation and atomically replaces the saved list after a successful extraction. Parent directories are created as needed; --output selects the local file to read or update, relative to the current directory.

  • No operation flag, or --list: read the saved file, or fetch it when missing, and print a pretty-printed JSON array of PostScript font names. The in-process return value is the same array. An existing reference and its timestamp are unchanged.
  • --update alone: refresh the file and print/return its absolute path.
  • --update --list: refresh and save first, then print/return only the updated font-name array, not the file path.

If the saved file is missing, the command fetches the list from the Adobe URL and saves it (when the location is writable) before continuing; a malformed file still fails with guidance to run --update (using the same --output path, if specified). No Adobe login, keyring access, or Photoshop API credentials are required.

The JSON contains sourceUrl, an ISO-8601 fetchedAt timestamp, and a fonts array of exact, case-preserved PostScript names (not family/display names). Names are deduplicated and sorted for deterministic future comparisons:

{
  "sourceUrl": "https://developer.adobe.com/firefly-services/docs/photoshop/getting-started/technical-usage-notes/",
  "fetchedAt": "2026-10-23T20:30:00.000Z",
  "fonts": ["AcuminVariableConcept", "AdobeArabic-Bold", "..."]
}

Extraction is scoped to the supported-fonts table, not unrelated documentation text. HTTP failures, a 30-second request timeout, missing/changed font tables, invalid names, empty lists, and filesystem errors fail explicitly without printing a success path. Failed fetches/parses/writes do not overwrite an existing list.

This command manages the reference list; use check-fonts below to compare text layer fonts against it. Adobe also supports some authorized Adobe Fonts and user-supplied fonts, so check-fonts reports names absent from this built-in list as custom fonts, not as universally unsupported fonts.

Get tags from a manifest

Use get-tags with a local --manifest JSON file. Both text and Smart Object layers are checked by default; --text or --smart-objects limits the layer types. This runs offline, with no credentials, uploads, or API calls. This command replaces extract-layers.

Smart Object names must consist entirely of a nonempty {{tag}} token, such as {{hero_image}}. Text layers match when their name is a {{tag}} token or their text.content contains a token, such as Hello {{first_name}}. Whitespace around the tag key is allowed. Untagged layers, empty tokens, escaped tokens, and triple braces are ignored.

For example, a Smart Object named {{hero_image}} matches, but one named Hero {{hero_image}} does not. A text layer named Headline with contents Hello {{first_name}} matches even though its name has no tag.

ffe photoshop get-tags --manifest manifest.json --text
ffe photoshop get-tags --manifest manifest.json --smart-objects
layers_path="$(ffe photoshop get-tags --manifest manifest.json)"
cat "$layers_path"

# Capture the generated temporary file path, then extract from its embedded JSON.
manifest_path="$(ffe photoshop generate-manifest --input-file ./document.psd)"
layers_path="$(ffe photoshop get-tags --manifest "$manifest_path")"
cat "$layers_path"

Input can be a raw Photoshop v2 manifest with a top-level layers array, or a successful job response containing embedded JSON manifests under result.outputs. Hosted/URL-only outputs and embedded string/base64 outputs must first be downloaded/decoded into a raw JSON manifest file; this command does not fetch URLs. Stdin is not supported.

JSONata recursively matches the v2 type values text_layer and smart_object_layer, including layers inside groups (children), artboards (layers), and deeper nesting, then filters for tags. Tagged linked and embedded Smart Objects, and hidden layers, are included. There is no configured depth limit. V1 camelCase layer types are not supported.

The command writes a pretty-printed JSON array of complete, unchanged matching layer objects, not a list of tag names or just their text or Smart Object metadata, to a separate, unique ffe-ps-layers-*/layers.json file under the OS temporary directory. Stdout and the in-process return value contain only its absolute path, not the JSON array. The input manifest is never modified. The directory is private and the file is owner-only (0600 on POSIX). The file remains after the command exits; delete it when no longer needed, or let OS temporary-directory cleanup apply.

Zero matches save []; one match still saves an array. Using both selectors combines the results without duplicating a layer. Multiple embedded manifests are combined in output order. Matching parents retain any nested data in their original objects. Missing/unreadable files, invalid JSON, and unsupported manifest structure fail with a nonzero exit status instead of returning an empty success. File-write failures remove incomplete output and do not print a success path.

Generate a sample CSV from data placeholders

Scan the layer JSON array returned by get-tags for unique {{key}} data placeholders in layer names and text.content. This includes Smart Object layer names, hidden layers, and layers nested under groups/artboards. Other metadata (font names, URLs, etc.) is not scanned.

# Creates sample.csv in the current directory and prints its absolute path.
ffe photoshop sample-csv --input layers.json

# Choose another output file.
ffe photoshop sample-csv --input layers.json --output ./data/sample.csv

# Use the temporary file returned by get-tags.
layers_path="$(ffe photoshop get-tags --manifest manifest.json)"
ffe photoshop sample-csv --input "$layers_path"

For placeholders {{headline}}, {{customer.name}}, and {{hero_image}}, the generated CSV contains:

headline,customer.name,hero_image
Example headline,Example customer.name,Example hero_image

Every unique key becomes one column, followed by one sample-data row. Replace all example values with real data before production use; image columns need valid image references, not the generated example text. Keys are case-sensitive, whitespace around a key is trimmed, and columns follow first-occurrence order (layer name before text content, then nested layers). Repeated keys produce only one column.

This is literal double-brace placeholder extraction, not a Handlebars engine: the trimmed contents of each {{...}} token are treated as its key, without evaluating helpers or block syntax. Escaped tokens (\{{key}}), triple braces ({{{key}}}), and empty placeholders are ignored. UTF-8 CSV output uses CRLF record endings and quotes/escapes commas, quotes, and newlines in keys and values. Keys starting with =, +, -, or @ are rejected to avoid spreadsheet formula execution without silently changing column names.

The command runs offline and prints/returns only the absolute saved file path. Output directories are created as needed; files use owner-only permissions (0600 on POSIX). Existing files, including the input, are never overwritten: choose a different --output path to generate another sample. Missing/unreadable files, invalid JSON/layer structure, no usable placeholders, and write failures fail explicitly without printing a success path. Incomplete new output is removed if writing fails.

Check text layers for custom fonts

Use the saved supported-font reference list to check an extracted-layer array, a raw Photoshop v2 manifest, or a successful generation response containing embedded JSON manifests:

# Only needed to initialize or refresh the reference list.
ffe photoshop supported-fonts --update

ffe photoshop check-fonts --input manifest.json
ffe photoshop check-fonts --input layers.json

# Optional: compare against a different supported-font file.
ffe photoshop check-fonts --input job.json --supported-fonts ./supported-fonts.json

# Check all text layers in the generated manifest, not just tagged layers.
manifest_path="$(ffe photoshop generate-manifest --input-file ./document.psd)"
ffe photoshop check-fonts --input "$manifest_path"

The command never updates the input or an existing supported-font reference file. By default it reads the shared file created by supported-fonts --update; if that file is missing it fetches the list from Adobe (public documentation, no authentication) and saves it. A malformed reference file fails with refresh guidance.

Passing the output of get-tags checks only the text layers in that filtered array. Pass the full manifest or generation response to check untagged text too.

JSONata selects all text_layer objects recursively, including hidden layers, groups, and artboards. A second JSONata query collects font.postScriptName throughout each layer's text object, covering multiple character styles and nested characterStyle entries. It also includes legacy fontName fields within the text metadata and the v2 text.missingFonts names. Non-text layers and unrelated document metadata do not contribute font names. Comparison uses exact, case-sensitive PostScript names, not display/family names. Used/custom font names are deduplicated and sorted.

Custom fonts produce a warning on stderr listing every custom font name. Stdout and the in-process return value contain a JSON report:

{
  "textLayersChecked": 2,
  "fontsUsed": ["BrandSans-Bold", "MyriadPro-Regular"],
  "customFonts": ["BrandSans-Bold"],
  "complete": true,
  "incompleteTextLayers": []
}

Text layers with no usable font names or with invalid font-name metadata produce an additional warning and complete: false. incompleteTextLayers identifies them by a 1-based index in the selected text-layer list, plus ID/name when present. Known fonts are still compared and reported; missing information is never treated as a clean result. An input with no text layers reports zero checked layers.

Custom-font and incomplete-check warnings do not set a failing exit status. Unreadable files, invalid JSON/manifest structure, invalid reference lists, or a failed fetch for a missing reference fail instead of printing a success report. Here "custom" means absent from the saved built-in list, not necessarily unusable: authorized Adobe Fonts and user-supplied fonts may still be available.

Generate a manifest

Authenticate through the host with credentials entitled to the Photoshop API. Use the scopes granted to your Developer Console project; the host's default Firefly scopes may not cover your Photoshop entitlement.

ffe auth login --profile production --scopes YOUR_GRANTED_SCOPES
ffe photoshop generate-manifest \
  --input-url 'https://example.com/document.psd?presigned-query' \
  --include-layer-thumbnails --include-xmp \
  --extract-smart-object-data --maximum-thumbnail-depth 0 \
  --profile production

--input-url creates this request, with optional flags mapped to their API fields:

{
  "image": {"source": {"url": "https://example.com/document.psd"}},
  "outputs": [{"destination": {"embedded": "json"}, "mediaType": "application/json"}]
}

Other convenience flags: --trim-to-transparency and --maximum-thumbnail-depth (nonnegative int32; 0 means unlimited). The API must be able to retrieve the URL. Use --input-file for a local document. Quote presigned URLs in your shell. For sensitive URLs, prefer a request file to avoid shell history and process-argument exposure.

Local document upload through the host CLI

--input-file invokes the main CLI's storage upload command in-process, then uses its read-only signed URL as the Photoshop request's image.source.url. It reuses the host's selected provider and upload-error handling; the plugin has no separate cloud storage SDK or credential implementation. Azure is the CLI default unless FFE_STORAGE_PROVIDER is set; --provider overrides it.

For Azure, require an up-to-date host with storage upload, Azure CLI (az) installed on PATH, an az login session, and an existing container. Your Azure identity needs Storage Blob Data Contributor at account scope, or that role at container scope plus Storage Blob Delegator at account scope for SAS generation. These Azure permissions are separate from Adobe credentials selected by --profile.

For AWS, use --provider aws --bucket <bucket> with configured AWS CLI credentials, the bucket's region, and optional --aws-profile. The identity needs S3 upload/read permissions and any applicable KMS permissions. Azure CLI is not required for S3. The requested URL lifetime is 12 hours, but temporary credentials or policies can shorten access. No buckets, containers, or access policies are created by manifest generation.

az login
ffe storage configure
ffe photoshop generate-manifest \
  --input-file ./document.psd \
  --include-layer-thumbnails --include-xmp \
  --profile production

# Optional: override the host's Azure destination selection.
ffe photoshop generate-manifest \
  --input-file "./design files/document.psd" \
  --account mystorageaccount --container inputs \
  --no-wait

If --account is omitted, the host uses AZURE_STORAGE_ACCOUNT, or discovers Storage accounts in the active Azure CLI subscription. If --container is omitted, it uses AZURE_STORAGE_CONTAINER, then the keyring default saved by ffe storage configure. Multiple discovered accounts prompt interactively; noninteractive runs require an explicit/environment account when ambiguous.

Exactly one of --input-file, --input-url, or --request is required. Storage flags (--provider, --account, --container, --bucket, --region, and --aws-profile) are accepted only with --input-file. The same manifest convenience flags work for either file or URL input. The upload's signed URL is not separately printed; stdout contains only the temporary JSON file path (or accepted job JSON with --no-wait). Upload failures stop the command before calling Photoshop. --timeout covers Photoshop submission/polling, not the preceding storage upload. Uploaded blobs or S3 objects are not deleted if Photoshop fails or URLs expire.

Full request file

Use --request for all API options, including JPEG/PNG export settings, Creative Cloud or Lightroom sources, multiple outputs, external URL destinations, or hosted output with validityPeriod (60–86400 seconds). The JSON body is passed through without discarding API fields. Core structure, common option types, and output counts (1–25) are validated locally; the service performs full API-specific validation.

Example request.json:

{
  "image": {"source": {"url": "https://example.com/document.psd"}},
  "outputs": [
    {"destination": {"embedded": "json"}, "mediaType": "application/json"}
  ],
  "includeLayerThumbnails": true,
  "includeXmp": true,
  "extractSmartObjectData": true,
  "maximumThumbnailDepth": 0,
  "trimToTransparency": false,
  "exportOptions": {"mediaType": "image/png", "compression": "high"}
}
ffe photoshop generate-manifest --request request.json --profile production
ffe photoshop generate-manifest --request request.json --no-wait

--request cannot be combined with --input-url, --input-file, or the convenience-option flags. Request-file sources are passed through as supplied; the command does not interpret local paths inside JSON as upload instructions. --profile defaults to default, or the host's FFE_PROFILE environment variable.

Async jobs and output

  • By default, the command waits, writes the complete successful job-status response as pretty-printed JSON to a unique ffe-ps-manifest-*/manifest.json file under the OS temporary directory, and prints only its absolute path to stdout. In-process command callers also receive that path as the return value. The file includes job metadata and result.outputs, not just the raw manifest. Embedded output representation follows the API; the command does not flatten it.
  • Temporary directories are private and files are created with owner-only permissions (0600 on POSIX). Files remain after the command exits for use by get-tags or other tools. Delete them when no longer needed; OS temporary-directory cleanup policies also apply.
  • --no-wait prints the accepted {jobId, statusUrl} JSON response without polling or creating a temporary file, since no completed manifest is available.
  • --poll-interval defaults to 2 seconds. --timeout defaults to 300 seconds and covers submission and polling. Both accept positive integer seconds.
  • Failed jobs, unknown statuses, malformed responses, timeouts, and HTTP errors fail with a nonzero exit status. Failed-job response bodies are not printed. A timeout does not cancel the remote job; the error includes its ID when known.
  • File-creation/write failures also fail without printing a success path. Incomplete output is removed if writing fails.
  • Polling always uses /v2/status/{jobId} on the fixed Photoshop origin. A returned statusUrl cannot redirect authenticated requests elsewhere.
  • No automatic resubmission/retries: avoiding duplicate jobs is more important than hiding transient failures. Hosted/presigned outputs are not downloaded.

Saved JSON responses may contain presigned URLs or document metadata. Treat them as sensitive, along with request files; do not commit either.

Activity indicator

The command uses the host CLI's rotating activity spinner while work is in progress:

  • With --input-file, separate indicators cover reading the saved container default, discovering Azure Storage accounts, and uploading/signing the file, when those steps are needed.
  • Generating Photoshop manifest covers submission and, by default, waiting for the job to complete. With --no-wait, it covers submission only.
  • Each indicator stops with done or failed. Indicators stop before interactive prompts and final path/JSON output, including when an operation fails.

The spinner writes only to stderr; stdout contains the temporary file path (or accepted job JSON with --no-wait). Capture the path while keeping the animation visible in your terminal:

manifest_path="$(ffe photoshop generate-manifest --input-file ./document.psd)"
cat "$manifest_path"

Animation requires interactive stdin and stderr. It is automatically disabled in CI (when CI is set), when either stream is not a terminal, and for TERM=dumb or TERM=emacs-color. It indicates activity, not percentage complete or estimated time remaining.

Publishing and JIT registration

The package is JIT-ready, but the host currently bundles the sibling local dependency rather than using JIT installation. This setup requires both checkouts and is not ready for publishing as-is. To switch the host to JIT after publishing:

  1. Use the npm package name @mboucher/ffe-cli-ps-plugin and choose the version. Ensure the matching @mboucher/ffe-cli peer version is published before releasing.

  2. Run npm test and npm pack. The prepack script builds TypeScript and generates oclif.manifest.json; both are included in the package.

  3. Publish the plugin to the registry used by the host. The local file:../ffe-cli development dependency is not a runtime dependency.

  4. In the host's package.json, register the published exact version:

    {
      "oclif": {
        "jitPlugins": {
          "@mboucher/ffe-cli-ps-plugin": "0.1.0"
        }
      }
    }

Remove the local @mboucher/ffe-cli-ps-plugin dependency and its oclif.plugins entry when switching to JIT, and decouple the host build from the sibling checkout.

  1. Run npm pack in the host to rebuild its merged command manifest, then release the host. Registry access is required for that merge.

The host JIT catalog intentionally remains unchanged until the plugin is published. Once registered and released, ffe photoshop generate-manifest prompts to install the plugin on first use. For CI/noninteractive use, preinstall the exact version with ffe plugins install @mboucher/[email protected].