A macOS menubar app for browsing and installing Claude, Codex, and agent skills from GitHub. Two independent parts: a Node.js scraper that builds the public skill library, and a Swift menubar app that reads bundled or remotely refreshed library data.
Agents can search the omgskills library through the published read-only MCP server:
npm install -g omgskills-mcpAdd it to your MCP client:
{
"mcpServers": {
"omgskills": {
"command": "omgskills-mcp"
}
}
}The npm package uses hosted library data by default from:
https://omgskills.com/data/manifest.json
No local repo checkout is required for MCP users. Full tool docs live in mcp/README.md.
This repo is public so people can inspect how omgskills works before running it. It is source-available, not open source. You may read and audit the code, but you may not copy, redistribute, or repackage it without permission.
The app:
- reads public skill-library data bundled with the app or served from
omgskills.com - scans local Claude, Codex, and agent skill folders to show installed skills
- installs skills from public GitHub repositories only when you choose to install them
- uses Developer ID signing and Apple notarization for public macOS releases
Local crawl logs, browser traces, .env files, signing certificates, API tokens, and release credentials are intentionally not part of the public repo.
Security reports should follow SECURITY.md.
For any public macOS download, use:
./scripts/release-mac.shDo not use menubar/build.sh for public distribution. It is dev-only and uses ad-hoc signing, which will trigger Gatekeeper warnings.
Required env vars:
DEVELOPER_ID_APPLICATIONASC_PRIVATE_KEY_PATHASC_KEY_IDASC_ISSUER_ID
Template:
cp .env.release.example .env.releaseThe release script:
- builds the app
- Developer ID signs it
- notarizes it
- staples it
- verifies it with
codesign,stapler, andspctl - writes a notarized DMG for public downloads
- writes Sparkle zip artifacts to
site/updatesand regeneratessite/appcast.xml
Optional version override:
./scripts/release-mac.sh 0.0.3omgskills/
├── index/ # Scraper — discovers skills and writes generated JSON
│ ├── skills.json # Generated skill library
│ ├── trending.json # Generated skills.sh trending feed
│ ├── x-trending.json # Generated X/Twitter feed
│ ├── package.json
│ ├── tsconfig.json
│ └── scraper/
│ ├── build.ts # Entry point — orchestrates all sources, merges, enriches
│ ├── build-trending.ts # Builds the skills.sh trending feed
│ ├── client.ts # Hardened Octokit with retry + throttle
│ ├── enrich.ts # Fetches SKILL.md, parses frontmatter, stores lightweight metadata
│ ├── types.ts # Skill interface
│ └── sources/
│ ├── topics.ts # GitHub topic + repo-name searches
│ ├── code.ts # GitHub code search (filename:SKILL.md + fingerprints)
│ ├── aggregators.ts # Parses "awesome-claude" lists for linked repos
│ └── social.ts # Hacker News + Reddit link extraction
├── scripts/
│ ├── publish-data.sh # Publishes hashed skill data into site/data
│ └── release-mac.sh # Signs, notarizes, packages DMG, and updates Sparkle appcast
│
├── mcp/ # Read-only MCP server for agent access to hosted skill data
│ ├── package.json
│ ├── README.md # MCP install, client config, tools, and data-source docs
│ └── src/
│ ├── index.ts # MCP stdio server and tool definitions
│ └── library.ts # Hosted/local JSON loading and search logic
│
├── site/
│ ├── data/ # Hosted skill data manifest + hashed JSON files
│ ├── downloads/ # Public download DMG/zip + checksums
│ └── updates/ # Sparkle update zips and deltas
│
└── menubar/ # macOS app
├── Package.swift
├── build.sh # Compiles release binary, assembles .app bundle
├── Info.plist
└── Sources/omgskills/
├── omgskillsApp.swift # App entry, NSPanel setup, global hotkey
├── ContentView.swift # Full UI — list, detail panel, search, sort
├── Skill.swift # Codable model
├── SkillsStore.swift # Loads library data, scans installed, builds FTS indexes
├── DataRefreshService.swift # Checks hosted data manifest and caches updates
├── SkillSearchIndex.swift # SQLite FTS5 search via GRDB
├── InstalledSkillsScanner.swift # Scans Claude/Codex/Agents skill folders
├── SkillInstaller.swift # Safe GitHub clone + symlink installer
├── LocalSkillCrossInstaller.swift # Installs a local skill onto the other platform
├── LocalDashboardView.swift # Installed tab dashboard
├── GitHubInstallPromptView.swift # Paste-GitHub-URL installer
├── ReadmeLoader.swift # Lazily fetches and caches repo READMEs on detail open
├── ReadmeWebView.swift # WKWebView wrapper for markdown/HTML rendering
├── KeyboardShortcutNames.swift # Global hotkey (Option-S)
└── Resources/
├── skills.json # Bundled fallback skill library
├── trending.json # Bundled fallback trending feed
├── manifest.json # Bundled data manifest, when available
└── marked.min.js # Bundled markdown parser for WebView
cd index
cp .env.example .env # add GITHUB_TOKEN=<fine-grained PAT>
npm install
npm run scrape # writes index/skills.json
npm run scrape:trending # writes index/trending.jsonThe scraper runs all discovery sources in parallel, merges candidates, enriches each one via the GitHub API, and writes skills.json sorted by stars. The trending scraper writes a small trending.json feed from skills.sh IDs and install counts.
Typical run time: 1–3 hours on a cold run (~14,000+ candidates, GitHub rate limits). Subsequent runs are significantly faster — existing skills with unchanged SKILL.md SHAs are returned from cache in milliseconds.
After the scrape finishes, publish data or rebuild the app:
./scripts/publish-data.sh
cd menubar && ./build.sh && open dist/omgskills.appImportant: skills.json must be valid UTF-8 with no lone Unicode surrogates. The scraper sanitizes strings automatically, but if you encounter a Failed to decode JSON error in the app, the file may be corrupt. Run npm run scrape again to regenerate it.
cd index
ENABLE_X_SOCIAL=1 X_AUTH_TOKEN=... X_CT0=... GITHUB_TOKEN=... npx tsx scraper/run-x-enrichment.ts
cd ..
./scripts/publish-data.shThis rebuilds:
index/top-x-skill-tweets.jsonindex/x-trending.json
The app reads X data from x-trending.json, not skills.json.
The app can receive new skill-library data without shipping a new macOS build.
cd index
npm run scrape
npm run scrape:trending
cd ..
./scripts/publish-data.shpublish-data.sh copies the generated JSON into site/data/ using content-hashed filenames and writes site/data/manifest.json.
Operator health page:
https://omgskills.com/health
Backed by:
https://omgskills.com/data/health.json
After the website is deployed, installed apps check:
https://omgskills.com/data/manifest.json
On launch, the app checks the manifest. If the hashes differ, it downloads and validates the new skills.json and trending.json, then caches them under:
~/Library/Application Support/omgskills/
If the app stays open, it also re-checks about once every 24 hours while running. First-run or missing-cache cases bypass the 24-hour throttle so a fresh install hydrates immediately. If nothing changed, the app keeps the current cache and does not reload the UI. Bundled app data remains the fallback when no cached remote data exists.
The main library workflow is:
.github/workflows/scrape.yml
It runs nightly and:
- rebuilds
index/skills.json - rebuilds
index/trending.json - republishes
site/data/manifest.json - verifies the published data
- deploys
site/to Netlify
Required GitHub Actions secrets:
SCRAPER_GITHUB_TOKENNETLIFY_AUTH_TOKEN
The repo includes a dedicated workflow:
.github/workflows/x-refresh.yml
It runs daily and on manual trigger. It:
- collects fresh X posts
- rebuilds
index/x-trending.json - republishes
site/data/manifest.json - verifies the published data
- deploys
site/to Netlify
Required GitHub Actions secrets:
SCRAPER_GITHUB_TOKENENABLE_X_SOCIAL=1X_AUTH_TOKENX_CT0NETLIFY_AUTH_TOKEN
If X collection fails, the workflow fails and the last good public xTrending file stays live.
The repo also includes:
.github/workflows/pipeline-health.yml
It:
- checks for stale or stuck
scrape/x-refreshworkflows - verifies the live manifest matches the repo manifest
- writes
site/data/health.json - updates
https://omgskills.com/health - opens or updates a
Pipeline healthGitHub issue when degraded
cd menubar
./build.sh # compiles release binary + assembles dist/omgskills.app
open dist/omgskills.appbuild.sh copies ../index/skills.json, ../index/trending.json, and ../site/data/manifest.json into the bundle when those files exist. Run the scraper first, or use existing generated JSON.
menubar/build.sh is for local/dev builds only. It uses ad-hoc signing, so it is not suitable for public downloads.
Use the canonical release script:
./scripts/release-mac.shOptional version override:
./scripts/release-mac.sh 0.0.3Required setup:
- Install a
Developer ID Applicationcertificate in your keychain. - Create an App Store Connect API key for notarization.
- Copy
.env.release.exampleand export these env vars in your shell:DEVELOPER_ID_APPLICATIONASC_PRIVATE_KEY_PATHASC_KEY_IDASC_ISSUER_ID
What the release script does:
- builds
menubar/dist/omgskills.app - signs it with Developer ID
- notarizes and staples it
- verifies it with
codesign,stapler, andspctl - writes:
menubar/dist/omgskills-mac.dmgmenubar/dist/omgskills-mac.dmg.sha256site/downloads/omgskills-mac.dmgsite/downloads/omgskills-mac.dmg.sha256menubar/dist/omgskills-mac.zipmenubar/dist/omgskills-mac.zip.sha256site/downloads/omgskills-mac.zipsite/downloads/omgskills-mac.zip.sha256site/updates/omgskills-<version>.zipsite/appcast.xml
All sources run simultaneously. Each returns a list of (repo, skill_md_path) candidates.
1. Topics (sources/topics.ts)
Searches GitHub repos by topic. Covers well-tagged repos in the Claude Code and Agent Skills ecosystems.
- Regular topics (14):
claude-code-skill,claude-skill,claude-skills,claude-code-plugin,claude-agent,agentic-skill,ai-agent-skills,claude-mcp,claude-commands,claude-code-tools,anthropic-claude,claude-skill-pack,claude-code-agent,agent-skill - Large topics (>1000 repos):
agent-skillssplit across 5 star ranges (>1000,100..1000,10..99,1..9,0) to stay under the GitHub 1000-result cap per query - Repo name patterns:
"Agent-Skill" in:name— catches repos liketwostraws/SwiftUI-Agent-SkillandAvdLee/Swift-Concurrency-Agent-Skillthat don't use topics
2. Code search (sources/code.ts)
Searches for SKILL.md files directly. Catches repos that never tagged themselves.
- Broad:
filename:SKILL.md— up to 1,000 results, any path accepted - Fingerprint A:
preamble-tier filename:SKILL.md— ~784 results, all genuine Claude Code skills (preamble-tier is Claude Code-specific) - Fingerprint B:
~/.claude/skills filename:SKILL.mdsplit across 8 size buckets — catches SKILL.md files that reference the install path
Any file path ending in /SKILL.md is accepted (no depth restriction). This handles root-level skills (SKILL.md), subdirectory skills (.claude/skills/name/SKILL.md, skills/name/SKILL.md, skillname/SKILL.md), and deeply nested monorepo skills (plugins/name/skills/name/SKILL.md).
The skill ID for subdirectory skills is owner/repo:parent-dir-name (e.g. AgriciDaniel/claude-ads:ads-google).
3. Aggregators (sources/aggregators.ts)
Finds repos matching awesome-claude in:name stars:>5 (top 50), fetches their READMEs, and extracts all GitHub URLs. Those repos become candidates. High false-positive rate (~80% skip), but enrichment filters them cheaply.
4. Social (sources/social.ts)
Extracts GitHub URLs from posts on Hacker News (Algolia API, free) and Reddit (r/ClaudeAI, r/anthropic, public JSON API, free). Both run in parallel. No authentication required. Contributes ~400–500 candidate repos per run.
All candidates are merged into a single Map<id, Candidate>. Code hits take priority (they carry the actual skill_md_path); topic/aggregator/social hits default to SKILL.md. Typical merged count: 14,000–20,000 candidates per run.
For each candidate:
- Fetch repo metadata (stars, last updated, topics) — skipped if already seeded from topic search
- Fetch
SKILL.mdviarepos.getContent— also returns the file's git object SHA - SHA cache check: if
fileData.sha === existing.skill_md_sha, return existing skill data with refreshed stars/last_updated. Skip YAML parse entirely. - Parse YAML frontmatter — must have
nameanddescription, otherwise skip - Sanitize all string fields to remove lone Unicode surrogates (prevents JSON decode failures in Swift)
- Store
skill_md_shafor next run's cache check
~50% of candidates are skipped (no valid SKILL.md frontmatter — false positives from broad searches). On warm cache runs, ~70–80% of existing skills hit the SHA cache.
README content is not shipped in skills.json. The app fetches README files lazily from GitHub only when a user opens a skill detail view, then caches them locally.
index/skills.json — array of Skill objects sorted by stars descending. Fields:
| Field | Description |
|---|---|
id |
owner/repo or owner/repo:skill-name for subdirectory skills |
name |
From SKILL.md frontmatter |
description |
From SKILL.md frontmatter |
github_url |
Repo URL |
install_cmd |
git clone command derived from path |
author_handle |
Repo owner |
tags |
Merged from SKILL.md frontmatter + GitHub repo topics |
stars |
GitHub star count |
last_updated |
Repo last push date |
first_seen |
Date first discovered (preserved across runs) |
skill_md_sha |
Git object SHA of SKILL.md (used for cache invalidation) |
FloatingPanel (NSPanel subclass) — borderless, floating level, non-activating. Positioned below the menubar status item. constrainFrameRect overridden to bypass macOS height capping. Toggled by clicking the status bar icon or pressing Option-S.
Height: 855pt. Width: 400pt (list only) or 750pt (with detail panel open).
SkillsStore loads lightweight library data from the remote cache when present, otherwise from the app bundle. It decodes snake_case → camelCase, sorts by stars, builds SQLite FTS5 search indexes asynchronously, and joins trending.json entries back to matching skills.
DataRefreshService checks the hosted manifest on launch and about once per day while the app is running. It validates byte counts and SHA-256 hashes, writes cached JSON into Application Support, skips rewrites when hashes are unchanged, and records refresh metadata for the “Data Updated” toast.
InstalledSkillsScanner scans ~/.claude/skills/, ~/.codex/skills/, and ~/.agents/skills/ for local SKILL.md files, parses their frontmatter, dedupes the list view, and keeps install-location counts for the local dashboard.
ReadmeLoader fetches repo READMEs on demand when a remote skill detail opens. It tries common names (README.md, README.mdx, README.txt, README) on main and master, renders the markdown in the detail panel, and caches successful loads under ~/Library/Application Support/omgskills/readmes/. This keeps startup data small and avoids README work during launch/search.
SkillInstaller installs remote skills for Claude or Codex by cloning the repo into ~/Library/Application Support/omgskills/repos/ and symlinking the correct skill folder into the selected global skills folder. It does not run raw install shell strings.
SkillSearchIndex (GRDB / SQLite FTS5) with BM25 weighted ranking:
- name: weight 10
- tags: weight 3
- author: weight 2
- description: weight 1
Uses FTS5Pattern(matchingAllPrefixesIn:) for prefix matching. Falls back to linear filter if FTS fails. Minimum query length: 2 chars.
ContentView — SwiftUI, single file.
- Left panel: search, sort menu, Installed/Discover tabs, starter search suggestions, and a starter-list link for Trending on skills.sh.
- Installed start state: local dashboard with All/Codex/Claude/Other counts, recently installed skills, and a GitHub URL install prompt.
- Remote detail: metadata, lazy README, GitHub link, and
Claude/Codexinstall buttons. - Local detail: Open, SKILL.md, GitHub when known, Install on the other platform when applicable, and Delete.
- Session state: if a skill detail is open when the window hides, the same selected skill/detail state is restored when it opens again during the same app session.
ReadmeWebView — NSViewRepresentable wrapping PassthroughWebView (WKWebView subclass). Uses marked.min.js (bundled, inlined at runtime) to render markdown/HTML. Height is reported back to SwiftUI via a WKScriptMessageHandler bridge so the ScrollView sizes correctly. Scroll events pass through to the parent ScrollView via scrollWheel override.
Discovery gaps: Repos with no GitHub topics, no preamble-tier or ~/.claude/skills in SKILL.md content, and no self-identifying name pattern (e.g. openai/plugins) can only be found via aggregator lists or social posts. These are impossible to discover programmatically without knowing the repo in advance.
GitHub code search cap: Each query returns at most 1,000 results. The ~/.claude/skills fingerprint queries use size splits to maximise coverage, but some buckets remain over the cap and only return a partial result set. Running the scraper repeatedly accumulates more skills over time as result ordering varies.
created: qualifier doesn't work in code search: GitHub's date filters are not supported for code search (only for repo/issue search). An earlier version of the scraper used broken date-range queries that returned 0 results — these were replaced with content fingerprint queries.
Lone Unicode surrogates: Some GitHub content contains lone surrogate characters that Node.js serialises to invalid JSON. Swift's JSONDecoder rejects the entire file if any entry contains one. The scraper strips surrogates from all string fields before writing. If the app shows Failed to decode JSON, regenerate skills.json with a fresh npm run scrape.
Scraper: Node.js 18+, TypeScript, tsx. Requires GITHUB_TOKEN in index/.env.
App: macOS 14+, Swift 6, SwiftUI. Dependencies: KeyboardShortcuts, Yams, GRDB.swift (via SPM).