Skip to content
Back to skills

Shadscan

ASecurity

Audit a React/shadcn app for missing UI fundamentals — accessibility, interaction, empty/error/loading states, form wiring, responsive shell, production polish — with the shadscan CLI, then route each real fix to the dev-flow skill that owns it. One of five pre-deploy gates, with `compliance-audit` (legal), `vercel-doctor` (cost), `launch-audit` (launch readiness) and `app-security` (code security); dev-flow proposes it at `feature_complete` and in the `deployed` maintenance loop. Use when th...

  • 6 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 19, 2026
developmentrustgoshellbashreactnextjsnodegitdatabasesecurity

Works with

  • cli
  • mcp

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 19, 2026

npx -y skills add lukedj78/dev-flow --skill shadscan --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Shadscan?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Shadscan
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/lukedj78-shadscan/badge)](https://www.skillsdirectory.com/skills/lukedj78-shadscan)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: shadscan
description: 'Audit a React/shadcn app for missing UI fundamentals — accessibility, interaction, empty/error/loading states, form wiring, responsive shell, production polish — with the shadscan CLI, then route each real fix to the dev-flow skill that owns it. One of five pre-deploy gates, with `compliance-audit` (legal), `vercel-doctor` (cost), `launch-audit` (launch readiness) and `app-security` (code security); dev-flow proposes it at `feature_complete` and in the `deployed` maintenance loop. Use when the user says "shadscan", "audit my UI", "check accessibility", "is this app accessible", "a11y audit", "missing loading/empty states", "UI quality check", or dev-flow routes here before a web deploy. Refuses for non-React / non-shadcn targets (mobile RN is out of scope). Not for: legal/privacy audit (use compliance-audit), Vercel cost (use vercel-doctor), launch readiness (use launch-audit), designing the UI (use design-md-to-app), or writing tests (use write-tests).'
---

# shadscan — UI-quality & accessibility pre-deploy gate for React/shadcn apps

Runs on a web project that **already exists** and uses **shadcn**. It wraps the third-party
[shadscan](https://www.shadscan.com/) CLI — a deterministic static audit of *UI fundamentals* — and turns
its report into applied fixes, routing each finding to the skill that owns it.

This is the gate that covers what neither sibling can see. `compliance-audit` reads the legal surface,
`vercel-doctor` reads the cost surface; **nothing in dev-flow mechanically verified that the UI we
prescribe actually got built** — that the reduced-motion guard `transitions` mandates is really there,
that the form errors `forms` specifies are really rendered, that a route has a loading boundary at all.

> **Third-party tool.** shadscan is **not** an official shadcn product — it's an independent open-source
> project by [TheOrcDev](https://github.com/TheOrcDev/shadscan), published to npm as **`@shadscan/cli`**
> (bin `shadscan`, **MIT**). Verified against the npm registry + repo README (at time of writing:
> **0.9.0**, ruleset `2026.07.41`, report `schemaVersion 9` — the report shape is versioned, re-check it).
> From the README, verbatim: *"The default scan is deterministic and read-only. It does not start the app,
> edit files, call an AI model, upload source, or require application secrets."* That makes it safe to run
> on a private codebase without the `--offline` dance `vercel-doctor` needs.

## Verified invocation + flags

```bash
npx --yes @shadscan/cli@0.9.0                                 # explore: human output, same pin
npx --yes @shadscan/cli@0.9.0 --json --no-interactive \
  > docs/ui/shadscan.json                                     # audit: PINNED — the mode this skill uses
npx --yes @shadscan/cli@0.9.0 --fail-under 70 --no-interactive # CI gate: pinned + ratcheted
```

⚠️ **Pin the version for anything you will compare.** `npx --yes @shadscan/cli` resolves to latest *on
every call* — during a single session it went 0.9.0 → 0.10.0 (ruleset `2026.07.41` → `2026.07.42`, 59
rules → 60), silently making a before/after diff a comparison across two different rulesets. shadscan
knows this: the `verification.shadscanCommand` it emits in its own acceptance criteria is **already
pinned** (`pnpm dlx @shadscan/cli@0.9.0 --json`). Follow it. Pin the baseline run, pin the re-run, and
only drop the pin when you deliberately want the newer ruleset — then re-baseline instead of diffing.
The explore line is pinned too: `--yes` runs a third-party package without asking, and an unpinned one
runs whatever was published last.

`pnpm dlx @shadscan/cli` and `bunx @shadscan/cli` work identically. Supported frameworks (per the README):
**Next.js, Vite, TanStack Start, Laravel, Astro, React Router** — it auto-detects the adapter and reports
it (`framework.adapter`, e.g. `next-app-router`) plus how confident it is that the project is shadcn at all.

| Flag | Effect |
|---|---|
| `[path]` | project directory to scan (default `.`) |
| `--json` | machine-readable report — **the mode this skill uses** |
| `--format <human\|json\|prompt>` | output shape |
| `--prompt` | print only a paste-ready remediation prompt for an agent |
| `--fail-under <score>` | exit non-zero below the score, **or when unassessed / partial source coverage** |
| `--category <category>` | run one audit category only |
| `--list-projects` / `--project <path>` | inspect / pick one workspace package instead of pooling them all |
| `--no-roast` / `--roast` | neutral copy vs. the snarky one-liners (roast is on by default in human output) |
| `--no-interactive` | disable follow-up prompts (**use in CI and in any agent run**) |
| `--apply` + `--agent <claude\|codex\|grok>` | hand the remediation prompt to an installed coding-agent CLI |
| `-V, --version` / `-h, --help` | version / help |
| `setup [path]` | write an explicit project integration |
| `mcp [paths...]` | serve shadscan as an **MCP server over stdio** for coding agents |

If a flag above is rejected, the CLI has moved — run `npx --yes @shadscan/cli@0.9.0 --help` (or the new
pin, if you are moving to it on purpose) and use what it reports rather than guessing.

⚠️ **Do not use `--apply`.** It shells out to another agent CLI to do the work. You *are* the agent —
read the JSON, triage it against the rules below, and apply the fixes yourself with the owning skill's
knowledge. `--apply` throws away the routing that makes this skill worth anything.

## The six categories (weights are the score, out of 100)

| Category | Weight | What it checks | Who fixes it in dev-flow |
|---|---:|---|---|
| **Foundation** | 20 | `components.json` parses, theme provider mounted in the shell, theme hydration safe, toast provider mounted | **`design-md-to-app`** (shell + providers), **`coss-ui`** when `stack.ui = "coss"` |
| **Interaction** | 20 | command menu present + `Cmd/Ctrl+K` bound, mobile nav trigger + controlled panel, responsive shell, keyboard navigation | **`design-md-to-app`** (it owns the shell/layout from DESIGN.md) |
| **States** | 20 | empty state, error state with retry, `not-found` recovery, route loading boundary, useful Suspense fallback | **`data-fetching`** (boundaries, Suspense, `loading.tsx`) + `design-md-to-app` (the *designed* state) |
| **Accessibility** | 20 | alt text, colour contrast, focus-visible not suppressed, dialog focus trap, heading structure, no positive `tabindex`, labels | **this skill** (mechanical) — icon a11y → **`animated-icons`**, motion → **`transitions`** |
| **Forms and Data Entry** | 10 | validation wired to the form, field errors rendered, invalid fields associated with their errors, async action pending state | **`forms`** |
| **Production Polish** | 10 | metadata title + description, social preview image, public SEO files, no starter copy left behind | **`design-md-to-app`** (metadata) + the deploy step |

Two rules deserve a name-check because they enforce **our own contract**:
`animations-respect-reduced-motion` is verbatim the core rule of the `transitions` skill, and
`forms-have-labels` / `invalid-fields-associated-with-errors` are what `forms` prescribes. shadscan is
the first thing in dev-flow that can *prove* those landed.

## Run, then route

1. **Preconditions.** `meta.json#stack.framework ∈ {"next","monorepo"}` and the project uses shadcn
   (`components.json` present). Refuse for `expo-rn` — the rules are DOM/React-web only. In a monorepo,
   `--list-projects` first, then `--project <path>` per app; pooling every package produces one
   meaningless blended score.
2. **Run** `npx --yes @shadscan/cli@<pinned> --json --no-interactive > docs/ui/shadscan.json`. Read
   `score`, `grade`, `categories[]`, and — critically — **`coverage.source`**. If coverage is not
   `"complete"`, the scan saw only part of the source and the score is not comparable to a previous run;
   say so instead of reporting a number.
2b. **Count hand-made UI beside the score — golden rule 3, `design/no-handmade-ui`.** shadscan audits
   what the UI is missing; it does not see a raw `<button>` that looks right and works, and neither did
   anything else on smart-travel, where 238 hand-made elements sat behind a green lint (2026-10-12). Run
   the project's rule on the app and report its count in the gate summary and in
   `meta.json#shadscan.handmade_ui` (with `card_in_card` beside it):

   ```bash
   pnpm exec eslint . -f json 2>/dev/null | node -e 'let r=JSON.parse(require("fs").readFileSync(0,"utf8")),c={};for(const f of r)for(const m of f.messages)if(/^design\//.test(m.ruleId||""))c[m.ruleId]=(c[m.ruleId]||0)+1;console.log(JSON.stringify(c))'
   ```

   A non-zero `design/no-handmade-ui` is a gate finding of its own, whatever the score: route each hit
   as golden rule 3 below (replace it with the primitive, never patch the copy, never `eslint-disable` it;
   a missing variant goes to the user as a DESIGN.md decision). No `design/no-handmade-ui` in the
   project at all is a finding too — the preset predates the rule and the write-time guard is missing:
   the retrofit is `design-md-to-app/scripts/setup_design_lint.py <root>`.
3. **Triage from `agentHandoff.actionables`, not from the human output.** Every actionable carries a
   `disposition`, a `confidence`, and the `file:line` it was found at. Act on the pair, never on the score:

   | `disposition` | What you do |
   |---|---|
   | `fix` | **Open the file and the code around it. Every time, whatever the confidence.** Fix only what survives that reading — on a real run 7 of 9 did not. See §Trust calibration for which rule kinds to disbelieve. |
   | `decide` | **Do not invent an answer** — it's a product question ("should this app have a command menu?"). Surface it to the user with the trade-off and move on. |
   | `verify` | Read the code and confirm. Many are advisories that **did not reduce the score**; a `pass` you can't reproduce is a finding about the detector, not the app. |

   ⚠️ **`confidence` is not a precision estimate — don't gate the work on it.** Measured on a real run,
   `high` items split 2 true / 3 false. What predicts correctness is the *kind of question the rule asks*
   (does a file exist? vs. is this component semantically complete?), not the label shadscan attaches.

   Each actionable ships `acceptanceCriteria` — machine-checkable, including a re-run of the rule and the
   project's own gates (`verification.projectGates`, e.g. `pnpm lint`, `pnpm typecheck`, `pnpm build`).
   **Run those gates after fixing** and report any you were not authorised to run.

   🚫 **Never optimise for the score.** shadscan says this itself, and it is the rule that keeps this gate
   honest: *do not add unused infrastructure solely to increase the audit score*, and *do not edit solely
   to force score-neutral static advisories to report pass*. A command menu nobody asked for is a
   regression that scores well. If a fix has no user-facing benefit, it is a `decide`, not a `fix`.
4. **Apply the fixes that survived step 3** here when they belong to nobody else — a missing file
   (`not-found.tsx`, `opengraph-image.tsx`, `robots`/`sitemap`), a genuinely missing `alt`, a positive
   `tabindex`, a heading level skipped.

   ⚠️ Two of those look mechanical and are not: a **suppressed focus ring** is only a defect if no
   ancestor carries `focus-within:`/`has-[…]:focus` — check the wrapper first; an **unlabelled control**
   inside `components/ui/` is usually a design-system primitive, where the label is the consumer's job
   and adding one inside is the wrong fix.

   When a fix creates a new surface, finish it: an `opengraph-image` without `metadataBase` resolves to
   `localhost` and shows nothing in production, and `ImageResponse` (Satori) **does not support `ch`
   units** — a `maxWidth: "20ch"` silently collapses and renders one word per line, through a green
   build. **Look at the generated image**, don't trust the exit code.
5. **Route the rest** to the owning skill rather than hand-fixing (table above). Don't re-implement
   `forms`' error-association pattern or `data-fetching`'s boundary ladder inside this skill.
6. **Persist**: keep `docs/ui/shadscan.json` (+ the human report if useful), update `meta.json#shadscan`,
   append `history`. **No phase bump.**

### ⚠️ Trust calibration — what a real run actually showed

Run against a live Next 16 + eve project: **64/100, grade D**, 59 rules (28 pass, 12 fail, 11
not-applicable, 8 advisory), 17 actionables — 9 `fix`, 7 `verify`, 4 `decide`. Every `fix` was opened in
the source. **Two of the nine survived.**

The seven that did not, and why — this table is the skill's real content:

| Finding | Why it was wrong |
|---|---|
| `focus-visible-not-suppressed` | the inner input's `outline-none` is deliberate; the **wrapper** carries `focus-within:ring-3` |
| `async-action-pending-state` | the parent passes `disabled={busy}` and renders a `Spinner` — pending state is **lifted**, not absent |
| `suspense-fallback-useful` | the `fallback={null}` is inside a **WebGL `<Canvas>`**, where DOM nodes are illegal; `null` is correct |
| `links-have-accessible-names` | the link is a **Base UI `render` prop**; the accessible name arrives as the parent's children |
| `empty-state-present` | the "data-backed collection" is a **static table authored by hand** |
| `validation-wired-to-form` | a chat composer needs no form library |
| `forms-have-labels` | flagged a **design-system primitive**; a primitive carries no label, its consumer does |

**The pattern: shadscan is precise about what one file contains, and blind to composition.** It never
resolves speculatively — every string it quoted was really there — but it cannot follow a prop across a
component boundary, climb to a wrapper, see through a `render` prop, or know that a subtree renders to
WebGL instead of the DOM. **The better-composed the codebase, the more false positives it produces.**

So trust it by *what kind of question the rule asks*:

| Rule kind | Examples | Trust |
|---|---|---|
| **Does this file exist?** | `not-found-route-present`, `social-preview-present`, `public-app-seo-files-present`, `shadcn-config-present` | **high** — no code comprehension needed; both true positives were of this kind |
| **What is in this one file, self-contained?** | `no-positive-tabindex`, `images-have-alt` on a literal `<img>` | medium — usually right |
| **Is this component semantically complete?** | pending state, empty state, labels, focus rings, Suspense fallbacks | **low — assume wrong until the surrounding composition proves otherwise** |

Read the **parent, the wrapper and the render target** before touching anything in the third row. Cost of
being wrong is asymmetric: a missed finding is a small gap, a "fix" applied to a false positive damages
working code and its author's trust in the gate.

This is a *different* failure mode from `vercel-doctor`'s, not a milder one. There, `knip` invented
import graphs and flagged 58% of a codebase as dead. Here nothing is invented — the tool simply cannot
see past the file boundary, and good composition is what puts the answer on the other side of it.

### When the fix is real but the rule stays red

`mobile-nav-present` failed on a header that hid its whole `<nav>` below `sm` with no replacement — a
real defect: on a phone one route was unreachable. The fix was to keep two links visible and let the
wordmark step aside, verified at 375 px. **The rule still failed**: the detector looks for a trigger +
controlled panel pair, and does not recognise the "keep links visible in an explicit small-screen layout"
alternative that its own remediation text offers.

The correct outcome is the one that feels wrong: **ship the fix, leave the rule red, write down why.**
Adding a hamburger menu for two links to turn the rule green is the textbook case of the anti-gaming rule
above. When a remediation's stated alternative is not what the detector checks, the detector is the
narrower of the two — follow the user's interest, not the score.

## `meta.json#shadscan` block

```jsonc
"shadscan": {
  "last_run_at": "<ISO>",
  "engine_version": "0.9.0",     // report.engineVersion
  "ruleset_version": "2026.07.41", // report.rulesetVersion — the score only compares within a ruleset
  "coverage": "complete",        // report.coverage.source — anything else invalidates the score
  "score": 0,                    // 0-100
  "grade": "D",
  "categories": { "foundation": 0, "interaction": 0, "states": 0,
                  "accessibility": 0, "forms": 0, "production-polish": 0 },
  "fixed": ["not-found-route-present","social-preview-present"],
  "decisions_open": ["command-menu-present"],   // disposition=decide, awaiting the user
  // Opened, read, and rejected — with the reason, so the next run doesn't re-litigate them.
  "false_positives": {
    "focus-visible-not-suppressed": "wrapper carries focus-within:ring",
    "suspense-fallback-useful": "inside a WebGL <Canvas>; null is correct"
  },
  // Real fix shipped, rule still red because the detector checks something narrower.
  "rules_left_red": {
    "mobile-nav-present": "links kept visible at <sm; detector wants a trigger+panel pair"
  },
  // golden rule 3, counted beside the score (step 2b): design/no-handmade-ui, design/no-card-in-card.
  // null = the rule is not in the project (itself a finding: retrofit with setup_design_lint.py)
  "handmade_ui": 0,
  "card_in_card": 0,
  "routed": { "forms": ["field-errors-rendered"],
              "data-fetching": ["route-loading-boundary-present"],
              "design-md-to-app": ["theme-hydration-safe"] }
}
```

⚠️ **Only compare scores within the same `rulesetVersion`.** A new ruleset adds rules; a score that
"dropped" across versions may be the same app measured against more checks. This is not theoretical — see
the pinning warning above; it fired on the very first real before/after. **Assert `rulesetVersion` equality
in code before reporting a delta**, rather than trusting that two runs minutes apart used the same tool.

## dev-flow hook

Horizontal capability — run any time. dev-flow **proposes it as a pre-deploy gate** at `feature_complete`,
one of five: `compliance-audit` (legal) · `vercel-doctor` (cost) · **`shadscan` (UI quality + a11y)** · `launch-audit` (commercial readiness) · `app-security` (code security).
Re-run it in the `deployed` maintenance loop to catch UI regressions. It records `meta.json#shadscan` +
`history` and **never bumps `phase`**. It never *blocks* the deploy on its own — it surfaces the score and
the open decisions so the user decides.

**In CI** the natural form is `npx --yes @shadscan/cli@<pinned> --json --no-interactive --fail-under <N>` — pinned, like every run you compare — with `N`
set to the score already achieved (a ratchet), not to an aspirational number. `--fail-under` also fails on
partial coverage, which is the behaviour you want.

## Ecosystem-first: shadscan ships its own agent skills

The repo carries `.agents/skills/` — read them from source rather than paraphrasing:

- **`migrate-radix-to-base/`** (SKILL.md + 9 reference files) — a Radix → **Base UI** migration guide.
  Directly relevant: our contract makes Base UI the default `ui_base`, so this is the upstream-maintained
  path for a project still on Radix. Prefer it over writing our own migration notes.
- **`shadcn/rules/`** — base-vs-radix, chat, composition, forms, icons, styling.

Consistent with rule zero: when the tool ships the knowledge, use the tool's copy.

## Relationship to other skills

- **`compliance-audit`** / **`vercel-doctor`** — the sibling gates. Same shape: `feature_complete`
  pre-deploy, no phase bump, "auto-fix safe + route/flag the rest". Different surface: legal, cost, UI.
- **`transitions`** — owns `prefers-reduced-motion`. shadscan **detects** the violation, `transitions`
  **defines** the fix (tokenised motion, cheapest tier first).
- **`forms`** — owns validation wiring, error rendering and error association. Route every `forms`
  category finding there.
- **`data-fetching`** — owns Suspense boundaries, `loading.tsx` and the read ladder behind the *States*
  category.
- **`design-md-to-app`** / **`coss-ui`** — own the shell, providers, responsive layout and metadata:
  most of *Foundation*, *Interaction* and *Production Polish*.
- **`composition-patterns-guide`** — owns component anatomy (input groups, item grouping, alert anatomy).
- **Golden rule 3** (`references/contracts.md` §Golden rules) — a finding whose cause is **a component
  that duplicates a primitive** (a hand-built dropdown missing ARIA, a custom modal without a focus trap, a
  `div` skeleton without `aria-busy`, a home-made empty state) is not fixed by patching the copy: the fix
  is to replace it with the installed primitive. Route by where it lives — a page's `_components/` →
  `screenshot-to-page`; a form control → `forms`; a module's screen → `module-add`; the shell or a
  primitive missing from `components/ui/` → `design-md-to-app` (`shadcn add <name>`; Coss/UI → `coss-ui`).
  Adding the missing ARIA to the hand-rolled copy makes the rule go green and keeps the violation.
  **The same routing applies to every `design/no-handmade-ui` hit** counted at step 2b (raw elements,
  hand-written roles, classes that redraw a Card/Badge/Alert/Separator): the message names the primitive;
  the fix is that primitive — «the primitive renders the design»: if it looks different from the board or
  the screen it replaces, tune its `cva` variant and DESIGN.md tokens until a before/after pixel diff is
  zero; never correct the board, never rebuild the look with classes at the call site.

  Example, from an actionable to the route:

  > `interaction/menu-keyboard` · `fix` · `app/(app)/_components/account-menu.tsx:9` — *menu items not
  > reachable by keyboard.* Reading the file: a `div` list toggled by `useState`, no roving focus.
  > `components/ui/dropdown-menu.tsx` is installed. → **Golden rule 3**, routed to `screenshot-to-page`:
  > replace with `DropdownMenu`; do not add `tabIndex`/`onKeyDown` to the copy.
- **`write-tests`** — complementary, not overlapping: shadscan is static and finds *missing* fundamentals;
  tests assert *behaviour*. A shadscan fix is a good prompt for a regression test.

## Definition of Done

- `docs/ui/shadscan.json` written; `meta.json#shadscan` populated with score, grade, per-category
  breakdown, **ruleset version** and **coverage**.
- Every `fix` actionable **opened in the source**, then either applied or declined **with the reason
  recorded** (which parent/wrapper/render target made it a false positive); every `decide` surfaced to
  the user as an open question; every `verify` checked in code.
- Any fix that is real but leaves its rule red is **shipped anyway and documented** — in the commit
  message and in `meta.json#shadscan.rules_left_red`, so the next run doesn't re-litigate it.
- The project's own gates (`verification.projectGates`) run green after the fixes — or the ones you
  couldn't run named explicitly.
- Nothing was added purely to move the number.

## The gate next door

`launch-audit` asks the question this one does not: **can a stranger find this, trust it, and buy
from it?** The overlap is deliberate and narrow, and stated in both directions so neither skill
assumes the other did it:

| | this skill | `launch-audit` |
|---|---|---|
| `not-found` route | checks it **exists** | — |
| social preview | checks the **file exists** | checks it **resolves and has dimensions** |
| SEO files | checks they exist | checks `robots` does not blanket-disallow and the sitemap names the public routes |
| metadata per route | — | title and description on **every** public route |
| privacy / terms / identity block | — | owns it |
| a11y, empty states, labels, focus | owns it | defers here, always |

A product passes this gate and still has no company name on it. That is not a hole in this skill —
it is a different question, and it has its own gate now.

## What this skill does NOT do

- **Not a legal/privacy audit** — that's `compliance-audit`. **Not a cost audit** — that's `vercel-doctor`.
- **Doesn't design the UI** — it checks that the built UI has its fundamentals; `design-md-to-app` designs.
- **Doesn't cover mobile** — the rules are DOM/React-web; Expo/RN has no equivalent here. And the *web-on-a-phone* layer (sticky hover, the tap-highlight flash, `100dvh` vs `100svh`, `touch-action`, safe areas) is not in its 62 rules either — that is `design-md-to-app/references/mobile-web.md`.
- **Doesn't feed the UI bad data.** Every rule reads the built output against whatever data was there. A component that passes all 62 can still break on a hyphenated surname or a count of exactly 1 — that is `write-tests/references/worst-case-data.md`, and it is a different operation, not a stricter setting.
- **Doesn't use `--apply`** — the routing to owning skills is the whole point.
- **Doesn't bump `phase`**, and never blocks deploy by itself.

## Sources

- Official site: https://www.shadscan.com/
- Repo + rules (`packages/cli/src/rules/`, 62 rules) + bundled agent skills: https://github.com/TheOrcDev/shadscan
- npm: https://www.npmjs.com/package/@shadscan/cli

## Reference files

- `references/contracts.md` — the `.workflow/` dev-flow contract (vendored).

Files in this skill

  • SKILL.md25 KB
  • references/contracts.md80.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.