---
name: crop-photos
description: Crop a folder of campaign photos into adidas component sizes with the Digital Toolkit Engine's Crop API. Use when someone asks to "crop the photos in my folder", "crop my SharePoint folder for <campaign>", "cut these photos for the homepage banner", or to run crop_batch on a folder of photos.
---

# Crop photos with the Crop API

You run the Crop API's batch script for the user and explain the result in
plain words. The user is often not a developer: short sentences, no jargon.

Install (once): copy this folder to `~/.claude/skills/crop-photos/`. Put
`crop_batch.ts` and `crop_batch.py` beside this file if you have them;
otherwise step 3 downloads them.

## 1. The key — from the environment only

The key lives in the environment variable `CROP_API_KEY`. Check it like this,
and never any other way:

```bash
[ -n "$CROP_API_KEY" ] && echo "key is set" || echo "key is missing"
```

- **Never** ask the user to paste the key into the chat, never print it,
  never write it to a file, never put it in a command you show them.
- If it is missing, give them these steps, word for word, then stop until
  they come back:

  > 1. Open the Terminal app on your Mac (not this chat).
  > 2. Type this line, but put your key from LastPass where it says
  >    `PASTE-YOUR-KEY-HERE`:
  >
  >    ```
  >    export CROP_API_KEY='PASTE-YOUR-KEY-HERE'
  >    ```
  >
  >    Keep the two single quotes `'` (the key next to Enter): one right
  >    before the key, one right after. No `<` `>`, no spaces around `=`,
  >    all on one line. If you copy this line from an email or Word, retype
  >    the quotes yourself: curly quotes (‘ ’) do not work.
  > 3. Press Enter, then type `claude` and press Enter. Ask for the crop in
  >    that window.

- **Running in VS Code or the desktop app?** A key set in a Terminal window
  only reaches programs started from that window, so this session will keep
  saying "missing" even after step 2. Tell them to use the Terminal window
  from step 3 instead.

## 2. What to crop — always ask, then confirm

Never start a crop on a guess. Even when the request seems to say everything,
ask before running, and run only after the user says yes.

| Field | Flag | Default | Plain words → value |
|---|---|---|---|
| Photos | `--photos <local folder>` or `--photos-from "<SharePoint folder>"` | none: ask | A path on this computer → `--photos`. "My SharePoint folder", "the test photos" or a path starting `API TEST/` → `--photos-from`: look the folder up first (below), never guess it |
| Components | `--template` | `"Banner Hero@HP"` | see the list below |
| Product | `--product` | `footwear` | shoes → footwear · clothes → apparel · both |
| Category | `--category` | ask | Football · Originals · Running |
| Campaign | `--campaign` | ask (may skip) | the campaign name, e.g. `"Predator Touch"`. With it the files get our content names |
| Season | `--season` | ask if campaign given | e.g. `FW26`, `SS27` |
| Phase | `--phase` | `Launch` | Introduce · Launch · Sustain · Exit (or `Unphased`) |
| Market | `--market` | `global` | global · EU · NAM · LAM · EM · JP · KR |
| Which photo | `--select` | `best` | best = each component from the photo whose shape fits it · all = every photo to every component |

`--campaign` needs `--category` and `--season`.

**How to ask.** Use the question tool (`AskUserQuestion`) so the user can
click an answer instead of typing it. Ask the questions in this order:

1. **Where are the photos?** Two answers:
   - **On this computer** → then ask for the folder. Tell them the easy way:
     "Drag the folder from Finder into this window and press Enter; that
     types its path for you." Check the folder exists and count the photos
     in it (`.jpg`, `.jpeg`, `.png`, `.webp`). No photos → say so and ask
     again.
   - **SharePoint test folder** → run the folder lookup below first, then
     offer what it returns for this key, including the two test sets
     (`test-20-photos`, `test-predator-footwear`). Never name a folder the
     lookup did not return.
2. **Which components?** Allow more than one answer. Offer them in plain
   words ("homepage banner", "homepage tile", "email header", "app drop
   card"); "Other" covers the rest. The user never types a page code: you
   turn their words into `"<Component>@<PAGE>"` with the table below.
3. **Which campaign?** Skip this when a test set already decides it (see
   the test-set table). Otherwise ask for product, category, campaign,
   season and phase, with the defaults marked "(default)".

Ask about market and "which photo" only when the user brings them up.
Otherwise use the defaults and show them in the check below.

**Check before running.** List everything that will happen in plain words,
then ask "Shall I start?":

> I will crop:
> - **Photos:** `API TEST/adidas-tester-2/test-20-photos` (20 photos)
> - **Into:** homepage banner, homepage tile
> - **As:** Originals clothes, adiSignature FW26, Launch, global
> - **Photo choice:** each component gets the photo that fits it best
> - **Crops go to:** `result.zip` here, and `…/test-20-photos/crops/` on SharePoint
>
> Shall I start?

Run only after a clear yes. If the user changes anything, show the full
list again before running.

### SharePoint: which folders this key may use

Ask the API, never guess the user's folder name (a tester does not know
whether they are `adidas-tester-2`):

```bash
curl -s -H "Authorization: Bearer $CROP_API_KEY" https://api.toolkitengine.com/crop/v1/sources
```

It answers `{"folders": [...]}`: the only folders this key may read. Offer
those by name. Empty list → this key has no SharePoint folder: use photos on
this computer, or ask Florien to give the key one. A folder named `crops` is
where results land, never a source.

Every tester folder holds two ready-made test sets. When the user says "the
test photos" (or names one), use it with these values:

| Folder | What is in it | `--product` | `--category` | `--campaign` / `--season` |
|---|---|---|---|---|
| `<their folder>/test-20-photos` | adiSignature, 20 photos with people | `apparel` | `Originals` | `adiSignature` / `FW26` |
| `<their folder>/test-predator-footwear` | Predator, 5 boot photos | `footwear` | `Football` | `"Predator Touch"` / `FW26` |

To add their own photos, the user makes a new folder inside their folder on
SharePoint ("Campaigns Workspace" library → `API TEST` → their folder) and drops
10 to 20 photos in it. They need SharePoint access to their folder for that; the
API key does not give it. If they cannot open it, they ask Florien.

**Components** are written `"<Component>@<PAGE>"`. The page goes into the
file names and decides the channel (web, app or email), so always send one.
Which components live on which page — ask the API, it is the same list the
people page shows:

```bash
curl -s -H "Authorization: Bearer $CROP_API_KEY" https://api.toolkitengine.com/crop/v1/touchpoints
```

Page codes: HP homepage · CATLP category landing page · CAMLP campaign landing
page · GLP gender landing page · WGLP women's landing page · MGLP men's landing
page · PLP product listing page · PDP product page · APP app ·
`APP [Drop Detail Page]` app drop detail page · CRM email · PN push notification.
A component may live on several pages (Banner Hero on HP, CATLP, GLP …): if the
user does not say which, ask, or cut it for each page they name.

**How many.** Add ` xN` after the page for more than one: `"Card Teaser@HP x3"`
cuts three DIFFERENT photos into Card Teasers, named `…-card_teaser_1`, `_2`,
`_3` (1-10; without a number it is one). For a tiled component the number is
its tiles and only the content plans' counts work: Group Media 2/3/4, Carousel
- User Generated 6/8, Group Header 1-4; `GET /crop/v1/touchpoints` lists them.
When the user says a plural ("three teasers", "a carousel"), ask how many. If
the batch has fewer suitable photos than asked, the report flags it — say so.

| They say | Use |
|---|---|
| homepage banner / hero | `"Banner Hero@HP"` |
| homepage tile / teaser | `"Card Teaser@HP"` |
| category page banner | `"Banner Hero@CATLP"` |
| landscape teaser | `"Card Teaser — Landscape@HP"` (em dash, exactly) |
| women's landing page banner | `"Banner Hero@WGLP"` (only photos showing women) |
| men's landing page banner | `"Banner Hero@MGLP"` (only photos showing men) |
| campaign page statement | `"Banner Statement@CAMLP"` |
| product listing page banner | `"Banner Statement@PLP"` |
| group media (2, 3 or 4 tiles) | `"Group Media@PLP x3"` — the number is the tiles; 2 picks the 2-tile size |
| app drop card | `"Drop Card@APP"` |
| app drop page header / group header | `"Header Asset@APP [Drop Detail Page]"` · `"Group Header@APP [Drop Detail Page]"` |
| email hero / email header | `"Hero Block Image@CRM"` |
| email story / category / UGC block | `"Storytelling Block@CRM"` · `"Category Block@CRM"` · `"CRM UGC Block@CRM"` |
| push notification image | `"Rich Push@PN"` (iOS square + Android wide) |

One job can mix web, app and email components. The one thing it refuses is
the same component on two channels (Group Media on PLP and in the app) —
then run two jobs.

## 3. Find a way to run the script

```bash
node -e 'process.exit(+process.versions.node.split(".")[0] >= 18 ? 0 : 1)' 2>/dev/null && echo node || echo python
```

- **node** → `npx -y tsx crop_batch.ts …`; **python** → `python3 crop_batch.py …`.
- Look for the script beside this SKILL.md first. If it is not there, download
  it into a new folder in the user's working directory:
  `curl -fsSL -o crop_batch.ts https://api.toolkitengine.com/developers/examples/crop_batch.ts`
  (or `…/crop_batch.py`).

## 4. Run it

Show the user the command **without** the key, then run it with the key from
the environment. Example, local photos:

```bash
npx -y tsx crop_batch.ts --key "$CROP_API_KEY" --photos ./my-photos \
  --template "Banner Hero@HP" "Card Teaser@CATLP" --product apparel \
  --category Originals --campaign "adiSignature" --season FW26 --phase Launch \
  --select best --out result.zip
```

SharePoint photos: replace `--photos ./my-photos` with
`--photos-from "API TEST/<their folder>/<shoot>"`. Crops are then also written
back to `<that folder>/crops/<job id>/` (add `--no-write-back` to skip that).

Up to 50 photos per run. It takes a few seconds per photo; let it finish.
If it stops with an error, read the error and say what it means in one line
(401 = key wrong or switched off; 403 = this key may not read that SharePoint
folder; 429 = too many runs today; 400 = a name or code is wrong, the message
says which).

## 5. Explain the result

Read the report inside the zip:

```bash
unzip -p result.zip report.json | python3 -c '
import json, sys
r = json.load(sys.stdin)
print("job", r["job_id"], "| photos", len(r["photos"]), "| not used:", r.get("unused") or "none")
for f in r.get("flagged", []): print("CHECK", f.get("file") or f.get("entry"), "|", "; ".join(f.get("warnings") or [f.get("reason", "")]))
for p, why in (r.get("failed") or {}).items(): print("FAILED", p, "|", why)'
```

Then tell the user, in plain words:

1. **Where the crops are**: the zip's full path (a folder per photo inside),
   and for a SharePoint run the folder `<photos folder>/crops/<job id>/`
   (the script prints the link).
2. **What to check**: each flagged crop by file name, and the warning in
   everyday words, for example "the person overlaps where the text goes, so
   the crop may need moving" or "the photo had to be enlarged, it may look
   soft". Say how many crops needed no check.
3. **Photos that failed or were not used**, and why (a photo not used was a
   worse fit than another one, or the wrong gender for WGLP/MGLP).
4. To move a crop by hand: open https://api.toolkitengine.com/start, "Try one
   photo", press Adjust.

Never call a run good without opening the report. Problems go to Florien or
into `API TEST/feedback.xlsx` on SharePoint (file name, what is wrong, screenshot).
