AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL unreviewed MIT Self-run

Portfolio Auto

skill-eliasoulkadi-shokunin-portfolio-auto · by EliasOulkadi

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.

No reviews yet
0 installs
33 views
0.0% view→install

Install

$ agentstack add skill-eliasoulkadi-shokunin-portfolio-auto

Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Reads credentials/environment and may exfiltrate them.

What it can access

  • Network access Used
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Reliability & compatibility

Not yet reviewed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Portfolio Auto? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

> 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:

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)
{
  title: '{repo.name}',
  description: 'Auto-generated: {description}',
  tech: '{language},{topics}',
  github_url: '{html_url}',
  live_url: '{homepage || ""}',
  featured: false
}
Supabase
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

{
  "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

| Cause | Fix | |-------|-----| | GitHub API returns 401 Unauthorized | No 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 URL | Cannot capture a screenshot. Mark the repo as "no live preview" in the output. Use the GitHub URL as fallback link. | | Playwright browser fails to launch | Chromium 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 15s | Deploy 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 break | A 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 constraint | Portfolio 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 edited | Parse 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

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

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

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

| Pattern | Problem | Fix | |---------|---------|-----| | Syncing without checking for existing customizations | Auto-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 indiscriminately | Repos 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 connection | Cloning 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 servicerole key for writes | Anon key has row-level security restrictions that block upserts. | Use servicerole 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 sync | Each 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 config | Paths 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. |

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.