eliasoulkadi/shokunin/portfolio-auto

Claude Code and agent skill by eliasoulkadi/shokunin

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Auto-sync GitHub repos to portfolio website. Scans GitHub repos, captures screenshots with Playwright, generates project entries, and updates projects-data.js or Supabase DB. Use when user asks to "update portfolio", "sync projects", "add my repos to portfolio", or "refresh portfolio projects". Do NOT use for one-time project additions — batch sync only.

Install

npx vskill@latest install eliasoulkadi/shokunin/portfolio-auto

SKILL.md

Note: last-sync.json state file is auto-created on first successful sync. Ignore "missing file" warnings on first run.

Portfolio Auto-Sync

Automatically sync GitHub repositories to your portfolio website.

Workflow

Step 1: Gather configuration

Ask the user:

  • GitHub username: (default from git config)
  • Portfolio type: static (projects-data.js) or supabase (API)
  • Portfolio directory: Path to portfolio project
  • Filters: Exclude repos (archived, forks, specific names)
Step 2: Fetch repos via GitHub API
GET /users/{username}/repos?per_page=100&sort=updated&direction=desc

Extract: name, description, html_url, homepage, language, topics, updated_at

Filter out:

  • Forks (unless user opts in)
  • Profile repos ({username}/{username})
  • Archived repos
Step 3: Detect changes

Compare against last-sync.json (stored in skill directory).

  • New repos: Not in last sync → full process
  • Updated repos: updated_at changed → re-screenshot
  • Unchanged repos: Skip
Step 4: Capture screenshots

For repos with a homepage or deploy URL:

javascript
const { chromium } = require('playwright');
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.setViewportSize({ width: 1280, height: 800 });
await page.goto(process.env.URL, { waitUntil: 'networkidle', timeout: 15000 });
await page.screenshot({ path: `/tmp/portfolio-${name}.png`, fullPage: false });
await browser.close();

Save to portfolio's screenshot directory.

Step 5: Update portfolio data
Static (projects-data.js)
javascript
{
title: '{repo.name}',
description: 'Auto-generated: {description}',
tech: '{language},{topics}',
github_url: '{html_url}',
live_url: '{homepage || ""}',
featured: false
}
Supabase
javascript
const res = await fetch('https://your-site.com/api/projects', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer {token}' },
body: JSON.stringify(projectData)
});
Step 6: Save sync state
json
{
"lastSync": "2026-05-12T18:30:00Z",
"repos": {
"example-repo": { "updated_at": "2026-04-20T10:00:00Z", "screenshot": true }
}
}
Step 7: Report results
Sync complete:
+ 2 new (Cyberian, Image Enhancer)
+ 1 screenshot captured
+ 1 updated (Portfolio)
- 0 errors

Notes

  • Screenshots require Playwright + valid URL. Skip if no homepage.
  • Never change featured: true on existing projects — only user can promote.
  • Schedule via cron/GitHub Actions for weekly sync.

Error Handling

CauseFix
GitHub API returns 401 UnauthorizedNo token or expired token. Set GITHUB_TOKEN environment variable. Generate a new Personal Access Token at github.com/settings/tokens with public_repo scope.
GitHub API rate limit exceeded (403)Unauthenticated requests are limited to 60/hour. Authenticated: 5000/hour. Add token. If already authenticated, wait for reset (check X-RateLimit-Reset header).
Repository has no homepage field and no deploy URLCannot capture a screenshot. Mark the repo as "no live preview" in the output. Use the GitHub URL as fallback link.
Playwright browser fails to launchChromium not installed. Run npx playwright install chromium. On headless server, add --no-sandbox flag: chromium.launch({ headless: true, args: ['--no-sandbox'] }).
Screenshot URL returns 404 or times out after 15sDeploy URL is stale or site is down. Skip screenshot for this repo. Log the URL and error to a failed-screenshots.json file for manual review.
projects-data.js has custom formatting that auto-generation would breakA regex-based insert could corrupt handwritten code. Read the file first to detect non-standard structure. If detected, append a comment block with the new entries and ask user to merge manually.
Supabase API insert fails with foreign key or schema constraintPortfolio schema might differ from the auto-generated payload shape. Read the Supabase table schema first. Map fields explicitly (not spread operator). Validate with a dry-run POST that returns 200 before committing.
last-sync.json is corrupted or manually editedParse as JSON in a try-catch. On failure, rename to last-sync.json.bak and start fresh sync from scratch. Warn user that incremental detection is lost.

GitHub API Patterns

Repository Discovery
python
import requests, os
headers = {"Authorization": f"token {os.environ['GITHUB_TOKEN']}"}
# Fetch all repos (paginated)
repos = []
page = 1
while True:
r = requests.get(f"https://api.github.com/user/repos?per_page=100&page={page}&sort=updated", headers=headers)
batch = r.json()
if not batch: break
repos.extend(batch)
page += 1
# Filter: only non-fork, non-archived, has description
candidates = [r for r in repos if not r["fork"] and not r["archived"] and r["description"]]
Rate Limiting
  • GitHub API: 5,000 requests/hour (authenticated). Check X-RateLimit-Remaining header.
  • For repos with 500+ repos: use conditional requests (If-None-Match with ETag)
  • Sleep 1s between screenshot captures (avoid GitHub rate limit on assets)
Playwright Screenshot Pattern
python
from playwright.sync_api import sync_playwright
def capture(url, output_path):
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1280, "height": 800})
page.goto(url, timeout=15000, wait_until="networkidle")
page.screenshot(path=output_path, full_page=False)
browser.close()
Error Recovery
  • Timeout on page load -> retry once, skip if still fails (mark in output)
  • Private repo -> skip (cannot screenshot without auth)
  • Page crashes -> capture error state as screenshot
  • DNS failure -> skip with log message
Caching Strategy
python
import hashlib, json, os
cache_file = ".sync-cache.json"
cache = json.load(open(cache_file)) if os.path.exists(cache_file) else {}
for repo in repos:
key = hashlib.md5(f"{repo['full_name']}:{repo['pushed_at']}".encode()).hexdigest()
if cache.get(repo['full_name']) == key:
continue # Skip: no changes since last sync
# ... capture screenshot, update entry ...
cache[repo['full_name']] = key
json.dump(cache, open(cache_file, 'w'))

Checklist

  • [ ] GitHub token has correct scopes (repo, read:user) before fetching repos
  • [ ] Screenshot runs only for repos with valid homepage URLs (HEAD check first)
  • [ ] Playwright browser closed in finally block to prevent orphan processes
  • [ ] Existing portfolio customizations preserved before upserting new entries
  • [ ] Dry-run mode available to preview changes without executing

Sources

  • GitHub REST API v3 documentation (docs.github.com/en/rest/repos/repos) — repository listing, filtering, and pagination
  • Playwright documentation (playwright.dev/docs/api/class-playwright) — headless browser launch, screenshot, and viewport configuration
  • Supabase JavaScript client documentation (supabase.com/docs/reference/javascript) — API insert, upsert, and auth patterns
  • Node.js fs module documentation (nodejs.org/api/fs.html) — file read/write with atomic rename for sync state safety
  • Personal Access Token best practices (docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) — scopes, expiration, and secure storage
  • GitHub Actions cron scheduling (docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#schedule) — weekly sync automation pattern
  • "Automate the Boring Stuff with Python" by Al Sweigart (No Starch Press, 2nd Edition, 2019) — automation reliability patterns applicable to JavaScript automation tasks

Anti-Patterns

PatternProblemFix
Syncing without checking for existing customizationsAuto-generated entries may overwrite handcrafted project descriptions, featured flags, or custom ordering set by the user.Read the target file first. Detect non-standard structure. For featured: true entries, preserve all fields. Only upsert new or changed repos.
Capturing screenshots for every repo indiscriminatelyRepos without live demos waste Playwright launch time (~3s each). Repos with broken URLs produce useless error screenshots.Pre-filter: only screenshot repos where homepage field is non-empty and returns 200 on a HEAD request.
Running sync on a metered or slow connectionCloning or screenshotting dozens of repos can consume significant bandwidth and time.Run in batches of 10. Add a --dry-run flag that lists what would change without executing.
Using the Supabase anon key instead of service_role key for writesAnon key has row-level security restrictions that block upserts.Use service_role key with caution. Store in environment variable, never in code. Add table-level RLS policies for the sync function.
Not cleaning up Playwright browser processes after syncEach chromium.launch() spawns a process. Orphaned processes accumulate memory and can exhaust system resources.Always call browser.close() in a finally block. Track launched browsers in an array and force-close on SIGINT/SIGTERM.
Hardcoding the portfolio data path instead of reading from configPaths change between projects. A hardcoded path breaks on first use on a different machine.Ask for portfolio directory in Step 1. Derive projects-data.js path from that base. Store in sync state for reuse.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
101
Repository forks
12
7-day trend score
23

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 30 Jun 2026, 4 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/eliasoulkadi/shokunin/portfolio-auto/badge)](https://verified-skill.com/skills/eliasoulkadi/shokunin/portfolio-auto)

Something wrong with this skill or its page? Tell us.