Agent skill

Photo Pipeline

by OpenLitterMap in OpenLitterMap/openlittermap-web

Photo upload, tagging, verification status, summary generation, XP calculation, AddTagsToPhotoAction, UploadPhotoController, and the VerificationStatus enum.

GPL-3.0Auto-check passed

Install Photo Pipeline

skills CLI
$ npx skills add OpenLitterMap/openlittermap-web --skill photo-pipeline -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install OpenLitterMap/openlittermap-web photo-pipeline --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/OpenLitterMap/openlittermap-web.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.ai/skills/photo-pipeline .claude/skills/photo-pipeline && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
photo-pipeline
GitHub stars
134
Token cost
~5.3k tokens
SKILL.md length
1,968 words
Files
1
Skills in repo
16
Repo updated
First seen
Licence
GPL-3.0

At a glance

Photo upload, tagging, verification status, summary generation, XP calculation, AddTagsToPhotoAction, UploadPhotoController, and the VerificationStatus enum.

  • Works in 2 steps: Upload → Tagging
  • SKILL.md covers Key Files, Invariants, VerificationStatus Enum and Patterns, plus 1 more section
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Photo Pipeline is an agent skill from OpenLitterMap/openlittermap-web. Photo upload, tagging, verification status, summary generation, XP calculation, AddTagsToPhotoAction, UploadPhotoController, and the VerificationStatus enum.

Its SKILL.md is about 5.3k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It works with PHP. The repository describes itself as: https://opengeospatialdata.springeropen.com/articles/10.1186/s40965-018-0050-y. The licence is GPL-3.0.

Example prompts

  • “/photo-pipeline”

Workflow steps

2 steps, taken from the step headings in SKILL.md.

  1. Upload
  2. Tagging

What it can do on your machine

Read from SKILL.md and the folder at commit ac688aa. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are php and json).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Photo Pipeline loads about 5.3k tokens when it runs. Until then it costs about 43 tokens; SKILL.md has 1,968 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~43
When it runs · the whole SKILL.md, loaded when a task matches
~5.3k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from OpenLitterMap/openlittermap-web at commit ac688aa, republished under its GPL-3.0 licence (© OpenLitterMap). 1,968 words, ~5,301 tokens.

Download SKILL.mdSave it as .claude/skills/photo-pipeline/SKILL.md (or your agent's skills folder).
name
photo-pipeline
description
Photo upload, tagging, verification status, summary generation, XP calculation, AddTagsToPhotoAction, UploadPhotoController, and the VerificationStatus enum.

Photo Pipeline

Photos flow through three phases: Upload (observation only) -> Tag (summary + XP) -> Verify (metrics). Each phase is independent and idempotent.

Key Files

  • app/Http/Controllers/Uploads/UploadPhotoController.php — Web upload entry point
  • app/Http/Requests/UploadPhotoRequest.php — Web upload validation (EXIF datetime, GPS). Dedup is NOT here — it's an idempotent lookup in the controller. HEIC files skip the image/dimensions rules AND the raw exif_read_data() GPS/datetime checks (both detected via MakeImageAction::isHeic()) — PHP can't read HEIC EXIF pre-conversion, so that validation is deferred to the controller, which reads the converted JPEG's EXIF.
  • app/Http/Controllers/API/Tags/PhotoTagsController.php — V5 tagging endpoint (POST /api/v3/tags add, PUT /api/v3/tags replace)
  • app/Actions/Tags/AddTagsToPhotoAction.php — Core tagging logic (v5)
  • app/Actions/Photos/MakeImageAction.php — Image processing + EXIF extraction. Converts HEIC→JPEG by shelling out to heif-convert (from libheif; NOT ImageMagick — IM's HEIC delegate is unreliable across variants). The binary path comes from config('services.heif_convert.path') (env HEIF_CONVERT_PATH, default heif-convert resolved via PATH). Set an absolute path when the web server's PATH excludes it (e.g. local Valet php-fpm under launchd lacks /opt/homebrew/bin).
  • app/Actions/Photos/UploadPhotoAction.php — S3 storage (requires non-null Carbon datetime)
  • app/Services/Tags/GeneratePhotoSummaryService.php — Builds summary JSON + calculates XP
  • app/Services/Tags/XpCalculator.php — XP scoring rules
  • app/Enums/VerificationStatus.php — Photo verification state machine
  • app/Enums/XpScore.php — XP values per tag type
  • app/Http/Requests/Api/PhotoTagsRequest.php — V5 tag request validation (POST — blocks already-verified photos)
  • app/Http/Requests/Api/ReplacePhotoTagsRequest.php — V5 replace tag request validation (PUT — ownership only, no verification gate)
  • app/Http/Controllers/API/GetUntaggedUploadController.php — Mobile untagged photos (supports ?platform=web|mobile filter)
  • app/Observers/PhotoObserver.php — Sets is_public = false for school team photos
  • app/Helpers/helpers.php — getDateTimeForPhoto(), getCoordinatesFromPhoto(), dmsToDec()
  • tests/Feature/UploadValidationTest.php — 11 tests (EXIF datetime, GPS DMS conversion, edge cases)
  • tests/Feature/Tags/ReplacePhotoTagsTest.php — 5 tests (replace tags, ownership, auth, extra tags cleanup)

Invariants

  1. Upload creates observation only. No tags, no XP, no summary, no metrics. Just the photo record with location FKs.
  2. EXIF datetime is required for web uploads. UploadPhotoRequest rejects images without EXIF datetime. Controller has ?? Carbon::now() safety fallback. UploadPhotoAction::run() type-hints Carbon $datetime — null will crash. Mobile uploads send explicit lat, lon, date — EXIF validation is skipped.
  3. GPS DMS conversion guards against division by zero. dmsToDec() validates all 6 denominator values before dividing. Returns null on malformed data.
  4. (0,0) coordinates rejected for explicit mode. Mobile uploads with lat=0, lon=0 get 422 (Null Island guard). Web uploads accept 0,0 from EXIF.
  5. Summary generation is unconditional. GeneratePhotoSummaryService::run() MUST run regardless of trust level. School photos need a summary at tag time so it exists when the teacher approves later. Gating summary behind a trust check causes null summary at approval = zero metrics.
  6. XP calculation is unconditional. Runs for all users, before verification.
  7. TagsVerifiedByAdmin fires for ALL non-school users. This ensures all users get immediate leaderboard credit. Trusted users also get ADMIN_APPROVED (visible on map). Non-trusted users stay at verified=0 (not on map). School students' photos stop at VERIFIED(1) and wait for teacher approval — event does NOT fire for them.
  8. VerificationStatus is an enum cast. $photo->verified returns the enum, not an int. Use ->value for >=/< comparisons, === for equality checks. Never compare enum to raw int.
  9. remaining is deprecated and NO LONGER read for picked-up. DB column is photos.remaining (tinyint(1) NOT NULL DEFAULT 1). The Photo::getPickedUpAttribute() accessor (appended via $appends) now derives picked-up from the first tag — data_get($this->summary, 'tags.0.picked_up') — returning ?bool (null for untagged), NOT !$this->remaining. So $photo->picked_up reflects per-tag edits everywhere (map popups, profile, uploads, team views). Requires summary to be loaded (all readers select it). photos.remaining is now only written at upload (a user default) and hidden from API serialization ($hidden = ['geom', 'remaining']) — it is no longer returned in any response. Clients use picked_up. Per-tag photo_tags.picked_up is the source of truth (nullable true/false/null). users.picked_up defaults to true for new users; column stays nullable tri-state.
  10. Loose PhotoTags (nullable CLO). photo_tags.category_litter_object_id, category_id, and litter_object_id are now NULLABLE. Extra-tag-only tags (brands, materials, custom tags) can exist as standalone PhotoTags without a litter object. AddTagsToPhotoAction::createExtraTagOnly() creates these with null CLO fields. GeneratePhotoSummaryService only counts objects when objectId > 0 (variable renamed $totalLitter → $totalObjects). XpCalculator only awards object XP when object_id > 0.
  11. Replace tags accepts empty tags array. PUT /api/v3/tags with tags: [] clears all tags on a photo (resets summary, XP, verified). ReplacePhotoTagsRequest validates tags as present|array (not required|array|min:1).
  12. Photo visibility is user-controlled (except school teams). Non-school users can set is_public = false per photo or globally via users.public_photos default. Private-by-choice photos still receive immediate upload XP — the metrics gate in recordUploadMetrics() uses a school team check ($photo->team_id && $team->isSchool()), NOT an is_public check. Never change the gate to $photo->is_public === false — that would incorrectly defer metrics for private-by-choice photos.

VerificationStatus Enum

php
enum VerificationStatus: int
{
    case UNVERIFIED = 0;     // Uploaded, no tags
    case VERIFIED = 1;       // Tagged (school students land here, awaiting teacher)
    case ADMIN_APPROVED = 2; // Verified by admin/trusted user OR teacher-approved
    case BBOX_APPLIED = 3;   // Bounding boxes drawn
    case BBOX_VERIFIED = 4;  // Bounding boxes verified
    case AI_READY = 5;       // Ready for OpenLitterAI training

    public function isPublicReady(): bool  // >= ADMIN_APPROVED
    public function isVerified(): bool     // >= VERIFIED
}

Patterns

Phase 1: Upload

UploadPhotoRequest::after() validates before controller runs:

  1. EXIF must exist and be non-empty
  2. DateTime must exist (DateTimeOriginal → DateTime → FileDateTime fallback)
  3. GPS fields must exist and dmsToDec() must succeed (guards zero denominators)

Duplicate handling is NOT validation. Dedup (user_id + datetime) lives in UploadPhotoController::__invoke(), before any S3 write / Photo::create / XP. A duplicate is idempotent success, not a 422: it returns { success: true, photo_id: <existing>, already_uploaded: true, tagged: <bool>, xp_awarded: 0 } (pure lookup, zero side effects). Skipped for participant uploads (students share the facilitator's user_id and may share a datetime). tagged = existing photo has a non-null summary.

Error contract on UploadPhotoRequest: failedValidation() returns a structured response:

json
{ "success": false, "error": "<code>", "message": "<human string>", "errors": {} }

resolveErrorCode() maps failures to typed string codes:

  • no_exif — image has no readable EXIF data
  • no_gps — image has no GPS coordinates
  • no_datetime — image has no datetime in EXIF
  • invalid_coordinates — GPS coordinates failed parsing (zero denominators, etc.)
  • validation_error — generic Laravel validation failure (wrong file type, size, etc.)

Mobile clients should read the error field for programmatic handling. Note: Handler::unauthenticated() returns { message: "Unauthenticated." } without an error code field (inconsistency — not yet fixed).

UploadPhotoController::__invoke() flow:

  1. MakeImageAction::run($file) — extract EXIF
  2. getDateTimeForPhoto($exif) ?? Carbon::now() — EXIF datetime with safety fallback
  3. UploadPhotoAction::run() x2 — S3 full image + bbox thumbnail
  4. getCoordinatesFromPhoto($exif) → ResolveLocationAction::run($lat, $lon) — Country/State/City FKs
  5. Photo::create() — observation record with FKs only. For participant uploads: team_id from participant's team, participant_id from participant slot
  6. event(new ImageUploaded(...)) — real-time broadcast
Phase 2: Tagging

POST is append-only + has an idempotent guard. store() first checks the target photo's summary: if non-null (already tagged), it returns an idempotent no-op { success: true, already_tagged: true, photoTags: [...] } WITHOUT re-adding — because POST appends, a retried POST would otherwise double-tag/double-count (the verified >= 1 authorize gate does NOT catch ordinary users, who stay verified = 0). To re-tag/edit an already-tagged photo, use PUT /api/v3/tags (replace).

PhotoTagsController::store() -> AddTagsToPhotoAction::run():

php
// AddTagsToPhotoAction::run() is wrapped in DB::transaction() — all tag creation,
// summary generation, and verification update are atomic.
public function run(int $userId, int $photoId, array $tags): array
{
    $photoTags = $this->addTagsToPhoto($userId, $photoId, $tags);
    // Creates PhotoTag + PhotoTagExtraTags (materials, brands, custom)
    // Handles 4 tag types: object, custom-only, brand-only, material-only

    $photo->generateSummary();
    // ALWAYS — generates summary JSON from PhotoTag records

    $photo->xp = $this->calculateXp($photoTags);
    // ALWAYS — uses XpScore enum multipliers (Upload=5, Object=1, Brand=3, Material=2, Custom=1)

    $this->updateVerification($userId, $photo);
    // Routes to trusted path or school-pending path
}
Frontend tag types handled by AddTagsToPhotoAction

The web frontend sends 4 distinct tag types. resolveTag() handles each:

  1. Object tag — { object: { id, key }, quantity, materials?, brands? }. Category auto-resolved from object->categories()->first().
  2. Custom-only — { custom: true, key: "dirty-bench", quantity }. Uses $tag['key'] (not $tag['custom']).
  3. Brand-only — { brand_only: true, brand: { id, key }, quantity }. PhotoTag with null category/object.
  4. Material-only — { material_only: true, material: { id, key }, quantity }. Same as brand-only pattern.
Verification routing
php
protected function updateVerification(int $userId, Photo $photo): void
{
    $user = User::find($userId);
    $isSchoolStudent = false;

    if ($user->verification_required) {
        $photo->verification = 0.1;
        if ($photo->team_id) {
            $team = Team::find($photo->team_id);
            if ($team && $team->isSchool()) {
                $photo->verified = VerificationStatus::VERIFIED->value;
                $isSchoolStudent = true;
            }
        }
    } else {
        // Trusted user — immediate approval + map visibility
        $photo->verification = 1;
        $photo->verified = VerificationStatus::ADMIN_APPROVED->value;
    }

    $photo->save();

    // ALL users get leaderboard credit immediately (except school students).
    // Non-trusted photos stay at verified=0 (not on map) but metrics are processed.
    if (! $isSchoolStudent) {
        event(new TagsVerifiedByAdmin(...));
    }
}

Key distinction: TagsVerifiedByAdmin fires for ALL non-school users. Trusted users also get verified = ADMIN_APPROVED (photo visible on map). Non-trusted users stay at verified = 0 (photo NOT on map, but user IS on leaderboard).

XP calculation
php
// XpScore enum values:
Upload    => 5   // Base for every photo
Object    => 1   // Per litter item (default)
Material  => 2   // Per material tag
Brand     => 3   // Per brand tag
CustomTag => 1   // Per custom tag
PickedUp  => 5   // Bonus if picked_up = true
Small     => 10  // Special objects: 'dumping_small'
Medium    => 25  // Special objects: 'dumping_medium'
Large     => 50  // Special objects: 'dumping_large'
BagsLitter => 10 // Special objects: 'bags_litter'
Show full SKILL.md (849 more words)Show less
Phase 2b: Replace Tags (edit mode)

PhotoTagsController::update() handles PUT /api/v3/tags for replacing all tags on an already-tagged photo. The entire operation is wrapped in DB::transaction():

  1. Delete all existing PhotoTags + PhotoTagExtraTags
  2. Reset photo: summary=null, xp=0, verified=0
  3. Call AddTagsToPhotoAction::run() — regenerates summary, XP, fires TagsVerifiedByAdmin
  4. MetricsService::processPhoto() detects prior processing (has processed_at), calls doUpdate() which calculates deltas between old processed_tags and new summary, applies adjustments to all metrics
  5. Marks onboarding_completed_at on first tag submission (parity with store()), guarded to non-empty tags. PUT-first-time == POST-first-time: on a never-tagged photo the reset is a no-op and the same AddTagsToPhotoAction::run(..., skipVerification=false) runs → identical verified/XP/metrics for trusted/school/ordinary users. The mobile auto-upload flow tags exclusively via PUT (idempotent).

Frontend edit mode: /tag?photo=<id> loads a specific photo. If it has existing tags, isEditMode=true → uses PUT. If untagged, uses POST. convertExistingTags() transforms API new_tags format back to frontend format (including litter_object_type_id for the type dimension).

Frontend guards: Double-submit prevention via isSubmitting ref. After success, REFRESH_USER() updates the nav XP bar (non-blocking). Stats and photos refresh in parallel via Promise.all().

Security: ReplacePhotoTagsRequest checks $photo->user_id === $this->user()->id. GET_SINGLE_PHOTO calls /api/v3/user/photos which filters by authenticated user.

result_string and total_litter (v4 compatibility — write-only)

GeneratePhotoSummaryService::run() still populates result_string from the summary keys for backward compatibility. Format: category.object qty,category.object qty,... (e.g., smoking.butts 3,food.wrapper 2,). However, no public-facing endpoint reads result_string anymore — all map endpoints (GlobalMapController, DisplayTagsOnMapController, TeamsClusterController, PointsController, FilterPhotosByGeoHashTrait) were updated to select and return summary instead. Both result_string and total_litter columns are now write-only and scheduled for eventual removal.

total_litter → total_tags: All active endpoints now read total_tags instead of total_litter. Fixed: CommunityController, ContributorAggregator, TimeSeriesAggregator, ProfileController (global litter fallback), JoinTeamAction (team pivot). Safe (location-level Redis, not photo column): GlobalStatsController, WorldCupController. Safe (correct fallback): CreateCSVExport. Console commands that read these columns (CompileResultsString, ResetResultString) have been deleted. Dead jobs deleted: Api/AddTags, Photos/AddTagsToPhoto (both wrote total_litter + verification float).

Summary JSON structure
json
{
  "tags": {
    "2": {
      "65": {
        "quantity": 5,
        "materials": {"16": 3, "15": 2},
        "brands": {"12": 3}
      }
    }
  },
  "totals": {
    "total_tags": 15, "total_objects": 5,
    "by_category": {"2": 10},
    "materials": 8, "brands": 3, "custom_tags": 0
  },
  "keys": {
    "categories": {"2": "smoking"},
    "objects": {"65": "wrapper"},
    "materials": {"16": "plastic"},
    "brands": {"12": "marlboro"}
  }
}
Photo model hidden attribute
php
protected $hidden = ['geom'];  // Binary spatial data — breaks JSON serialization

Always ensure geom stays in $hidden. If you need coordinates, use lat/lon columns.

Common Mistakes

  • Gating summary generation behind trust check. Summary MUST be unconditional. This is the #1 cause of broken metrics for school photos.
  • Comparing VerificationStatus enum to int. $photo->verified >= 2 fails. Use $photo->verified->value >= VerificationStatus::ADMIN_APPROVED->value.
  • Dispatching TagsVerifiedByAdmin for school students. School photos must wait for teacher approval. Only trusted users get immediate dispatch.
  • Including geom in API responses. Binary spatial data. Keep it in $hidden.
  • Using $photo->toArray() for queue responses. The Location model's updatedAtDiffForHumans accessor crashes on null updated_at. Build response arrays manually when including country relation. See AdminQueueController for pattern.
  • Passing null datetime to UploadPhotoAction::run(). The method type-hints Carbon $datetime. If EXIF has no datetime, getDateTimeForPhoto() returns null. Validation must reject first; controller has ?? Carbon::now() safety fallback.
  • Not guarding dmsToDec() against zero denominators. EXIF GPS values are "numerator/denominator" format. If denominator is 0 in any of the 6 components (degrees/minutes/seconds for lat and lon), division crashes. The function now returns null instead.
  • Rejecting 0,0 coordinates. Photos at latitude 0, longitude 0 are valid (Gulf of Guinea). Do not reject 0,0 — only reject null.
  • Forgetting city_id in factory. PhotoFactory doesn't include city_id by default. Add 'city_id' => City::factory() when testing location-dependent features.
  • Confusing category_litter_object_id with category_id. Phase 1 adds category_litter_object_id (FK to category_litter_object pivot) and litter_object_type_id (FK to litter_object_types) to photo_tags. Both are nullable in Phase 1. The existing category_id and litter_object_id columns remain and are still the authoritative source until Phase 3.
  • Returning 'tags' instead of 'new_tags' in upload controller. UsersUploadsController must return tags under the key 'new_tags' — the Uploads.vue frontend reads photo.new_tags for tag counts and objects list.
  • Using where('verified', 0) or doesntHave('photoTags') for untagged filter. Use whereNull('summary') — summary is set by GeneratePhotoSummaryService when tags are added, regardless of verification status. After "leaderboard immediate credit," untrusted users' verified stays at 0 after tagging, so where('verified', 0) includes tagged photos.
  • Not including litter_object_type_id in photo response. UsersUploadsController::getNewTags() must include litter_object_type_id so the frontend can preserve the type dimension on edit round-trips.
  • Replace tags without DB::transaction(). If AddTagsToPhotoAction::run() fails after old tags are deleted, the photo loses all tag data. The entire delete-reset-add sequence must be atomic. AddTagsToPhotoAction::run() itself is also wrapped in a transaction — both operations are independently protected.
  • getNewTags() conditionally includes category/object. For extra-tag-only PhotoTags (brand/material/custom-only), category and object fields are null. The serializer only includes them when both category_id and litter_object_id are non-null. Frontend must handle null category/object gracefully.
  • Expecting UploadPhotoRequest errors to match Laravel's default shape. UploadPhotoRequest overrides failedValidation() to return a custom { success, error, message, errors } shape with typed error codes. Do not assert the standard Laravel { errors: { field: [...] } } shape for upload failures.
  • Adding image/dimensions back unconditionally, running raw exif_read_data() on HEIC, or swapping the converter to ImageMagick. Laravel's image rule excludes HEIC and dimensions (getimagesize()) returns false for HEIC — UploadPhotoRequest::rules() drops both for HEIC (detected via MakeImageAction::isHeic()), keeping mimes+max. after() also skips the raw exif_read_data() GPS/datetime checks for HEIC (PHP can't read HEIC EXIF pre-conversion → false no_exif); the controller validates GPS from the converted JPEG instead and returns no_gps if genuinely missing. And MakeImageAction converts via heif-convert (libheif; path from config('services.heif_convert.path')), NOT ImageMagick convert/magick — IM's HEIC delegate is unreliable across variants. Reintroducing any of these breaks HEIC upload (silent client/server rejection, or conversion failure).

© OpenLitterMap, GPL-3.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .ai/skills/photo-pipeline of OpenLitterMap/openlittermap-web.

Open the folder on GitHubat commit ac688aa

Compare with similar skills

Photo Pipeline next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Photo Pipeline compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Photo Pipeline this skillOpenLitterMap/openlittermap-web134—~5.3kAutomated safety check: PassGPL-3.0
Configuring Horizoncoollabsio/coolify63k4 repos~898Automated safety check: PassMIT
Tailwindcss Developmentanonaddy/anonaddy4.9k10 repos~865Automated safety check: PassMIT
Fortify Developmentcoollabsio/coolify63k4 repos~1.9kAutomated safety check: PassMIT
MCP Developmentcoollabsio/coolify63k1 repos~949Automated safety check: PassMIT
WooCommerce Code Reviewwoocommerce/woocommerce11k3 repos~1.1kAutomated safety check: PassCustom licence

Similar skills

  • Configuring Horizon

    coollabsio/coolify

    A skill your agent uses whenever the user mentions Horizon by name in a Laravel context.

    63k GitHub starsUsed in 4 repos~898 tokens
    Backend & APIsAuto-check passed
  • Tailwindcss Development

    anonaddy/anonaddy

    Always invoke when the user's message includes 'tailwind' in any form.

    4.9k GitHub starsUsed in 10 repos~865 tokens
    Frontend & DesignAuto-check passed
  • Fortify Development

    coollabsio/coolify

    ACTIVATE when the user works on authentication in Laravel. An agent skill from coollabsio/coolify.

    63k GitHub starsUsed in 4 repos~1.9k tokens
    Backend & APIsAuto-check passed
  • MCP Development

    coollabsio/coolify

    A skill your agent uses for Laravel MCP development. An agent skill from coollabsio/coolify.

    63k GitHub starsUsed in 1 repo~949 tokens
    Frontend & DesignAuto-check passed
  • WooCommerce Code Review

    woocommerce/woocommerce

    Reviews WooCommerce code changes against the project's standards, flagging backend PHP architecture, naming, documentation, data integrity and testing violations.

    11k GitHub starsUsed in 3 repos~1.1k tokens
    DevelopmentAuto-check passed
  • Sync Translations

    symfony/symfony

    Synchronize translation catalogs across maintained Symfony branches: find messages that newer branches added to the English catalogs but that are still missing from the oldest maintained branch…

    31k GitHub stars~1.9k tokensUpdated today
    Writing & ContentAuto-check passed

More from OpenLitterMap/openlittermap-web

All 16 skills in this repo
  • Tailwindcss Development

    OpenLitterMap/openlittermap-web

    Styles applications using Tailwind CSS v3 utilities. An agent skill from OpenLitterMap/openlittermap-web.

    134 GitHub stars~713 tokensUpdated 26 days ago
    Auto-check passed
  • Olm Architecture

    OpenLitterMap/openlittermap-web

    OpenLitterMap v5 architecture reference. An agent skill from OpenLitterMap/openlittermap-web.

    134 GitHub stars~5.2k tokensUpdated 26 days ago
    Auto-check passed
  • Achievements System

    OpenLitterMap/openlittermap-web

    AchievementEngine, AchievementRepository, milestone checkers, AchievementsSeeder, userachievements pivot, AchievementsController API, and achievement evaluation flow.

    134 GitHub stars~1.4k tokensUpdated 26 days ago
    Auto-check passed
  • Admin System

    OpenLitterMap/openlittermap-web

    AdminController, photo approval, tag editing, deletion, MetricsService integration, admin middleware, verification queue, and admin XP.

    134 GitHub stars~3.9k tokensUpdated 26 days ago
    Auto-check passed
  • API Endpoints

    OpenLitterMap/openlittermap-web

    REST API endpoints, route structure, auth guards, request/response contracts, error patterns, and the full API surface for web SPA and mobile clients.

    134 GitHub stars~4k tokensUpdated 26 days ago
    Auto-check passed
  • Clustering System

    OpenLitterMap/openlittermap-web

    ClusteringService, tile keys, dirty tiles/teams, clustering commands, ClusterController GeoJSON API, PhotoObserver dirty marking, and map cluster rendering.

    134 GitHub stars~2k tokensUpdated 26 days ago
    Auto-check passed

Works with

Questions about Photo Pipeline

What does Photo Pipeline do?

Photo upload, tagging, verification status, summary generation, XP calculation, AddTagsToPhotoAction, UploadPhotoController, and the VerificationStatus enum. Photo Pipeline is an agent skill from OpenLitterMap/openlittermap-web. Photo upload, tagging, verification status, summary generation, XP calculation, AddTagsToPhotoAction, UploadPhotoController, and the VerificationStatus enum.

How do I install Photo Pipeline in Claude Code?

Run `npx skills add OpenLitterMap/openlittermap-web --skill photo-pipeline -a claude-code`. Or copy the skill folder (.ai/skills/photo-pipeline in OpenLitterMap/openlittermap-web) into .claude/skills/photo-pipeline in your project. Claude Code loads it when a task matches its description.

How do I install Photo Pipeline in Codex?

Run `npx skills add OpenLitterMap/openlittermap-web --skill photo-pipeline -a codex`. Or copy the skill folder (.ai/skills/photo-pipeline in OpenLitterMap/openlittermap-web) into .agents/skills/photo-pipeline in your project. Codex loads it when a task matches its description.

Can I use Photo Pipeline in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add OpenLitterMap/openlittermap-web --skill photo-pipeline -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/photo-pipeline, .gemini/skills/photo-pipeline, .github/skills/photo-pipeline and .opencode/skills/photo-pipeline in your project.

What does Photo Pipeline need to run?

SKILL.md names no scripts, command-line tools or credentials: Photo Pipeline is instructions for the agent only.

Does Photo Pipeline access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Photo Pipeline safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Photo Pipeline use?

Photo Pipeline is published under the GPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Photo Pipeline use?

About 5.3k tokens (SKILL.md is roughly 21k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Photo Pipeline?

Skills that share tags, products or a category with Photo Pipeline: Configuring Horizon (coollabsio/coolify, 63k stars), Tailwindcss Development (anonaddy/anonaddy, 4.9k stars), Fortify Development (coollabsio/coolify, 63k stars) and MCP Development (coollabsio/coolify, 63k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Photo Pipeline?

OpenLitterMap (a GitHub organization) maintains it in OpenLitterMap/openlittermap-web, which has 134 GitHub stars. The repository holds 16 skills in this directory. The repository was last updated on September 14, 2026.

Source: OpenLitterMap/openlittermap-web on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.