[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71KA1YKP9D9VYMCXMYMA3SY":3,"skill-same:01M71KA1YKP9D9VYMCXMYMA3SY":63},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":28,"linkOnly":29,"markdownContent":30,"markdownBody":31,"frontMatter":32,"refName":33,"commitSha":34,"skillDirectory":35,"forksCount":36,"repositoryHtmlUrl":37,"repositoryHomepageUrl":38,"ownerHtmlUrl":39,"ownerBlogUrl":40,"ownerDescription":41,"updatedAt":42,"install":43,"resources":56},"01M71KA1YKP9D9VYMCXMYMA3SY","photo-editor",null,"Edit, resize, crop, filter, and optimize images — backgrounds, watermarks, and batch processing.","clawdsolana\u002FOpenClawd","clawdsolana","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F279036008?v=4","01M71JTVF9FNGKE2SVJRPTQM31","OpenClawd","01M71JTVGCKWBSS8ZYC1HYWVA5",2,"MIT","https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fsecondary_skills\u002Fphoto-editor\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27],"photo editor","editor","images","processing","optimize images","batch","filter","optimize","edit","2026-11-10 10:12:30",false,"---\nname: photo-editor\ndescription: Edit, resize, crop, filter, and optimize images — backgrounds, watermarks, and batch processing.\n---\n\n# Photo Editor\n\n## OpenClawd Operator Adaptation\n\nRun this skill as part of the OpenClawd operator deck. Preserve the skill-specific workflow below, but frame outputs for Solana-native agents when relevant: prefer OpenClawd language, note whether the work can support autonomous agent operations, and keep financial, legal, tax, hiring, medical, or other regulated outputs informational unless the skill already requires a stricter disclaimer. Use the Llobster Legend persona only as light operator framing; do not let branding override accuracy, safety, or the user's stated domain.\n\n\nResize, crop, filter, and optimize images. Pillow for Python, sharp for Node. Clarify intent before starting.\n\n## Clarify Intent First\n\nWhen a user asks to \"edit a photo\" or \"change an image,\" the request could mean two very different things. **Ask before proceeding** if it's ambiguous:\n\n1. **Edit the existing image** — crop, resize, recolor, adjust brightness\u002Fcontrast, add text, remove background, apply filters, watermark, etc. → Use the tools below (Pillow, sharp, OpenCV).\n2. **Generate a new AI image** — create something from scratch or heavily reimagine the photo (e.g., \"make this photo look like a painting,\" \"put me on a beach,\" \"create a logo from this concept\"). → Use image generation tools instead, not this skill.\n\n### When to ask\n\n- \"Can you fix this photo?\" → Probably editing. Ask what specifically needs fixing.\n- \"Make this look better\" → Ambiguous. Ask: \"Do you want me to adjust the existing photo (brightness, contrast, cropping, etc.) or generate a new version with AI?\"\n\n- \"Change the background\" → Could be either. Ask: \"Should I remove the current background (I can make it transparent or a solid color), or do you want an AI-generated scene behind you?\"\n- \"Make a profile picture from this\" → Likely crop\u002Fresize, but could mean AI enhancement. Clarify.\n\n#### Don't ask when it's obvious\n\n- \"Crop this to 1080x1080\" → Just crop it.\n- \"Make this a PNG\" → Just convert it.\n\n- \"Remove the background\" → Use rembg.\n- \"Generate a photo of a sunset\" → No existing photo to edit — use image generation.\n\n## Tool Selection\n\n| Tool | Use when | Install |\n\n|---|---|---|\n\n| **Pillow** | Default: resize, crop, filters, text, format conversion | `pip install Pillow` |\n\n| **OpenCV** | Computer vision: face detection, perspective transform, inpainting, contours | `pip install opencv-python numpy` |\n\n| **sharp** (Node) | High-volume pipelines — 4-5x faster than Pillow (libvips-backed) | `npm install sharp` |\n\n| **rembg** | AI background removal | `pip install rembg` |\n\n| **ImageMagick** | CLI batch ops, 200+ formats | `apt install imagemagick` |\n\n## Open — ALWAYS Fix Orientation First\n\n```python\n\nfrom PIL import Image, ImageOps\n\nimg = Image.open(\"photo.jpg\")\n\nimg = ImageOps.exif_transpose(img) \\# CRITICAL: applies EXIF rotation, then strips tag\n\n# Without this, phone photos appear sideways after processing\n\n```\n\n## Resize & Crop\n\n```python\n\nfrom PIL import Image, ImageOps\n\n# --- Fit inside box, keep aspect ratio (shrink only) ---\n\nimg.thumbnail((1080, 1080), Image.Resampling.LANCZOS) \\# modifies in place\n\n# --- Exact size, keep aspect, center-crop overflow (best for thumbnails) ---\n\nthumb = ImageOps.fit(img, (300, 300), Image.Resampling.LANCZOS, centering=(0.5, 0.5))\n\n# --- Exact size, keep aspect, pad with color (letterbox) ---\n\npadded = ImageOps.pad(img, (1920, 1080), color=(0, 0, 0))\n\n# --- Exact size, ignore aspect (will distort) ---\n\nstretched = img.resize((800, 600), Image.Resampling.LANCZOS)\n\n# --- Scale by factor ---\n\nhalf = img.resize((img.width \u002F\u002F 2, img.height \u002F\u002F 2), Image.Resampling.LANCZOS)\n\n# --- Manual crop (left, upper, right, lower) — NOT (x, y, w, h) ---\n\ncropped = img.crop((100, 50, 900, 650))\n\n```\n\n**Resampling filters:** `LANCZOS`for photo downscale (best quality),`BICUBIC`for upscale,`NEAREST` for pixel art\u002Ficons (no smoothing).\n\n## Face-Aware Cropping\n\nFor portraits and headshots, detect the face first and crop around it instead of guessing coordinates. This produces much better results for profile pictures.\n\n```python\n\nimport cv2\n\nimport numpy as np\n\nfrom PIL import Image, ImageOps\n\nimg = Image.open(\"portrait.jpg\")\n\nimg = ImageOps.exif_transpose(img)\n\ncv_img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)\n\ngray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)\n\ncascade = cv2.CascadeClassifier(cv2.data.haarcascades + \"haarcascade_frontalface_default.xml\")\n\nfaces = cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(100, 100))\n\nif len(faces) > 0:\n\n# Use the largest detected face\n\nfx, fy, fw, fh = max(faces, key=lambda f: f[2] * f[3])\n\nface_cx = fx + fw \u002F\u002F 2\n\nface_cy = fy + fh \u002F\u002F 2\n\n# Square crop centered on face with padding (3x face height for head+shoulders)\n\ncrop_size = min(img.width, img.height, fh * 3)\n\nleft = max(0, face_cx - crop_size \u002F\u002F 2)\n\ntop = max(0, face_cy - int(crop_size * 0.35)) \\# face in upper third\n\nright = left + crop_size\n\nbottom = top + crop_size\n\n# Clamp to image bounds\n\nif right > img.width:\n\nleft -= (right - img.width)\n\nright = img.width\n\nif bottom > img.height:\n\ntop -= (bottom - img.height)\n\nbottom = img.height\n\nleft = max(0, left)\n\ntop = max(0, top)\n\ncropped = img.crop((left, top, right, bottom))\n\nprofile = cropped.resize((800, 800), Image.Resampling.LANCZOS)\n\nprofile.save(\"profile_800x800.jpg\", quality=92, optimize=True)\n\nelse:\n\n# Fallback: center crop\n\nprofile = ImageOps.fit(img, (800, 800), Image.Resampling.LANCZOS, centering=(0.5, 0.4))\n\nprofile.save(\"profile_800x800.jpg\", quality=92, optimize=True)\n\n```\n\n### Tips\n\n- `centering=(0.5, 0.4)` in the fallback biases the crop slightly toward the top — better for portraits than dead center.\n- For group photos with multiple faces, you may want to fit all detected faces in the crop instead of picking the largest.\n\n## Color & Exposure\n\n```python\n\nfrom PIL import ImageEnhance, ImageOps\n\n# --- Enhancers: 1.0 = unchanged, \u003C1 less, >1 more ---\n\nimg = ImageEnhance.Brightness(img).enhance(1.15)\n\nimg = ImageEnhance.Contrast(img).enhance(1.2)\n\nimg = ImageEnhance.Color(img).enhance(1.1) \\# saturation\n\nimg = ImageEnhance.Sharpness(img).enhance(1.5)\n\n# --- Quick ops ---\n\ngray = ImageOps.grayscale(img)\n\ninverted = ImageOps.invert(img.convert(\"RGB\"))\n\nauto = ImageOps.autocontrast(img, cutoff=1) \\# stretch histogram, clip 1% extremes\n\nequalized = ImageOps.equalize(img) \\# flatten histogram\n\n```\n\n## Filters\n\n```python\n\nfrom PIL import ImageFilter\n\nimg.filter(ImageFilter.GaussianBlur(radius=5))\n\nimg.filter(ImageFilter.UnsharpMask(radius=2, percent=150, threshold=3)) \\# better than SHARPEN\n\nimg.filter(ImageFilter.BoxBlur(10))\n\nimg.filter(ImageFilter.FIND_EDGES)\n\nimg.filter(ImageFilter.MedianFilter(size=3)) \\# denoise, removes salt-and-pepper\n\n```\n\n## Watermark \u002F Logo Removal\n\nUse OpenCV's `cv2.inpaint()`— it fills a masked region by sampling surrounding pixels, producing seamless results. **Do not use pixel-by-pixel`getpixel`\u002F`putpixel` loops** — they are slow and produce visible artifacts.\n\n### Step 1: Find the watermark boundaries\n\nAlways inspect the image at full resolution first. Watermarks are often much larger than they appear in thumbnails. Save a crop of the watermark region to verify coordinates before attempting removal.\n\n```python\n\nimport cv2\n\nimport numpy as np\n\nfrom PIL import Image, ImageOps\n\nimg = Image.open(\"photo.jpg\")\n\nimg = ImageOps.exif_transpose(img)\n\nw, h = img.size\n\n# Save a debug crop of the suspected watermark area to verify its extent\n\ndebug = img.crop((0, 0, min(w, 1000), min(h, 500)))\n\ndebug.save(\"debug_watermark_area.jpg\")\n\n# IMPORTANT: View this debug image to confirm where the watermark actually is\n\n# before proceeding. Guessing coordinates wastes iterations.\n\n```\n\n### Step 2: Create a mask and inpaint\n\n```python\n\ncv_img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)\n\n# --- Option A: Color-based mask (best for colored logos on neutral backgrounds) ---\n\nhsv = cv2.cvtColor(cv_img, cv2.COLOR_BGR2HSV)\n\n# Example: detect blue watermark pixels (adjust ranges for your watermark color)\n\nlower_blue = np.array([90, 40, 40])\n\nupper_blue = np.array([130, 255, 255])\n\nmask = cv2.inRange(hsv, lower_blue, upper_blue)\n\n# Also catch dark text pixels in the watermark region\n\nroi_gray = cv2.cvtColor(cv_img[:wm_h, :wm_w], cv2.COLOR_BGR2GRAY)\n\n_, dark_mask = cv2.threshold(roi_gray, 80, 255, cv2.THRESH_BINARY_INV)\n\n# Combine: place dark_mask into the full-size mask\n\nmask[:wm_h, :wm_w] = cv2.bitwise_or(mask[:wm_h, :wm_w], dark_mask)\n\n# --- Option B: Region-based mask (when you know the bounding box) ---\n\n# Simpler but removes everything in the box, not just the watermark pixels\n\nmask = np.zeros(cv_img.shape[:2], dtype=np.uint8)\n\nmask[0:wm_h, 0:wm_w] = 255 \\# fill the entire watermark region\n\n# Dilate the mask slightly to catch anti-aliased edges\n\nkernel = np.ones((5, 5), np.uint8)\n\nmask = cv2.dilate(mask, kernel, iterations=2)\n\n# Inpaint — fills masked area using surrounding pixel data\n\nresult = cv2.inpaint(cv_img, mask, inpaintRadius=7, flags=cv2.INPAINT_TELEA)\n\n# INPAINT_TELEA: fast marching method (best for most cases)\n\n# INPAINT_NS: Navier-Stokes (better for large regions, slower)\n\nresult_pil = Image.fromarray(cv2.cvtColor(result, cv2.COLOR_BGR2RGB))\n\nresult_pil.save(\"clean.jpg\", quality=92, optimize=True)\n\n```\n\n### Step 3: Verify the result\n\n```python\n\n# Save a crop of the same region after removal to confirm it's clean\n\nverify = result_pil.crop((0, 0, min(w, 1000), min(h, 500)))\n\nverify.save(\"debug_watermark_removed.jpg\")\n\n# View this image before proceeding to resize\u002Fcrop\n\n```\n\n#### Tips (2)\n\n- For watermarks on uniform backgrounds (studio portraits, product photos), `INPAINT_TELEA`with`inpaintRadius=5-10` works well.\n- For watermarks over textured areas (landscapes, fabric), use `INPAINT_NS` with a larger radius (10-15).\n\n- If the watermark is semi-transparent, color-based masking (Option A) is more precise than a rectangular region mask.\n- Always verify at full resolution — artifacts invisible in thumbnails may be obvious when zoomed in.\n\n## Text & Watermark (Adding)\n\n```python\n\nfrom PIL import Image, ImageDraw, ImageFont\n\ndraw = ImageDraw.Draw(img)\n\ntry:\n\nfont = ImageFont.truetype(\"DejaVuSans-Bold.ttf\", 48) \\# Linux default\n\nexcept OSError:\n\nfont = ImageFont.load_default() \\# fallback (tiny, ugly)\n\n# --- Text with outline ---\n\ndraw.text((50, 50), \"Caption\", font=font, fill=\"white\",\n\nstroke_width=3, stroke_fill=\"black\")\n\n# --- Centered text ---\n\nbbox = draw.textbbox((0, 0), \"Centered\", font=font)\n\ntw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]\n\ndraw.text(((img.width - tw) \u002F\u002F 2, (img.height - th) \u002F\u002F 2), \"Centered\", font=font, fill=\"white\")\n\n# --- Watermark (semi-transparent PNG overlay) ---\n\nlogo = Image.open(\"logo.png\").convert(\"RGBA\")\n\nlogo.thumbnail((img.width \u002F\u002F 5, img.height \u002F\u002F 5))\n\n# Fade to 40% opacity\n\nalpha = logo.split()[3].point(lambda p: int(p * 0.4))\n\nlogo.putalpha(alpha)\n\npos = (img.width - logo.width - 20, img.height - logo.height - 20)\n\nimg.paste(logo, pos, logo) \\# third arg = alpha mask — REQUIRED for transparency\n\n```\n\n## Save & Optimize\n\n```python\n\n# --- JPEG ---\n\nimg.convert(\"RGB\").save(\"out.jpg\", quality=85, optimize=True, progressive=True)\n\n# convert(\"RGB\") REQUIRED if source has alpha — JPEG can't store transparency\n\n# --- PNG (lossless — quality param does nothing) ---\n\nimg.save(\"out.png\", optimize=True, compress_level=9)\n\n# --- WebP (best web format: ~30% smaller than JPEG at same quality) ---\n\nimg.save(\"out.webp\", quality=85, method=6) \\# method 0-6, 6=slowest\u002Fbest compression\n\n# --- AVIF (smallest files, Pillow 11+, slower encode) ---\n\nimg.save(\"out.avif\", quality=75) \\# 75 ≈ JPEG 85 visually, ~50% smaller\n\n# --- Strip all metadata (privacy) ---\n\nclean = Image.new(img.mode, img.size)\n\nclean.putdata(list(img.getdata()))\n\nclean.save(\"stripped.jpg\", quality=85)\n\n```\n\n**Quality guide:** JPEG\u002FWebP 85 = sweet spot. 90+ = diminishing returns. \u003C70 = visible artifacts. Never re-save JPEGs repeatedly — each save degrades (generation loss).\n\n## Batch Processing\n\n```python\n\nfrom pathlib import Path\n\nfrom PIL import Image, ImageOps\n\nout = Path(\"optimized\"); out.mkdir(exist_ok=True)\n\nfor p in Path(\"photos\").glob(\"*.[jJ][pP]*[gG]\"): \\# matches jpg, jpeg, JPG, JPEG\n\nimg = ImageOps.exif_transpose(Image.open(p))\n\nimg.thumbnail((1920, 1920), Image.Resampling.LANCZOS)\n\nimg.convert(\"RGB\").save(out \u002F f\"{p.stem}.webp\", quality=85, method=6)\n\n```\n\n## sharp (Node.js — use for high throughput)\n\n```javascript\n\nconst sharp = require('sharp');\n\n\u002F\u002F Resize + convert + optimize, streaming (flat memory)\n\nawait sharp('in.jpg')\n\n.rotate() \u002F\u002F auto-rotate from EXIF (like exif_transpose)\n\n.resize(1080, 1080, { fit: 'cover', position: 'center' }) \u002F\u002F = ImageOps.fit\n\n.webp({ quality: 85 })\n\n.toFile('out.webp');\n\n\u002F\u002F fit options: 'cover' (crop), 'contain' (letterbox), 'inside' (shrink to fit), 'fill' (stretch)\n\n\u002F\u002F Composite watermark\n\nawait sharp('photo.jpg')\n\n.composite([{ input: 'logo.png', gravity: 'southeast' }])\n\n.toFile('watermarked.jpg');\n\n```\n\nsharp strips all metadata by default. Use `.withMetadata()` to preserve EXIF\u002FICC.\n\n## OpenCV (when Pillow isn't enough)\n\n```python\n\nimport cv2\n\nimg = cv2.imread(\"in.jpg\") \\# BGR order, not RGB!\n\ngray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n\n# Face detection\n\ncascade = cv2.CascadeClassifier(cv2.data.haarcascades + \"haarcascade_frontalface_default.xml\")\n\nfaces = cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5)\n\nfor (x, y, w, h) in faces:\n\ncv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)\n\ncv2.imwrite(\"out.jpg\", img)\n\n# Pillow \u003C-> OpenCV\n\nimport numpy as np\n\ncv_img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)\n\npil_img = Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB))\n\n```\n\n## Bulk Pixel Manipulation — Use numpy, Not getpixel\u002Fputpixel\n\n**Never loop over pixels with `getpixel()`\u002F`putpixel()`** for large regions — it is extremely slow (minutes for a full image). Convert to a numpy array, operate on the array, then convert back.\n\n```python\n\nimport numpy as np\n\nfrom PIL import Image\n\nimg = Image.open(\"photo.jpg\").convert(\"RGB\")\n\narr = np.array(img) \\# shape: (height, width, 3), dtype: uint8\n\n# Example: replace a region with sampled background color\n\nbg_color = arr[50, -100, :] \\# sample one pixel from the right side\n\narr[0:300, 0:800, :] = bg_color \\# fill the region instantly\n\n# Example: blend two regions with a gradient mask\n\nalpha = np.linspace(1, 0, 100).reshape(1, 100, 1) \\# horizontal fade over 100px\n\nregion = arr[0:300, 700:800, :]\n\nbg_strip = np.full_like(region, bg_color)\n\narr[0:300, 700:800, :] = (region * (1 - alpha) + bg_strip * alpha).astype(np.uint8)\n\nresult = Image.fromarray(arr)\n\n```\n\n**Speed comparison:** `putpixel` on a 700x250 region = ~175,000 calls = 30+ seconds. numpy array slice = instant.\n\n## Platform Dimensions\n\n| Platform | Size | Ratio |\n\n|---|---|---|\n\n| Instagram post | 1080x1080 | 1:1 |\n\n| Instagram story \u002F TikTok | 1080x1920 | 9:16 |\n\n| LinkedIn profile photo | 400x400 | 1:1 |\n\n| LinkedIn banner | 1584x396 | 4:1 |\n\n| LinkedIn post | 1200x627 | 1.91:1 |\n\n| Twitter\u002FX profile | 400x400 | 1:1 |\n\n| Twitter\u002FX post | 1200x675 | 16:9 |\n\n| Facebook profile | 320x320 | 1:1 |\n\n| Facebook cover | 851x315 | 2.7:1 |\n\n| YouTube thumbnail | 1280x720 | 16:9 |\n\n| WhatsApp profile | 500x500 | 1:1 |\n\n| Open Graph (link preview) | 1200x630 | 1.91:1 |\n\n## Debug Workflow\n\nWhen edits don't look right, follow this process instead of re-running the whole pipeline blindly:\n\n1. **Save a debug crop of the target area** before and after processing. View both to confirm what changed.\n2. **Work at full resolution first.** Watermarks and artifacts that look small in a thumbnail can be large at native resolution. Always inspect at the original size before resizing.\n\n3. **Save intermediate results.** After each major processing step (watermark removal, crop, resize), save a checkpoint image so you can identify which step introduced a problem.\n4. **Spot-check specific pixels** to verify processing took effect:\n\n```python\n\n# Quick pixel check after watermark removal\n\nfor (x, y) in [(60, 50), (200, 130), (400, 100)]:\n\nprint(f\"({x},{y}): {img.getpixel((x, y))}\")\n\n# If these still show watermark colors (e.g. bright blue), removal failed\n\n```\n\n## Gotchas\n\n- **`img.crop()`box is`(left, top, right, bottom)`** — absolute coords, NOT`(x, y, width, height)`\n- **`thumbnail()`mutates in place and returns`None`** — don't do`img = img.thumbnail(...)`\n\n- **Paste with transparency** needs the image as the third (mask) arg: `bg.paste(fg, pos, fg)`\n- **Palette mode (\"P\")** breaks many filters — `img.convert(\"RGB\")` first\n\n- **Fonts:** `ImageFont.truetype`needs a real font file. Linux:`\u002Fusr\u002Fshare\u002Ffonts\u002Ftruetype\u002Fdejavu\u002F`. Ship a`.ttf` with your code for portability.\n- **OpenCV needs numpy** — always `pip install opencv-python numpy` together\n\n- **OpenCV uses BGR, Pillow uses RGB** — convert when switching between them or colors will be wrong\n","# Photo Editor\n\n## OpenClawd Operator Adaptation\n\nRun this skill as part of the OpenClawd operator deck. Preserve the skill-specific workflow below, but frame outputs for Solana-native agents when relevant: prefer OpenClawd language, note whether the work can support autonomous agent operations, and keep financial, legal, tax, hiring, medical, or other regulated outputs informational unless the skill already requires a stricter disclaimer. Use the Llobster Legend persona only as light operator framing; do not let branding override accuracy, safety, or the user's stated domain.\n\n\nResize, crop, filter, and optimize images. Pillow for Python, sharp for Node. Clarify intent before starting.\n\n## Clarify Intent First\n\nWhen a user asks to \"edit a photo\" or \"change an image,\" the request could mean two very different things. **Ask before proceeding** if it's ambiguous:\n\n1. **Edit the existing image** — crop, resize, recolor, adjust brightness\u002Fcontrast, add text, remove background, apply filters, watermark, etc. → Use the tools below (Pillow, sharp, OpenCV).\n2. **Generate a new AI image** — create something from scratch or heavily reimagine the photo (e.g., \"make this photo look like a painting,\" \"put me on a beach,\" \"create a logo from this concept\"). → Use image generation tools instead, not this skill.\n\n### When to ask\n\n- \"Can you fix this photo?\" → Probably editing. Ask what specifically needs fixing.\n- \"Make this look better\" → Ambiguous. Ask: \"Do you want me to adjust the existing photo (brightness, contrast, cropping, etc.) or generate a new version with AI?\"\n\n- \"Change the background\" → Could be either. Ask: \"Should I remove the current background (I can make it transparent or a solid color), or do you want an AI-generated scene behind you?\"\n- \"Make a profile picture from this\" → Likely crop\u002Fresize, but could mean AI enhancement. Clarify.\n\n#### Don't ask when it's obvious\n\n- \"Crop this to 1080x1080\" → Just crop it.\n- \"Make this a PNG\" → Just convert it.\n\n- \"Remove the background\" → Use rembg.\n- \"Generate a photo of a sunset\" → No existing photo to edit — use image generation.\n\n## Tool Selection\n\n| Tool | Use when | Install |\n\n|---|---|---|\n\n| **Pillow** | Default: resize, crop, filters, text, format conversion | `pip install Pillow` |\n\n| **OpenCV** | Computer vision: face detection, perspective transform, inpainting, contours | `pip install opencv-python numpy` |\n\n| **sharp** (Node) | High-volume pipelines — 4-5x faster than Pillow (libvips-backed) | `npm install sharp` |\n\n| **rembg** | AI background removal | `pip install rembg` |\n\n| **ImageMagick** | CLI batch ops, 200+ formats | `apt install imagemagick` |\n\n## Open — ALWAYS Fix Orientation First\n\n```python\n\nfrom PIL import Image, ImageOps\n\nimg = Image.open(\"photo.jpg\")\n\nimg = ImageOps.exif_transpose(img) \\# CRITICAL: applies EXIF rotation, then strips tag\n\n# Without this, phone photos appear sideways after processing\n\n```\n\n## Resize & Crop\n\n```python\n\nfrom PIL import Image, ImageOps\n\n# --- Fit inside box, keep aspect ratio (shrink only) ---\n\nimg.thumbnail((1080, 1080), Image.Resampling.LANCZOS) \\# modifies in place\n\n# --- Exact size, keep aspect, center-crop overflow (best for thumbnails) ---\n\nthumb = ImageOps.fit(img, (300, 300), Image.Resampling.LANCZOS, centering=(0.5, 0.5))\n\n# --- Exact size, keep aspect, pad with color (letterbox) ---\n\npadded = ImageOps.pad(img, (1920, 1080), color=(0, 0, 0))\n\n# --- Exact size, ignore aspect (will distort) ---\n\nstretched = img.resize((800, 600), Image.Resampling.LANCZOS)\n\n# --- Scale by factor ---\n\nhalf = img.resize((img.width \u002F\u002F 2, img.height \u002F\u002F 2), Image.Resampling.LANCZOS)\n\n# --- Manual crop (left, upper, right, lower) — NOT (x, y, w, h) ---\n\ncropped = img.crop((100, 50, 900, 650))\n\n```\n\n**Resampling filters:** `LANCZOS`for photo downscale (best quality),`BICUBIC`for upscale,`NEAREST` for pixel art\u002Ficons (no smoothing).\n\n## Face-Aware Cropping\n\nFor portraits and headshots, detect the face first and crop around it instead of guessing coordinates. This produces much better results for profile pictures.\n\n```python\n\nimport cv2\n\nimport numpy as np\n\nfrom PIL import Image, ImageOps\n\nimg = Image.open(\"portrait.jpg\")\n\nimg = ImageOps.exif_transpose(img)\n\ncv_img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)\n\ngray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)\n\ncascade = cv2.CascadeClassifier(cv2.data.haarcascades + \"haarcascade_frontalface_default.xml\")\n\nfaces = cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(100, 100))\n\nif len(faces) > 0:\n\n# Use the largest detected face\n\nfx, fy, fw, fh = max(faces, key=lambda f: f[2] * f[3])\n\nface_cx = fx + fw \u002F\u002F 2\n\nface_cy = fy + fh \u002F\u002F 2\n\n# Square crop centered on face with padding (3x face height for head+shoulders)\n\ncrop_size = min(img.width, img.height, fh * 3)\n\nleft = max(0, face_cx - crop_size \u002F\u002F 2)\n\ntop = max(0, face_cy - int(crop_size * 0.35)) \\# face in upper third\n\nright = left + crop_size\n\nbottom = top + crop_size\n\n# Clamp to image bounds\n\nif right > img.width:\n\nleft -= (right - img.width)\n\nright = img.width\n\nif bottom > img.height:\n\ntop -= (bottom - img.height)\n\nbottom = img.height\n\nleft = max(0, left)\n\ntop = max(0, top)\n\ncropped = img.crop((left, top, right, bottom))\n\nprofile = cropped.resize((800, 800), Image.Resampling.LANCZOS)\n\nprofile.save(\"profile_800x800.jpg\", quality=92, optimize=True)\n\nelse:\n\n# Fallback: center crop\n\nprofile = ImageOps.fit(img, (800, 800), Image.Resampling.LANCZOS, centering=(0.5, 0.4))\n\nprofile.save(\"profile_800x800.jpg\", quality=92, optimize=True)\n\n```\n\n### Tips\n\n- `centering=(0.5, 0.4)` in the fallback biases the crop slightly toward the top — better for portraits than dead center.\n- For group photos with multiple faces, you may want to fit all detected faces in the crop instead of picking the largest.\n\n## Color & Exposure\n\n```python\n\nfrom PIL import ImageEnhance, ImageOps\n\n# --- Enhancers: 1.0 = unchanged, \u003C1 less, >1 more ---\n\nimg = ImageEnhance.Brightness(img).enhance(1.15)\n\nimg = ImageEnhance.Contrast(img).enhance(1.2)\n\nimg = ImageEnhance.Color(img).enhance(1.1) \\# saturation\n\nimg = ImageEnhance.Sharpness(img).enhance(1.5)\n\n# --- Quick ops ---\n\ngray = ImageOps.grayscale(img)\n\ninverted = ImageOps.invert(img.convert(\"RGB\"))\n\nauto = ImageOps.autocontrast(img, cutoff=1) \\# stretch histogram, clip 1% extremes\n\nequalized = ImageOps.equalize(img) \\# flatten histogram\n\n```\n\n## Filters\n\n```python\n\nfrom PIL import ImageFilter\n\nimg.filter(ImageFilter.GaussianBlur(radius=5))\n\nimg.filter(ImageFilter.UnsharpMask(radius=2, percent=150, threshold=3)) \\# better than SHARPEN\n\nimg.filter(ImageFilter.BoxBlur(10))\n\nimg.filter(ImageFilter.FIND_EDGES)\n\nimg.filter(ImageFilter.MedianFilter(size=3)) \\# denoise, removes salt-and-pepper\n\n```\n\n## Watermark \u002F Logo Removal\n\nUse OpenCV's `cv2.inpaint()`— it fills a masked region by sampling surrounding pixels, producing seamless results. **Do not use pixel-by-pixel`getpixel`\u002F`putpixel` loops** — they are slow and produce visible artifacts.\n\n### Step 1: Find the watermark boundaries\n\nAlways inspect the image at full resolution first. Watermarks are often much larger than they appear in thumbnails. Save a crop of the watermark region to verify coordinates before attempting removal.\n\n```python\n\nimport cv2\n\nimport numpy as np\n\nfrom PIL import Image, ImageOps\n\nimg = Image.open(\"photo.jpg\")\n\nimg = ImageOps.exif_transpose(img)\n\nw, h = img.size\n\n# Save a debug crop of the suspected watermark area to verify its extent\n\ndebug = img.crop((0, 0, min(w, 1000), min(h, 500)))\n\ndebug.save(\"debug_watermark_area.jpg\")\n\n# IMPORTANT: View this debug image to confirm where the watermark actually is\n\n# before proceeding. Guessing coordinates wastes iterations.\n\n```\n\n### Step 2: Create a mask and inpaint\n\n```python\n\ncv_img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)\n\n# --- Option A: Color-based mask (best for colored logos on neutral backgrounds) ---\n\nhsv = cv2.cvtColor(cv_img, cv2.COLOR_BGR2HSV)\n\n# Example: detect blue watermark pixels (adjust ranges for your watermark color)\n\nlower_blue = np.array([90, 40, 40])\n\nupper_blue = np.array([130, 255, 255])\n\nmask = cv2.inRange(hsv, lower_blue, upper_blue)\n\n# Also catch dark text pixels in the watermark region\n\nroi_gray = cv2.cvtColor(cv_img[:wm_h, :wm_w], cv2.COLOR_BGR2GRAY)\n\n_, dark_mask = cv2.threshold(roi_gray, 80, 255, cv2.THRESH_BINARY_INV)\n\n# Combine: place dark_mask into the full-size mask\n\nmask[:wm_h, :wm_w] = cv2.bitwise_or(mask[:wm_h, :wm_w], dark_mask)\n\n# --- Option B: Region-based mask (when you know the bounding box) ---\n\n# Simpler but removes everything in the box, not just the watermark pixels\n\nmask = np.zeros(cv_img.shape[:2], dtype=np.uint8)\n\nmask[0:wm_h, 0:wm_w] = 255 \\# fill the entire watermark region\n\n# Dilate the mask slightly to catch anti-aliased edges\n\nkernel = np.ones((5, 5), np.uint8)\n\nmask = cv2.dilate(mask, kernel, iterations=2)\n\n# Inpaint — fills masked area using surrounding pixel data\n\nresult = cv2.inpaint(cv_img, mask, inpaintRadius=7, flags=cv2.INPAINT_TELEA)\n\n# INPAINT_TELEA: fast marching method (best for most cases)\n\n# INPAINT_NS: Navier-Stokes (better for large regions, slower)\n\nresult_pil = Image.fromarray(cv2.cvtColor(result, cv2.COLOR_BGR2RGB))\n\nresult_pil.save(\"clean.jpg\", quality=92, optimize=True)\n\n```\n\n### Step 3: Verify the result\n\n```python\n\n# Save a crop of the same region after removal to confirm it's clean\n\nverify = result_pil.crop((0, 0, min(w, 1000), min(h, 500)))\n\nverify.save(\"debug_watermark_removed.jpg\")\n\n# View this image before proceeding to resize\u002Fcrop\n\n```\n\n#### Tips (2)\n\n- For watermarks on uniform backgrounds (studio portraits, product photos), `INPAINT_TELEA`with`inpaintRadius=5-10` works well.\n- For watermarks over textured areas (landscapes, fabric), use `INPAINT_NS` with a larger radius (10-15).\n\n- If the watermark is semi-transparent, color-based masking (Option A) is more precise than a rectangular region mask.\n- Always verify at full resolution — artifacts invisible in thumbnails may be obvious when zoomed in.\n\n## Text & Watermark (Adding)\n\n```python\n\nfrom PIL import Image, ImageDraw, ImageFont\n\ndraw = ImageDraw.Draw(img)\n\ntry:\n\nfont = ImageFont.truetype(\"DejaVuSans-Bold.ttf\", 48) \\# Linux default\n\nexcept OSError:\n\nfont = ImageFont.load_default() \\# fallback (tiny, ugly)\n\n# --- Text with outline ---\n\ndraw.text((50, 50), \"Caption\", font=font, fill=\"white\",\n\nstroke_width=3, stroke_fill=\"black\")\n\n# --- Centered text ---\n\nbbox = draw.textbbox((0, 0), \"Centered\", font=font)\n\ntw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]\n\ndraw.text(((img.width - tw) \u002F\u002F 2, (img.height - th) \u002F\u002F 2), \"Centered\", font=font, fill=\"white\")\n\n# --- Watermark (semi-transparent PNG overlay) ---\n\nlogo = Image.open(\"logo.png\").convert(\"RGBA\")\n\nlogo.thumbnail((img.width \u002F\u002F 5, img.height \u002F\u002F 5))\n\n# Fade to 40% opacity\n\nalpha = logo.split()[3].point(lambda p: int(p * 0.4))\n\nlogo.putalpha(alpha)\n\npos = (img.width - logo.width - 20, img.height - logo.height - 20)\n\nimg.paste(logo, pos, logo) \\# third arg = alpha mask — REQUIRED for transparency\n\n```\n\n## Save & Optimize\n\n```python\n\n# --- JPEG ---\n\nimg.convert(\"RGB\").save(\"out.jpg\", quality=85, optimize=True, progressive=True)\n\n# convert(\"RGB\") REQUIRED if source has alpha — JPEG can't store transparency\n\n# --- PNG (lossless — quality param does nothing) ---\n\nimg.save(\"out.png\", optimize=True, compress_level=9)\n\n# --- WebP (best web format: ~30% smaller than JPEG at same quality) ---\n\nimg.save(\"out.webp\", quality=85, method=6) \\# method 0-6, 6=slowest\u002Fbest compression\n\n# --- AVIF (smallest files, Pillow 11+, slower encode) ---\n\nimg.save(\"out.avif\", quality=75) \\# 75 ≈ JPEG 85 visually, ~50% smaller\n\n# --- Strip all metadata (privacy) ---\n\nclean = Image.new(img.mode, img.size)\n\nclean.putdata(list(img.getdata()))\n\nclean.save(\"stripped.jpg\", quality=85)\n\n```\n\n**Quality guide:** JPEG\u002FWebP 85 = sweet spot. 90+ = diminishing returns. \u003C70 = visible artifacts. Never re-save JPEGs repeatedly — each save degrades (generation loss).\n\n## Batch Processing\n\n```python\n\nfrom pathlib import Path\n\nfrom PIL import Image, ImageOps\n\nout = Path(\"optimized\"); out.mkdir(exist_ok=True)\n\nfor p in Path(\"photos\").glob(\"*.[jJ][pP]*[gG]\"): \\# matches jpg, jpeg, JPG, JPEG\n\nimg = ImageOps.exif_transpose(Image.open(p))\n\nimg.thumbnail((1920, 1920), Image.Resampling.LANCZOS)\n\nimg.convert(\"RGB\").save(out \u002F f\"{p.stem}.webp\", quality=85, method=6)\n\n```\n\n## sharp (Node.js — use for high throughput)\n\n```javascript\n\nconst sharp = require('sharp');\n\n\u002F\u002F Resize + convert + optimize, streaming (flat memory)\n\nawait sharp('in.jpg')\n\n.rotate() \u002F\u002F auto-rotate from EXIF (like exif_transpose)\n\n.resize(1080, 1080, { fit: 'cover', position: 'center' }) \u002F\u002F = ImageOps.fit\n\n.webp({ quality: 85 })\n\n.toFile('out.webp');\n\n\u002F\u002F fit options: 'cover' (crop), 'contain' (letterbox), 'inside' (shrink to fit), 'fill' (stretch)\n\n\u002F\u002F Composite watermark\n\nawait sharp('photo.jpg')\n\n.composite([{ input: 'logo.png', gravity: 'southeast' }])\n\n.toFile('watermarked.jpg');\n\n```\n\nsharp strips all metadata by default. Use `.withMetadata()` to preserve EXIF\u002FICC.\n\n## OpenCV (when Pillow isn't enough)\n\n```python\n\nimport cv2\n\nimg = cv2.imread(\"in.jpg\") \\# BGR order, not RGB!\n\ngray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n\n# Face detection\n\ncascade = cv2.CascadeClassifier(cv2.data.haarcascades + \"haarcascade_frontalface_default.xml\")\n\nfaces = cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5)\n\nfor (x, y, w, h) in faces:\n\ncv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)\n\ncv2.imwrite(\"out.jpg\", img)\n\n# Pillow \u003C-> OpenCV\n\nimport numpy as np\n\ncv_img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)\n\npil_img = Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB))\n\n```\n\n## Bulk Pixel Manipulation — Use numpy, Not getpixel\u002Fputpixel\n\n**Never loop over pixels with `getpixel()`\u002F`putpixel()`** for large regions — it is extremely slow (minutes for a full image). Convert to a numpy array, operate on the array, then convert back.\n\n```python\n\nimport numpy as np\n\nfrom PIL import Image\n\nimg = Image.open(\"photo.jpg\").convert(\"RGB\")\n\narr = np.array(img) \\# shape: (height, width, 3), dtype: uint8\n\n# Example: replace a region with sampled background color\n\nbg_color = arr[50, -100, :] \\# sample one pixel from the right side\n\narr[0:300, 0:800, :] = bg_color \\# fill the region instantly\n\n# Example: blend two regions with a gradient mask\n\nalpha = np.linspace(1, 0, 100).reshape(1, 100, 1) \\# horizontal fade over 100px\n\nregion = arr[0:300, 700:800, :]\n\nbg_strip = np.full_like(region, bg_color)\n\narr[0:300, 700:800, :] = (region * (1 - alpha) + bg_strip * alpha).astype(np.uint8)\n\nresult = Image.fromarray(arr)\n\n```\n\n**Speed comparison:** `putpixel` on a 700x250 region = ~175,000 calls = 30+ seconds. numpy array slice = instant.\n\n## Platform Dimensions\n\n| Platform | Size | Ratio |\n\n|---|---|---|\n\n| Instagram post | 1080x1080 | 1:1 |\n\n| Instagram story \u002F TikTok | 1080x1920 | 9:16 |\n\n| LinkedIn profile photo | 400x400 | 1:1 |\n\n| LinkedIn banner | 1584x396 | 4:1 |\n\n| LinkedIn post | 1200x627 | 1.91:1 |\n\n| Twitter\u002FX profile | 400x400 | 1:1 |\n\n| Twitter\u002FX post | 1200x675 | 16:9 |\n\n| Facebook profile | 320x320 | 1:1 |\n\n| Facebook cover | 851x315 | 2.7:1 |\n\n| YouTube thumbnail | 1280x720 | 16:9 |\n\n| WhatsApp profile | 500x500 | 1:1 |\n\n| Open Graph (link preview) | 1200x630 | 1.91:1 |\n\n## Debug Workflow\n\nWhen edits don't look right, follow this process instead of re-running the whole pipeline blindly:\n\n1. **Save a debug crop of the target area** before and after processing. View both to confirm what changed.\n2. **Work at full resolution first.** Watermarks and artifacts that look small in a thumbnail can be large at native resolution. Always inspect at the original size before resizing.\n\n3. **Save intermediate results.** After each major processing step (watermark removal, crop, resize), save a checkpoint image so you can identify which step introduced a problem.\n4. **Spot-check specific pixels** to verify processing took effect:\n\n```python\n\n# Quick pixel check after watermark removal\n\nfor (x, y) in [(60, 50), (200, 130), (400, 100)]:\n\nprint(f\"({x},{y}): {img.getpixel((x, y))}\")\n\n# If these still show watermark colors (e.g. bright blue), removal failed\n\n```\n\n## Gotchas\n\n- **`img.crop()`box is`(left, top, right, bottom)`** — absolute coords, NOT`(x, y, width, height)`\n- **`thumbnail()`mutates in place and returns`None`** — don't do`img = img.thumbnail(...)`\n\n- **Paste with transparency** needs the image as the third (mask) arg: `bg.paste(fg, pos, fg)`\n- **Palette mode (\"P\")** breaks many filters — `img.convert(\"RGB\")` first\n\n- **Fonts:** `ImageFont.truetype`needs a real font file. Linux:`\u002Fusr\u002Fshare\u002Ffonts\u002Ftruetype\u002Fdejavu\u002F`. Ship a`.ttf` with your code for portability.\n- **OpenCV needs numpy** — always `pip install opencv-python numpy` together\n\n- **OpenCV uses BGR, Pillow uses RGB** — convert when switching between them or colors will be wrong\n",{"name":5,"description":7},"main","6b4a291df75c639681e93b4b5b812126443fd6ed","secondary_skills\u002Fphoto-editor",3,"https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd","https:\u002F\u002Fsolanaclawd.com\u002Fopenclawd","https:\u002F\u002Fgithub.com\u002Fclawdsolana","https:\u002F\u002Fmusebook.trade","Welcome to Open Clawd. The future of Ai on and off chain. Bringing webMCP to web 3 using X402, A2A, MPP and so much more!","2026-08-01 09:30:51",{"source":8,"skillName":5,"commands":44,"prompt":54,"zipAvailable":55},[45,48,51],{"tool":46,"command":47},"npx","npx skills add clawdsolana\u002FOpenClawd --skill photo-editor",{"tool":49,"command":50},"bunx","bunx skills add clawdsolana\u002FOpenClawd --skill photo-editor",{"tool":52,"command":53},"pnpm","pnpm dlx skills add clawdsolana\u002FOpenClawd --skill photo-editor","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fsecondary_skills\u002Fphoto-editor\u002FSKILL.md",true,[57],{"path":58,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":61,"sourceHtmlUrl":62},".fingerprint","OTHER","其他",32,"https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fsecondary_skills\u002Fphoto-editor\u002F.fingerprint",[64,97,125,151,161,185],{"skillNo":65,"name":66,"nameZh":6,"description":67,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":68,"categories":69,"keywords":75,"publishedAt":96},"01M71KRBD0W01EAXJ8YM4M1SJ2","solana-dev","End-to-end Solana development playbook (Jan 2026). Prefer Solana Foundation framework-kit (@solana\u002Fclient + @solana\u002Freact-hooks) for React\u002FNext.js UI. Prefer @solana\u002Fkit for all new client\u002FRPC\u002Ftransaction code. When legacy dependencies require web3.js, isolate it behind @solana\u002Fweb3-compat (or @solana\u002Fweb3.js as a true legacy fallback). Covers wallet-standard-first connection (incl. ConnectorKit), Anchor\u002FPinocchio programs, Codama-based client generation, LiteSVM\u002FMollusk\u002FSurfpool testing, and security checklists.","https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fsrc\u002Fskills\u002Fsolana-dev\u002FSKILL.md",[70],{"categoryNo":71,"categoryName":72,"taxonomyType":73,"taxonomyTypeDesc":74},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95],"solana dev","dev","solana","Wallet","end","testing","hooks","kit","new","when","web3","code","react","next","2026","playbook","security","ui","fallback","framework","2026-11-10 10:56:23",{"skillNo":98,"name":99,"nameZh":6,"description":100,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":101,"categories":102,"keywords":106,"publishedAt":96},"01M71KR8QPESSY61SVE8ZTFNVT","xurl","A CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.","https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fskills\u002Fxurl\u002FSKILL.md",[103],{"categoryNo":104,"categoryName":105,"taxonomyType":73,"taxonomyTypeDesc":74},"01JBSEEDCAT000000000000023","Skill与智能体开发",[99,107,108,109,110,111,112,85,113,114,115,116,117,118,119,120,121,122,123,124],"posts","Media","x api","search","send","tap","you","api","cli","read","install","cli tool","endpoint","brew","for","manage","tool","twitter",{"skillNo":126,"name":127,"nameZh":6,"description":128,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":129,"categories":130,"keywords":132,"publishedAt":96},"01M71KR6FSXV88YDKWP7NBY3SH","wurk-integration","WURK API integration for monetizing agents with x402 payments on Solana\u002FBase - create social jobs, custom challenges, and agent help tasks","https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fskills\u002Fwurk-integration\u002FSKILL.md",[131],{"categoryNo":104,"categoryName":105,"taxonomyType":73,"taxonomyTypeDesc":74},[133,134,135,136,137,138,139,140,114,141,142,143,144,145,146,147,121,148,149,150],"wurk","wurk integration","integration","x402","social","payments","tasks","team","create","social-jobs","command","agent-help","custom-jobs","clawd","safe","api integration","custom","true",{"skillNo":152,"name":153,"nameZh":6,"description":154,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":155,"categories":156,"keywords":157,"publishedAt":96},"01M71KR4HCX41BPAK9GP7RPRRP","workflows","Manage application workflows including configuration, restart, and removal.","https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fskills\u002Fworkflows\u002FSKILL.md",[],[153,122,158,159,160],"configuration","application","restart",{"skillNo":162,"name":163,"nameZh":6,"description":164,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":165,"categories":166,"keywords":167,"publishedAt":96},"01M71KR1VM0F5GPEQDNG5J7A0H","web-search","Search the web, fetch content, extract branding profiles, and capture screenshots from URLs. Use for real-time information, API documentation, current events, design matching, and visual reference.","https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fskills\u002Fweb-search\u002FSKILL.md",[],[168,110,169,170,171,121,172,173,174,175,176,177,178,114,179,180,181,182,183,184],"web search","web","fetch","api documentation","real-time","time","profiles","design","use","information","visual","documentation","branding","matching","reference","capture","content",{"skillNo":186,"name":187,"nameZh":6,"description":188,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":189,"categories":190,"keywords":191,"publishedAt":96},"01M71KQZN5MR1JFQHG5GWXYAHS","weather","Get current weather and forecasts via wttr.in or Open-Meteo. Use when: user asks about weather, temperature, or forecasts for any location. NOT for: historical weather data, severe weather alerts, or detailed meteorological analysis. No API key needed.","https:\u002F\u002Fgithub.com\u002Fclawdsolana\u002FOpenClawd\u002Fblob\u002Fmain\u002Fskills\u002Fweather\u002FSKILL.md",[],[187,192,121,193,176,85,194,114,195,196,197,198,199,200,201,202,203,204],"get","open","analysis","alerts","temperature","curl","via","help","about","https","location","data","user"]