One .zip per skill. Inside: SKILL.md with the intent and the frontmatter, and the code that runs in the page.
Settings → Personalize → Skills. This is Cowork's own skill library, shared across every chat.
Click Add and choose the file you just downloaded. Cowork installs it once; no CLAUDE.md, no per-project setup.
The skill stays available in every session. Cowork runs it in your own Chrome, riding the session you are already signed into.
Search GitHub repositories by keyword, sorted by stars, forks or recency.
{"query":"string, REQUIRED (GitHub search query; supports the full syntax, e.g. \"browser automation language:python stars:>100\")","sort":"string optional: stars | forks | updated","order":"string optional: desc (default) | asc","limit":"int optional"}{"count":"int","total":"int (total matches on GitHub)","query":"string","results":"array of { full_name, description, stars, forks, language, topics, url, updatedAt }"}Searches repos by keyword. Each result's full_name is what github-get-repo and github-list-issues take, the first step of the chain. Resolves in a single bridge call: the extractor fetches what it needs itself, so the params actually drive the request and no page is opened.
The extractor lives as text in the site's localStorage (key github-search-repos) and is rebuilt on each call with synchronous eval. It is stored once and then persists across navigation and browser restarts, so on every run except the first it is already there.
Default action: just call (step 1). The call itself reports NEEDS_STORE when the extractor is missing or out of date, only then do you store it (step 2) and call again.
Versioning. The stored extractor is gated by VER (must match the version in the frontmatter above). Both snippets carry VER = "1", keep them in sync with the frontmatter. When evaluate.js changes, bump version and VER in both snippets: the next call sees the mismatch, returns NEEDS_STORE, and re-stores automatically. Storage stays at two keys (github-search-repos and github-search-repos:ver), overwritten each store, versions never accumulate.
await (() => {
const KEY = "github-search-repos", VER = "1";
const s = localStorage.getItem(KEY);
if (!s || localStorage.getItem(KEY + ":ver") !== VER) return 'NEEDS_STORE';
const fn = eval('(' + s + ')');
return fn(document, { query: "browser automation language:python", sort: "stars" });
})()
Returns the matching repos. If it returns the string 'NEEDS_STORE', the extractor is missing or stale in this browser → do step 2 once, then run this exact call again.
Run this snippet verbatim. Never add an await before the eval, only the outer await is allowed.
A github.com tab is what holds the stored extractor (localStorage is per-origin). The data itself comes from the public api.github.com REST API, which is CORS-open, so no session and no page are involved.
NEEDS_STORE; persists until localStorage is cleared)From a github.com tab, evaluate the following with the bridge, replacing <FN> with the contents of evaluate.js (the ( async (root, params) => { ... } ) function). Keep VER equal to the call snippet's:
(() => {
const KEY = "github-search-repos", VER = "1";
const fn = (<FN>);
localStorage.setItem(KEY, String(fn));
localStorage.setItem(KEY + ":ver", VER);
return 'stored';
})()
Returns "stored". The extractor passes through the model only here, once. Then go back to step 1.
api.github.com allows 60 requests per hour per IP unauthenticated (verified: x-ratelimit-limit: 60). That budget is shared by every github-* skill and by anything else on your IP hitting the API. On exhaustion the call returns the rate-limit error, wait, do not retry in a loop.
query is passed to GitHub verbatim, so its qualifiers all work: language:python, stars:>1000, topic:agents, org:anthropics, created:>2025-01-01. Filter there rather than post-filtering the results, it costs the same one request.
NEEDS_STORE → extractor missing or version-mismatched in this browser; run step 2 once, then retry step 1.rate limited → see Rate limit. Search has a tighter budget than the other endpoints.request_failed → the fetch could not run from this tab.{ "type": "json", "jsonPath": "count" }