
Gpc Metadata Sync
- 27 installs
- 1 repo stars
- Updated August 1, 2026
- yasserstudio/gpc-skills
Helps with ai & agent building tasks.
About
gpc-metadata-sync is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- gpc-metadata-sync
- AI & Agent Building
- AI-coding skill
Gpc Metadata Sync by the numbers
- 27 all-time installs (skills.sh)
- +1 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #9,601 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/yasserstudio/gpc-skills --skill gpc-metadata-syncAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 27 |
|---|---|
| repo stars | ★ 1 |
| Last updated | August 1, 2026 |
| Repository | yasserstudio/gpc-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
GPC Metadata Sync
When to use
Use this skill when the task involves:
- Viewing or updating store listings (title, description, short description)
- Syncing metadata between local files and Google Play Console
- Managing screenshots and images (upload, delete, list)
- Working with Fastlane-compatible metadata directory structure
- Multi-language listing management
- Pulling listings to local for version control
- Pushing local listings to Play Console
Inputs required
- Package name (or configured default via
GPC_APP) - Language code(s) (e.g.,
en-US,ja-JP) - For push/pull: local directory path for metadata files
- For images: image type and file paths
Procedure
0) View current listings
# Default language listing
gpc listings get
# Specific language
gpc listings get --lang ja-JP
# All languages at once
gpc listings get --all-languages
# JSON output for scripting
gpc listings get --output json1) Update listings inline
gpc listings update --lang en-US \
--title "My App" \
--short-desc "A great app for great things" \
--full-desc "Full description here..."Or from a metadata directory:
gpc listings update --lang en-US --file metadata/en-US/1a) Validate listings locally (lint)
Local validation with no API call — checks character limits before pushing:
gpc listings lint
# Point at a specific metadata directory
gpc listings lint --dir metadata/
gpc listings lint --dir fastlane/metadata/android/Output table:
Field Chars Limit % Status
────────────────────────────────────────────
title 28 30 93% ✓
shortDescription 76 80 95% ✓
fullDescription 3820 4000 96% ✓
video URL 0 256 0% ✓Character limits enforced: title = 30, shortDescription = 80, fullDescription = 4000, video URL = 256.
Returns exit code 1 if any field exceeds its limit. Use as a pre-commit or pre-push gate.
1b) Validate live listings (analyze)
Fetches the live listings from Play Store and runs the same character-limit check:
gpc listings analyze
# Compare against an expected set of locales
gpc listings analyze --expected en-US,ja-JP,de-DE
# JSON output
gpc listings analyze --jsonSame output table as lint but reflects what is currently live on the Play Store. The --expected flag will flag any locales present in your list but missing from Play Console (or vice versa).
2) Pull/Push workflow (bidirectional sync)
This is the recommended workflow for version-controlling your listings.
Pull (download from Play Console to local):
gpc listings pull --dir metadata/Creates a directory structure:
metadata/
├── en-US/
│ ├── title.txt
│ ├── short_description.txt
│ ├── full_description.txt
│ └── changelogs/
│ └── 142.txt
├── ja-JP/
│ ├── title.txt
│ ├── short_description.txt
│ └── full_description.txt
└── ...Push (upload local files to Play Console):
gpc listings push --dir metadata/
# Prevent changes from being auto-submitted for review
gpc listings push --dir metadata/ --changes-not-sent-for-review
# Fail if the app is currently in review or rejected
gpc listings push --dir metadata/ --error-if-in-review
# Preview changes without applying
gpc listings push --dir metadata/ --dry-run
# Bypass the preflight lint gate (not recommended)
gpc listings push --dir metadata/ --forcePreflight lint gate: gpc listings push automatically runs gpc listings lint before uploading. If any field exceeds its character limit the push is aborted with exit code 1. Pass --force to skip the gate and push anyway.
Read:
references/directory-structure.md
3) Fastlane compatibility
GPC reads and writes the Fastlane metadata directory format. If you're migrating from Fastlane:
# Your existing Fastlane metadata/ directory works as-is
gpc listings push --dir fastlane/metadata/android/
# Pull into Fastlane-compatible structure
gpc listings pull --dir fastlane/metadata/android/Read:
references/fastlane-migration.md
4) Image management
List existing images:
gpc listings images list --lang en-US --type phoneScreenshotsImage types: phoneScreenshots, sevenInchScreenshots, tenInchScreenshots, tvScreenshots, wearScreenshots, icon, featureGraphic, tvBanner, promoGraphic.
Upload images:
# Single image
gpc listings images upload --lang en-US --type phoneScreenshots screenshot.png
# Multiple images (glob)
gpc listings images upload --lang en-US --type phoneScreenshots ./screens/*.png
# Preview what would be uploaded without making API calls (v0.9.74+)
gpc listings images upload --lang en-US --type phoneScreenshots ./screens/*.png --dry-runDelete images:
gpc listings images delete --lang en-US --type phoneScreenshots --id <image-id>
# Preview what would be deleted without making API calls (v0.9.74+)
gpc listings images delete --lang en-US --type phoneScreenshots --dry-runNew in v0.9.74: Bothgpc listings images uploadandgpc listings images deleterespect--dry-run. In dry-run mode, the commands print what would happen (files that would be uploaded, image IDs that would be deleted) without calling the Play API.
Sync images from a local directory (v0.9.69+)
gpc listings images sync uses SHA-256 content hashing to deduplicate images — it only uploads files that are not already on Play Store, and optionally deletes remote images that have no local counterpart.
# Sync all phone screenshots for en-US from a local directory
gpc listings images sync --lang en-US --type phoneScreenshots --dir ./screens/
# Sync all image types for a language
gpc listings images sync --lang en-US --dir ./screenshots/en-US/
# Delete remote images that no longer exist locally
gpc listings images sync --lang en-US --type phoneScreenshots --dir ./screens/ --delete
# Preview what would change without touching the API
gpc listings images sync --lang en-US --type phoneScreenshots --dir ./screens/ --dry-runKey behaviors:
- SHA-256 hash of each local file is compared against the remote image hash. Already-matching images are skipped (no re-upload).
--deleteremoves remote images that have no matching local file. Without this flag, extra remote images are left in place.--dry-runprints a diff table (add / skip / delete) without making any API calls.- Supports the same image types as
gpc listings images upload:phoneScreenshots,sevenInchScreenshots,tenInchScreenshots,tvScreenshots,wearScreenshots,icon,featureGraphic,tvBanner,promoGraphic.
| Flag | Description |
|---|---|
--dir | Local directory containing image files to sync |
--lang | Language code (e.g., en-US, ja-JP) |
--type | Image type filter (omit to sync all types in the directory) |
--delete | Delete remote images with no local counterpart |
--dry-run | Preview changes without uploading or deleting |
5) Multi-language workflow
For apps with many languages:
# Pull all languages
gpc listings pull --dir metadata/
# Edit locally (use your preferred text editor or translation tools)
# Push all languages back
gpc listings push --dir metadata/ --dry-run # Preview first
gpc listings push --dir metadata/ --changes-not-sent-for-review # Apply without auto-submitting for reviewTip: For generating per-locale "What's new" release notes text (not the full listing fields), see gpc changelog generate --target play-store (v0.9.62+) — it takes your git log and emits per-locale output with the 500-char Play Store budget enforced. Add --ai (v0.9.63+) to translate non-source locales via your own LLM key (auto-detects AI_GATEWAY_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_GENERATIVE_AI_API_KEY).
6) Preview with dry-run
All write operations support --dry-run:
gpc listings update --lang en-US --title "New Title" --dry-run
gpc listings push --dir metadata/ --dry-run
gpc listings images upload --lang en-US --type phoneScreenshots ./screens/*.png --dry-run
gpc listings images delete --lang en-US --type phoneScreenshots --dry-run
gpc listings images sync --lang en-US --type phoneScreenshots --dir ./screens/ --dry-runIn dry-run mode no API calls are made. The output describes what would change: files that would be uploaded, image IDs that would be deleted, and fields that would be updated.
Verification
gpc listings get --lang <lang>shows updated contentgpc listings get --all-languagesconfirms all languages are correctgpc listings images list --lang <lang> --type <type>shows uploaded images- Play Console UI reflects the changes (may take a few minutes)
Security notes (v0.9.74+)
Symlink traversal protection in --notes-dir
When --notes-dir is used to load release notes (e.g. gpc releases upload --notes-dir changelogs/), GPC now calls lstat() on every entry and rejects symbolic links before reading file contents. This prevents directory traversal attacks where a crafted symlink inside the notes directory points to sensitive files outside the tree (e.g. /etc/passwd, SSH keys).
If a symlink is encountered, the command exits with a descriptive error identifying the offending path. Use real files in your notes directories.
CSV formula injection prevention in review exports
gpc reviews export and other commands that produce CSV output now prefix cells that begin with =, +, -, @, tab (\t), or carriage return (\r) with a single quote ('). This prevents spreadsheet applications (Excel, Google Sheets) from interpreting user-supplied content (review text, app titles) as formulas when the CSV is opened.
If you process the CSV programmatically, strip the leading ' from string values where needed.
Failure modes / debugging
| Symptom | Likely Cause | Fix |
|---|---|---|
LISTING_NOT_FOUND | Language not set up in Play Console | Add the language in Console first, then push |
| Image upload fails | Wrong format or size | Check Google's image requirements (PNG/JPEG, size limits per type) |
| Truncated description | Exceeds character limit | Title: 30 chars, short desc: 80 chars, full desc: 4000 chars |
| Push shows no changes | Local files match remote | Confirm edits are saved in the correct file paths |
| Encoding issues | Non-UTF-8 file encoding | Ensure all text files are UTF-8 encoded |
Related skills
- gpc-setup: Authentication and configuration
- gpc-release-flow: Upload and release management
- gpc-ci-integration: Automated metadata sync in CI/CD
{
"skill_name": "gpc-metadata-sync",
"evals": [
{
"id": 1,
"prompt": "We currently use Fastlane supply for our store listings and the metadata is at fastlane/metadata/android/ with en-US, ja-JP, and fr-FR translations. We want to switch to gpc for everything. Can we keep using the same directory structure or do we need to convert something?",
"expected_output": "Confirms Fastlane metadata format is directly compatible, shows how to push existing metadata with GPC",
"files": [],
"expectations": [
"Confirms GPC reads Fastlane metadata directory format natively",
"Shows gpc listings push --dir fastlane/metadata/android/",
"Suggests --dry-run first to preview changes",
"Mentions gpc listings pull for downloading current listings",
"Notes that the same directory structure works for both tools"
]
},
{
"id": 2,
"prompt": "I need to update our app screenshots for the phone category in en-US. I have 5 new PNGs in ~/screenshots/ and I want to replace all the existing screenshots. What's the process?",
"expected_output": "Shows how to list existing screenshots, delete old ones, and upload new ones",
"files": [],
"expectations": [
"Shows gpc listings images list to see existing images",
"Shows gpc listings images delete to remove old screenshots",
"Shows gpc listings images upload with the correct --type phoneScreenshots",
"Mentions the --lang en-US flag",
"Notes image format requirements (PNG/JPEG, size limits)"
]
},
{
"id": 3,
"prompt": "Our app supports 12 languages and the product team wants to pull all current listings into git so translators can work on them in PRs. Then we need a way to push changes back after review. How should we set this up?",
"expected_output": "Describes a pull → edit → PR → push workflow using GPC with version-controlled metadata",
"files": [],
"expectations": [
"Shows gpc listings pull --dir metadata/ to download all languages",
"Explains the directory structure that gets created (lang/title.txt, etc.)",
"Suggests committing the pulled metadata to git",
"Shows gpc listings push --dir metadata/ --dry-run for previewing",
"Recommends using --dry-run in CI to validate before pushing"
]
}
]
}
Metadata Directory Structure
Standard Layout
When you run gpc listings pull --dir metadata/, GPC creates this structure:
metadata/
├── en-US/
│ ├── title.txt # App title (max 50 chars)
│ ├── short_description.txt # Short description (max 80 chars)
│ ├── full_description.txt # Full description (max 4000 chars)
│ └── changelogs/
│ ├── 142.txt # Release notes for version code 142
│ └── default.txt # Default release notes
├── ja-JP/
│ ├── title.txt
│ ├── short_description.txt
│ └── full_description.txt
├── fr-FR/
│ ├── title.txt
│ ├── short_description.txt
│ └── full_description.txt
└── ...File Encoding
- All text files must be UTF-8 encoded
- No BOM (Byte Order Mark)
- Newlines: LF preferred, CRLF accepted
Character Limits
| Field | Max Length |
|---|---|
| Title | 50 characters |
| Short description | 80 characters |
| Full description | 4,000 characters |
| Release notes (per language) | 500 characters |
Language Codes
Use BCP 47 language tags as directory names. Common codes:
| Code | Language |
|---|---|
en-US | English (US) |
en-GB | English (UK) |
ja-JP | Japanese |
ko-KR | Korean |
zh-CN | Chinese (Simplified) |
zh-TW | Chinese (Traditional) |
fr-FR | French |
de-DE | German |
es-ES | Spanish |
pt-BR | Portuguese (Brazil) |
it-IT | Italian |
ru-RU | Russian |
ar | Arabic |
hi-IN | Hindi |
Push Behavior
When running gpc listings push --dir metadata/:
1. GPC reads all language directories 2. For each language, reads title, short_description, full_description 3. Compares with current Play Console content 4. Only updates languages/fields that have changed 5. Uses a single edit (atomic commit)
Use --dry-run to preview changes:
gpc listings push --dir metadata/ --dry-runUse --changes-not-sent-for-review to prevent the edit from being auto-submitted for review:
gpc listings push --dir metadata/ --changes-not-sent-for-reviewUse --error-if-in-review to abort if the app is currently in review or rejected:
gpc listings push --dir metadata/ --error-if-in-reviewMigrating from Fastlane Supply
Compatibility
GPC reads and writes the same metadata directory structure that Fastlane supply uses. Migration is straightforward.
Fastlane Directory Layout
Fastlane stores Android metadata at:
fastlane/metadata/android/
├── en-US/
│ ├── title.txt
│ ├── short_description.txt
│ ├── full_description.txt
│ ├── changelogs/
│ │ └── 142.txt
│ └── images/
│ ├── phoneScreenshots/
│ │ ├── 1.png
│ │ └── 2.png
│ ├── icon.png
│ └── featureGraphic.png
├── ja-JP/
│ └── ...
└── ...Using GPC with Existing Fastlane Metadata
Point GPC directly at your Fastlane metadata directory:
# Push existing Fastlane metadata to Play Console
gpc listings push --dir fastlane/metadata/android/
# Pull latest from Play Console into Fastlane directory
gpc listings pull --dir fastlane/metadata/android/No conversion needed — the format is identical.
Command Mapping: Fastlane → GPC
| Fastlane Command | GPC Equivalent |
|---|---|
fastlane supply --track beta --aab app.aab | gpc releases upload app.aab --track beta |
fastlane supply --track production --rollout 0.1 | gpc releases upload app.aab --track production --rollout 10 |
fastlane supply --skip_upload_aab --metadata_path metadata/ | gpc listings push --dir metadata/ --changes-not-sent-for-review |
fastlane supply --skip_upload_aab --download_metadata | gpc listings pull --dir metadata/ |
fastlane supply --track production --version_code 42 | gpc releases promote --from beta --to production |
Key Differences
| Aspect | Fastlane supply | GPC |
|---|---|---|
| Runtime | Ruby + Bundler | Node.js (or standalone binary) |
| Startup | 2-3 seconds | <500ms |
| API coverage | ~20 endpoints | 162 endpoints |
| Config | Fastfile + Appfile | .gpcrc.json + env vars |
| Output | Ruby logs | JSON/table/yaml/markdown |
| Auth | JSON key file only | Service account + OAuth + ADC |
| Rollout syntax | --rollout 0.1 (decimal) | --rollout 10 (percentage) |
Migration Checklist
1. Install GPC: npm install -g @gpc-cli/cli 2. Set up auth: gpc auth login --service-account path/to/key.json (same key file works) 3. Configure app: gpc config set app com.example.app 4. Test with existing metadata: gpc listings push --dir fastlane/metadata/android/ --dry-run 5. Update CI: Replace fastlane supply with gpc commands 6. Remove Fastlane (optional): Delete Gemfile, Fastfile, Appfile if no longer needed
Keeping Both During Migration
You can run both tools during transition. They share:
- Service account key files (same JSON format)
- Metadata directory structure (compatible)
They don't conflict because each creates its own API edit.
Google Play Image Requirements
Image specifications for each type supported by gpc listings images upload.
Image types
| Type | Flag value | Min size | Max size | Max count | Format |
|---|---|---|---|---|---|
| Phone screenshots | phoneScreenshots | 320×320 | 3840×3840 | 8 | PNG, JPEG |
| 7" tablet screenshots | sevenInchScreenshots | 320×320 | 3840×3840 | 8 | PNG, JPEG |
| 10" tablet screenshots | tenInchScreenshots | 320×320 | 3840×3840 | 8 | PNG, JPEG |
| TV screenshots | tvScreenshots | 1280×720 | 3840×3840 | 8 | PNG, JPEG |
| Wear screenshots | wearScreenshots | 320×320 | 3840×3840 | 8 | PNG, JPEG |
| App icon | icon | 512×512 | 512×512 | 1 | PNG (32-bit, alpha) |
| Feature graphic | featureGraphic | 1024×500 | 1024×500 | 1 | PNG, JPEG |
| TV banner | tvBanner | 1280×720 | 1280×720 | 1 | PNG, JPEG |
Screenshot aspect ratio
- Minimum: 1:2 (portrait) or 2:1 (landscape)
- Maximum: 2:1 (portrait) or 1:2 (landscape)
- Screenshots must not have alpha/transparency
File size limits
- Maximum file size per image: 15 MB
- Recommended: optimize PNGs under 5 MB for faster uploads
Commands
# List existing images
gpc listings images list --lang en-US --type phoneScreenshots
# Upload a single image
gpc listings images upload --lang en-US --type phoneScreenshots screenshot1.png
# Upload multiple images
gpc listings images upload --lang en-US --type phoneScreenshots \
screenshot1.png screenshot2.png screenshot3.png
# Preview upload without making API calls (v0.9.74+)
gpc listings images upload --lang en-US --type phoneScreenshots screenshot1.png --dry-run
# Delete all images of a type
gpc listings images delete --lang en-US --type phoneScreenshots
# Delete a specific image by ID
gpc listings images delete --lang en-US --type phoneScreenshots --id <image-id>
# Preview deletion without making API calls (v0.9.74+)
gpc listings images delete --lang en-US --type phoneScreenshots --dry-runBest practices
- Upload phone screenshots first — they're the most visible in the Play Store
- Use consistent dimensions across all screenshots in a set
- Feature graphic is required for apps featured by Google
- TV banner is required for Android TV apps
- Optimize file sizes — large files slow down CI uploads
#!/usr/bin/env node
/**
* Detection script for GPC CLI.
* Returns JSON with installation status, version, auth state, and config.
* Used by Claude Code skill system for deterministic environment detection.
*
* Exit codes:
* 0 — GPC detected (may or may not be authenticated)
* 1 — GPC not found
*/
import { execSync } from "node:child_process";
import { existsSync } from "node:fs";
import { join } from "node:path";
function run(cmd) {
try {
return execSync(cmd, { encoding: "utf-8", timeout: 10000 }).trim();
} catch {
return null;
}
}
const result = {
installed: false,
version: null,
installMethod: null,
authStatus: null,
authMethod: null,
profile: null,
envAuth: false,
defaultApp: null,
configFile: null,
nodeVersion: process.version,
};
// Check if gpc is installed globally
const versionOutput = run("gpc --version");
if (!versionOutput) {
// Try npx
const npxVersion = run("npx gpc --version 2>/dev/null");
if (!npxVersion) {
console.log(JSON.stringify(result, null, 2));
process.exit(1);
}
result.version = npxVersion;
result.installed = true;
result.installMethod = "npx";
} else {
result.version = versionOutput;
result.installed = true;
result.installMethod = "global";
}
// Check auth status
const authOutput = run("gpc auth status --json 2>/dev/null");
if (authOutput) {
try {
const auth = JSON.parse(authOutput);
result.authStatus = auth.status || "unknown";
result.authMethod = auth.method || null;
result.profile = auth.profile || null;
} catch {
result.authStatus = "parse_error";
}
}
// Check for env-based auth
if (process.env.GPC_SERVICE_ACCOUNT) {
result.envAuth = true;
}
// Check default app
const configOutput = run("gpc config get app --json 2>/dev/null");
if (configOutput) {
try {
const config = JSON.parse(configOutput);
result.defaultApp = config.value || config.app || null;
} catch {
result.defaultApp = configOutput || null;
}
}
// Check for .gpcrc.json in current directory
const rcPath = join(process.cwd(), ".gpcrc.json");
if (existsSync(rcPath)) {
result.configFile = rcPath;
}
console.log(JSON.stringify(result, null, 2));
process.exit(0);