Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 28 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,17 @@ The registry's deploys are not gated on the scan result.

Stable URLs, no auth required:

+ Public site: `https://coder.github.io/coder-skill-scanner/`
+ Per-skill detail: `https://coder.github.io/coder-skill-scanner/skills/<namespace>/<slug>`
+ Run history: `https://coder.github.io/coder-skill-scanner/history`
+ CDN-cached JSON: `https://coder.github.io/coder-skill-scanner/latest.json`
+ Public site: `https://scanner.registry.coder.com/`
+ Per-skill detail: `https://scanner.registry.coder.com/skills/<namespace>/<slug>`
+ Run history: `https://scanner.registry.coder.com/history`
+ CDN-cached JSON: `https://scanner.registry.coder.com/latest.json`
+ Tagged release: `https://github.com/coder/coder-skill-scanner/releases/latest/download/latest.json`
+ Schema: `https://coder.github.io/coder-skill-scanner/schema.json` (v1)
+ Per-scan history (JSON): `https://coder.github.io/coder-skill-scanner/history/index.json`
+ Schema: `https://scanner.registry.coder.com/schema.json` (v1)
+ Per-scan history (JSON): `https://scanner.registry.coder.com/history/index.json`

The custom domain is configured via `site/public/CNAME`; the legacy
project-page URL (`https://coder.github.io/coder-skill-scanner/`) is
still redirected by GitHub Pages but should not be used in new code.

## Public API (v1)

Expand Down Expand Up @@ -68,19 +72,23 @@ Two badges per skill:
Embed a status badge in a README:

```markdown
![skill scan](https://coder.github.io/coder-skill-scanner/api/v1/skills/coder/setup/badge/status.svg)
![skill scan](https://scanner.registry.coder.com/api/v1/skills/coder/setup/badge/status.svg)
```

Or via shields.io if you want their renderer:

```markdown
![skill scan](https://img.shields.io/endpoint?url=https://coder.github.io/coder-skill-scanner/api/v1/skills/coder/setup/badge/status.json)
![skill scan](https://img.shields.io/endpoint?url=https://scanner.registry.coder.com/api/v1/skills/coder/setup/badge/status.json)
```

For a fork, swap the host: `https://<owner>.github.io/<repo>/api/v1/...`.
The scanner derives the public base URL from `$GITHUB_REPOSITORY` at
publish time, so the same URL pattern is correct for any fork without
config changes.
For a fork, swap the host: `https://<your-host>/api/v1/...`. The scanner
picks the public base URL at publish time in this order:

1. `site/public/CNAME` (the custom Pages domain, if set),
2. otherwise `$GITHUB_REPOSITORY` -> `https://<owner>.github.io/<repo>`.

So a fork that just sets a CNAME gets the right URLs everywhere without
touching workflow code.
Comment on lines +84 to +91

## Running locally

Expand Down Expand Up @@ -114,6 +122,7 @@ as `/skills/coder/setup` stay client-side.
|-- scanner/ # Python module (CLI + enumerate + combine + aggregate + history)
|-- tests/ # pytest, no on-disk fixtures
|-- site/ # React SPA (Vite + Tailwind + Radix + react-router-dom)
| `-- public/CNAME # custom Pages domain (drop or change for a fork)
|-- pyproject.toml
|-- Makefile
|-- mise.toml # pinned Python + Node versions
Expand All @@ -140,15 +149,19 @@ This scanner is data-driven. To run it against a different registry:
2. Edit `config.yaml`'s `catalogue.registry_repo` block.
3. Configure GitHub Pages on your fork (Settings, Pages, source:
"GitHub Actions").
4. Set Actions workflow permissions to "Read and write" so the
4. Optional: set a custom domain by editing `site/public/CNAME` (one
line, the bare host). Delete the file to publish at the github.io
project-page URL instead. Whichever you choose, DNS for the host
needs to point at `<owner>.github.io` separately.
5. Set Actions workflow permissions to "Read and write" so the
publish-release job can create releases.
5. To enable the LLM semantic pass, set the credential secret matching
6. To enable the LLM semantic pass, set the credential secret matching
`config.yaml`'s `scanners.skillspector.llm.provider` on your fork
(for the default `anthropic` provider, `ANTHROPIC_API_KEY`), AND
confirm `.github/workflows/scan.yaml` exports that secret into the
SkillSpector step. Static-only mode (without the secret) is the
default and works out of the box.
6. Enable Actions.
7. Enable Actions.

No source changes required for catalogue changes.

Expand Down
20 changes: 18 additions & 2 deletions scanner/_carry_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,33 @@
import urllib.request
from pathlib import Path

DEFAULT_BASE = "https://coder.github.io/coder-skill-scanner"
DEFAULT_BASE_FALLBACK = "https://coder.github.io/coder-skill-scanner"
MAX_SNAPSHOTS = 120 # roughly 30 days at a 6h cadence.


def _default_base() -> str:
"""Return the canonical Pages URL for this repo.

Prefer ``site/public/CNAME`` when present so a custom-domain deploy
fetches prior history from the same origin it will publish to. Fall
back to the github.io project-page URL otherwise.
"""
cname = Path("site/public/CNAME")
if cname.is_file():
for line in cname.read_text(encoding="utf-8").splitlines():
host = line.strip()
if host:
return f"https://{host}"
return DEFAULT_BASE_FALLBACK
Comment on lines +25 to +38


def _fetch(url: str, dest: Path) -> None:
dest.parent.mkdir(parents=True, exist_ok=True)
urllib.request.urlretrieve(url, dest)


def main(out_dir: str = "prior-history") -> int:
base = os.environ.get("PAGES_URL") or DEFAULT_BASE
base = os.environ.get("PAGES_URL") or _default_base()
base = base.rstrip("/")
out = Path(out_dir)
out.mkdir(parents=True, exist_ok=True)
Expand Down
1 change: 1 addition & 0 deletions site/public/CNAME
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
scanner.registry.coder.com
14 changes: 10 additions & 4 deletions site/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ const REPORT_REGEX = /^\/(latest\.json|schema\.json|history\/.+\.json)$/;
* Resolve the production `base` path. Lookup priority:
* 1. `VITE_BASE_PATH` env (explicit override; useful for local prod
* builds when you want to mimic a specific Pages deployment).
* 2. The repo name parsed from `GITHUB_REPOSITORY` (CI default; a fork
* named `<owner>/<repo>` gets `/<repo>/` automatically with zero
* config).
* 3. `/` (apex / local-build fallback).
* 2. A `public/CNAME` file. When the site is published to a custom
* Pages domain, GitHub serves it at the apex of that domain so
* assets must resolve at `/`, not under `/<repo>/`.
* 3. The repo name parsed from `GITHUB_REPOSITORY` (CI default; a fork
* named `<owner>/<repo>` without a custom domain gets `/<repo>/`
* automatically with zero config).
* 4. `/` (apex / local-build fallback).
*
* Always returns a value with leading and trailing slashes so Vite's
* own asset URL logic does not have to special-case the input.
Expand All @@ -34,6 +37,9 @@ function resolveProductionBase(): string {
const prefixed = explicit.startsWith("/") ? explicit : `/${explicit}`;
return prefixed.endsWith("/") ? prefixed : `${prefixed}/`;
}
if (fs.existsSync(path.resolve("public", "CNAME"))) {
return "/";
}
Comment on lines +40 to +42
const repo = process.env.GITHUB_REPOSITORY?.split("/")[1]?.trim();
if (repo) return `/${repo}/`;
return "/";
Expand Down