{"owner":"davila7","repo":"claude-code-templates","hasSkills":true,"totalSkillsCount":4,"totalTokensCount":11516,"categories":["claude-rule","mcp-config","marketplace","plugin-manifest"],"hasMcp":true,"mcpConfig":{"mcpServers":{"claude-code-templates":{"command":"npx","args":["-y","@modelcontextprotocol/server-claude-code-templates"]}}},"found":["CLAUDE.md",".cursor/mcp.json",".claude-plugin/marketplace.json",".claude-plugin/plugin.json"],"skills":{"CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code when working with this repository.\n\n## Project Overview\n\nNode.js CLI tool for managing Claude Code components (agents, commands, MCPs, hooks, settings) with a static website for browsing and installing components. The dashboard and its API routes are deployed on Cloudflare Pages, with supporting cron and monitoring tasks running as Cloudflare Workers.\n\n## Essential Commands\n\n```bash\n# Development\nnpm install                    # Install dependencies\nnpm test                       # Run tests\nnpm version X.Y.Z --ignore-scripts=false  # Sync, commit, and tag all package versions\nnpm run version:set -- X.Y.Z   # Sync versions without creating a commit or tag\nnpm publish --ignore-scripts=false  # Publish and run the trusted prepublish guard\n\n# Component catalog\npython scripts/generate_components_json.py                   # Update docs/components.json (pulls download counts from Supabase: minutes)\npython scripts/generate_components_json.py --skip-downloads  # Same, keeping the counts already in the catalog: seconds\n\n# Dashboard + API (Astro on Cloudflare Pages)\ncd dashboard && npm run build  # Build before deploy\nnpm run deploy                 # Deploy www + app.aitmpl.com via wrangler\n```\n\n> Deploys to production happen automatically via GitHub Actions on push to `main`\n> (changes in `dashboard/**`). Manual deploy uses `wrangler pages deploy`, not Vercel.\n\n## Security Guidelines\n\n### ⛔ CRITICAL: NEVER Hardcode Secrets or IDs\n\n**NEVER write API keys, tokens, passwords, project IDs, org IDs, or any identifier in code.** This includes Cloudflare account/project IDs, Supabase URLs, Discord IDs, database connection strings, and any other infrastructure identifier. ALL must go in `.env` (or Cloudflare secrets via `wrangler secret put`).\n\n```javascript\n// ❌ WRONG\nconst API_KEY = \"AIzaSy...\";\n\n// ✅ CORRECT\nconst API_KEY = process.env.GOOGLE_API_KEY;\n```\n\n**When creating scripts with API keys:**\n1. Use `process.env` (Node.js) or `os.environ.get()` (Python)\n2. Load from `.env` file using `dotenv`\n3. Add variable to `.env.example` with placeholder\n4. Verify `.env` is in `.gitignore`\n\n**If you accidentally commit a secret:**\n1. Revoke the key IMMEDIATELY\n2. Generate new key\n3. Update `.env`\n4. Old key is compromised forever (git history)\n\n## Component System\n\n### Component Types\n\n**Agents** (600+) - AI specialists for development tasks\n**Commands** (200+) - Custom slash commands for workflows\n**MCPs** (55+) - External service integrations\n**Settings** (60+) - Claude Code configuration files\n**Hooks** (39+) - Automation triggers\n**Loops** (18+) - Autonomous agentic workflows (goal + interval + stop condition) that reference other components\n**Mods** (10) - Claude Mods: plugins whose behaviour lives in a function-hooks module (`register(on, options)` hooking engine events as `($, e, next)` middleware). Anthropic's reference is [anthropics/claude-code/mods](https://github.com/anthropics/claude-code/tree/main/mods) (three built-in mods + `mods/types/claude-code.d.ts`); discussion in [anthropics/claude-code#91870](https://github.com/anthropics/claude-code/issues/91870). Mods are official and on by default in Claude Code >= 2.1.287 ([docs](https://code.claude.com/docs/en/plugins/mods/overview)); 2.1.287+ ignores `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS`, builds 2.1.259–2.1.286 still need it set to `1`; the `$` API may change between releases. Each mod is a complete plugin directory in Anthropic's `mods/` layout: `cli-tool/components/mods/{category}/{name}/` with `.claude-plugin/plugin.json` (name, description, `userConfig` — options are read from user/managed settings `pluginConfigs[name].options`, never project settings), `hooks/hooks.json` (`modules`), any number of hooks-modules under `hooks/` (relative imports allowed), optional `commands/*.md` (slash commands the plugin ships; a `skill.prompt` hook may rewrite their prompt at run time), `types/`, `tests/`, and a `README.md` the site shows. The generator uses README.md as content and ships every text file in the per-component content file (`files`), so the site explorer and the send-to-repo flow have the whole plugin. `--mod` (alias `--function-hook`) downloads the directory recursively (like a skill) and writes it verbatim to `.claude/skills/{name}/`, which Claude Code auto-loads as `{name}@skills-dir` — **only in a trusted project** (see \"Debugging a mod that seems silent\" below). Third-party mods are vendored as-is with LICENSE + attribution (e.g. `games/cc-arcade`). **Every module must typecheck against `cli-tool/components/mods/types/claude-code.d.ts`** (`cd cli-tool/components/mods && npx -y -p typescript@5 tsc -p tsconfig.json`; CI runs it in `mods-typecheck.yml`). Rules from the engine: import types only from `'claude-code'`, spell `$` as `$.noun.event(...)` at the call site (never pass `$` to a helper), treat `e` as frozen, deny with `{ deny }` without calling `next` (returning `{}` is fail-open). When Anthropic bumps the API, regenerate the d.ts with `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude -p \"/plugin-types <dir>\"` on a current Claude Code and re-run tsc (the header's first line names the writing version; currently 2.1.301; 2.1.278 added `prompt.attachment` — the per-attachment hook `jev-skill-suggestion` uses to withhold the `skill_listing` system-reminder — and 2.1.283 changed `$.model.complete` from a string and `$.model.fork` from `result | null` to `{ isAnswered, text, usage } | { isAnswered: false, reason }`). Note `/context`'s \"Skills\" row is estimated from the roster and does not reflect that hook; a mod can ship `commands/*.md` and rewrite their prompt in `skill.prompt` (that is how `/jev-skill-suggestion:setup` hides every skill as `user-invocable-only` while the mod injects the chosen `SKILL.md` itself). Typechecking against the d.ts does not prove an older engine draws a mod: Claude Code 2.1.287 skips the whole `ui.render` of a Button with Text children instead of a `label` string, and the Claude Desktop app may run an older bundled build than your terminal: `ultra-config` drew in a 2.1.301 terminal and stayed empty in Desktop with exactly that skip. Test a pane on the oldest supported engine too: `npm i @anthropic-ai/claude-code@2.1.287` in a scratch folder, then run its `claude plugin test {category}/{name}` from `cli-tool/components/mods`. The early-access banner and Beta badge were removed 2026-10-04 (mods are official). Old URLs (`/function-hooks`, `/component/function-hook/*`) redirect via `dashboard/public/_redirects`.\n**Templates** (14+) - Complete project configurations\n\n#### Debugging a mod that seems silent (verified 2026-09-19 on Claude Code 2.1.278)\n\nA mod that \"produces no output\" has, in order of likelihood, these causes — none of which is the mod's code. Check them before touching the module:\n\n1. **The project is not trusted.** Claude Code adopts a plugin from a project's `.claude/skills/{name}/` only when `~/.claude.json` has `projects[<path>].hasTrustDialogAccepted: true`; an untrusted folder's `.claude/` is repository content and is not read at all (`plugin.scope===\"project\"` candidates are dropped and re-discovered \"post-trust\"). `claude -p` never shows the trust prompt, so a headless run in a fresh folder never loads a project mod. Fix: open `claude` interactively in the folder and accept the prompt, or pass `--plugin-dir .claude/skills/{name}` (explicit, loads as `{name}@inline`). `~/.claude/skills/{name}/` (user-level) always loads.\n2. **You are looking in the wrong place.** `$.ui.log` appends a dim transcript line and writes to the debug log; `$.ui.status` pins a line under the prompt. In `claude -p` / the SDK there is no transcript and no status row: the lines are only in `~/.claude/debug/<session-id>.txt` (`.txt`, not `.log`; `latest` symlinks the newest) and reach an SDK host as `ui_log`. The header, statusline and effort box show the *session's* settings and never move: a mod rewrites `model`/`effort` per *request*. The other observable is `\"effort\":\"…\"` in the transcript JSONL under `~/.claude/projects/<slug>/`.\n3. **Options under the wrong key.** `pluginConfigs` is keyed by the plugin's full id: `\"{name}@skills-dir\"` when auto-loaded, `\"{name}\"` with `--plugin-dir`. Under the wrong key every option is its default and the debug log says `plugin {name}: no pluginConfigs[\"{name}@skills-dir\"].options in user, --settings or managed settings`.\n4. **Function hooks off.** On Claude Code < 2.1.287 with `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS` unset/`0` the debug log says `installed plugins' hooks modules not loaded: rollout flag (tengu_plugin_hooks_modules) is off`. Built-in mods (`agents-md@builtin`) load regardless, so their presence proves nothing about ours.\n\nThe lines that settle each question in `claude --debug`: `Found N plugins`, `Read hooks.json for plugin {name}`, `hooks module {name}@skills-dir loaded (worker, environment N, tier user); events: …`, then `hooks module {name}@… <event> settled in Xms`. Positive control: `pacman@skills-dir` loads from `.claude/skills/` in the trusted `claude-code-templates` checkout. Anthropic documents only `claude --plugin-dir mods/<name>` for its reference mods; the `.claude/skills` auto-load is Claude Code 2.1.157+ (CHANGELOG: \"Plugins in `.claude/skills` directories are now automatically loaded\").\n\n### Installation Patterns\n\n```bash\n# Single component\nnpx claude-code-templates@latest --agent frontend-developer\nnpx claude-code-templates@latest --command setup-testing\nnpx claude-code-templates@latest --hook automation/simple-notifications\nnpx claude-code-templates@latest --loop engineering/docs-sweep-loop  # also installs the loop's referenced components\nnpx claude-code-templates@latest --mod security/secret-redactor          # Claude Mod: plugin at .claude/skills/secret-redactor/\n\n# Batch installation\nnpx claude-code-templates@latest --agent security-auditor --command security-audit --setting read-only-mode\n\n# Interactive mode\nnpx claude-code-templates@latest\n```\n\n### Component Development\n\n#### Adding New Components\n\n**CRITICAL: Use the component-reviewer agent for ALL component changes**\n\nWhen adding or modifying components, you MUST use the `component-reviewer` subagent to validate the component before committing:\n\n```\nUse the component-reviewer agent to review [component-path]\n```\n\n**Component Creation Workflow:**\n\n1. Create component file in `cli-tool/components/{type}/{category}/{name}.md`\n2. Use descriptive hyphenated names (kebab-case)\n3. Include clear descriptions and usage examples\n4. **REVIEW with component-reviewer agent** (validates format, security, naming)\n5. Fix any issues identified by the reviewer\n6. **TEST before generating/publishing**: ask the human in the session whether\n   they want to test the newly created/modified component(s) first. Do NOT run\n   `generate_components_json.py`, commit, or publish until testing is confirmed\n   or explicitly skipped by the user.\n7. Run `python scripts/generate_components_json.py` to update catalog\n   (**maintainers only** — see below)\n\n#### Generated catalog files: who regenerates them\n\n`scripts/generate_components_json.py` writes `docs/components.json` and the\nsplit `dashboard/public/` artifacts (`components.json`, `counts.json`,\n`search-index.json`, `components/*.json`, `component-content/**`, `api/v1/**`). These files\nare **generated output, never hand-edited**, and who commits them depends on\nthe workflow:\n\n| Workflow | Regenerate + commit the catalog? |\n|----------|----------------------------------|\n| Maintainer working directly on this repo (local branch, sync PRs, agent-driven migrations) | ✅ Yes — run the script (`--skip-downloads` is enough for a content change) and commit the output with the component |\n| **External contributor PR (fork)** | ❌ **No** — the PR must only contain files under `cli-tool/components/` (plus supporting files). The catalog is regenerated automatically after merge (`update-json-data.yml` daily cron, or a maintainer). |\n\nWhy: the generated JSON files are single-line blobs that change on every\ncomponent/download-count update, so two PRs that both commit them always\nconflict. `.github/workflows/generated-files-guard.yml` fails any\nnon-maintainer PR that touches them and posts revert instructions; the\n`component-pr-welcome.yml` bot also warns about it up front.\n\nTwo workflows regenerate them on `main`: `update-component-content.yml`\nruns on every push that touches `cli-tool/components/**` with\n`--skip-downloads` (content only, seconds) and then dispatches `deploy.yml`\n(its own push uses `GITHUB_TOKEN`, which never triggers other workflows, so\nwithout that the new catalog would not reach aitmpl.com), and `update-json-data.yml`\n(daily cron) refreshes the download counts by pulling the whole\n`component_downloads` table from Supabase (minutes) — that table has one\nrow per download, which is what makes a full run slow.\n\nWhen reviewing a contributor PR that includes these files, ask them to revert\nwith `git checkout origin/main -- docs/components.json dashboard/public/` rather\nthan resolving the conflict by hand.\n\n**Mods (`cli-tool/components/mods/`) are plugin directories, not `.md` files.** Creating one: `mods/{category}/{name}/` with `.claude-plugin/plugin.json`, `hooks/hooks.json`, the hooks-modules under `hooks/`, a `README.md`, optionally `commands/`, `types/` and `tests/`. Before review: `cd cli-tool/components/mods && npx -y -p typescript@5 tsc -p tsconfig.json` and `claude plugin validate cli-tool/components/mods/{category}/{name}`. The component-reviewer applies this checklist to a mod:\n- ✅ `plugin.json` parses, has `name` (= directory name), `description`, `license`, and `author`/`repository` (attribution for vendored code)\n- ✅ `hooks/hooks.json` has a non-empty `modules` list and every entry exists under `hooks/`\n- ✅ Modules import types only from `'claude-code'`, use relative imports, spell `$` as `$.noun.event(...)`, never shadow `h` in a surface module\n- ✅ Options are declared in `plugin.json` `userConfig` (string/number/boolean/directory/file; lists as comma-separated strings)\n- ✅ Guards deny with `{ deny }` without calling `next`; `.catch` where fail-closed matters\n- ✅ No secrets (keys only via options), no absolute paths, no `$.process.run` with a shell\n- ✅ README states the command, controls/options, the minimum Claude Code version and (for `games/`) the attribution\n\n**The component-reviewer agent checks:**\n- ✅ Valid YAML frontmatter and required fields\n- ✅ Proper kebab-case naming conventions\n- ✅ No hardcoded secrets (API keys, tokens, passwords)\n- ✅ Relative paths only (no absolute paths)\n- ✅ Supporting files exist (for hooks with scripts)\n- ✅ Clear, specific descriptions\n- ✅ Correct category placement\n- ✅ Security best practices\n\n**Example Usage:**\n```\n# After creating a new agent\nUse the component-reviewer agent to review cli-tool/components/agents/development-team/react-expert.md\n\n# Before committing hook changes\nUse the component-reviewer agent to review cli-tool/components/hooks/git/prevent-force-push.json\n\n# For PR reviews with multiple components\nUse the component-reviewer agent to review all modified components in cli-tool/components/\n```\n\nThe agent will provide prioritized feedback:\n- **❌ Critical Issues**: Must fix before merge (security, missing fields)\n- **⚠️ Warnings**: Should fix (clarity, best practices)\n- **📋 Suggestions**: Nice to have improvements\n\n#### Skill Security Scanning (SkillSpector)\n\nSkills under `cli-tool/components/skills/**` are scanned for security\nvulnerabilities by [SkillSpector](https://github.com/NVIDIA/skillspector)\n(NVIDIA, Apache-2.0) — a static analyzer with 64 vulnerability patterns\n(prompt injection, data exfiltration, supply chain, dangerous code/AST, taint\ntracking, YARA signatures, etc.). It runs in static-only mode (`--no-llm`), so\nno API key or secret is required.\n\nTwo GitHub Actions drive it, both via the batch orchestrator\n`scripts/skillspector_scan.py`:\n\n- **`.github/workflows/skill-security-scan.yml`** (PR) — scans only the skills\n  changed in the PR (`git diff`), posts an idempotent report comment, and\n  **blocks** the check if any changed skill scores HIGH/CRITICAL (risk score\n  > 50). Uploads an aggregated SARIF to the Security tab.\n- **`.github/workflows/skill-security-scan-all.yml`** (weekly + manual) — scans\n  all skills, reports to the run summary and SARIF, and **never blocks**.\n\n**Catalog traffic light (every component type).** `generate_components_json.py`\nalso runs SkillSpector on every component through `scripts/skillspector_catalog.py`\nand stores a `skillspector` object (score, severity, recommendation, top 5\nfindings, version, file hash) on each entry of `components.json`. The dashboard\nshows a neutral \"Scanned by NVIDIA SkillSpector\" seal on cards and component pages\n(`SkillSpectorBadge.tsx`) and an informative report on the component page\n(`SkillSpectorReport.tsx`, `#security-scan`). Daniel's call (2026-10-21): no\ntraffic light, no red, no severity labels or install verdicts. It is\ndisplay-only: never use it for ordering, search or ranking. Results are cached in the previous\n`docs/components.json` by file hash + SkillSpector version, so a run only scans\nnew or changed components; bumping the pinned tag in `update-component-content.yml`\n/ `update-json-data.yml` rescans everything (~1 h). Without the `skillspector`\nCLI on PATH the previous results are kept. Agents, commands, hooks, MCPs and\nsettings are wrapped in a temporary SKILL.md (SkillSpector requires one), which\nis why JSON components with a script get an \"MCP Least Privilege / no tool scope\"\nfinding. Flags: `--skip-skillspector`, `--skillspector-only TYPE/PATH ...`.\n\nSkillSpector requires Python 3.12+ and is installed from NVIDIA's `main`\nbranch (`pip install git+https://github.com/NVIDIA/skillspector.git@main`); it\nis not published to PyPI. Risk bands: 0-20 LOW, 21-50 MEDIUM, 51-80 HIGH,\n81-100 CRITICAL.\n\n#### Statuslines with Python Scripts\n\nStatuslines can reference Python scripts that are auto-downloaded to `.claude/scripts/`:\n\n```javascript\n// In src/index.js:installIndividualSetting()\nif (settingName.includes('statusline/')) {\n  const pythonFileName = settingName.split('/')[1] + '.py';\n  const pythonUrl = githubUrl.replace('.json', '.py');\n  additionalFiles['.claude/scripts/' + pythonFileName] = {\n    content: pythonContent,\n    executable: true\n  };\n}\n```\n\n### Publishing Workflow\n\n```bash\n# 1. Update component catalog\npython scripts/generate_components_json.py\n\n# 2. Run tests\nnpm test\n\n# 3. Check current npm version, then create the synchronized version commit and tag\nnpm view claude-code-templates version  # check latest on registry\nnpm version X.Y.Z --ignore-scripts=false  # X.Y.Z = one patch above the registry version\nnpm run check:version-sync\n\n# 4. Push the version commit and tag created by npm version\ngit push origin main --follow-tags\n\n# 5. Publish to npm (requires granular access token with \"Bypass 2FA\" enabled)\nnpm config set //registry.npmjs.org/:_authToken=YOUR_GRANULAR_TOKEN\nnpm publish --ignore-scripts=false\nnpm config delete //registry.npmjs.org/:_authToken  # always clean up after\n\n# 6. Deploy website (dashboard on Cloudflare Pages)\n# Automatic on push to main (GitHub Actions). Manual: from dashboard/ run `npm run deploy`\n```\n\n**npm Publishing Notes:**\n- Classic npm tokens were revoked Dec 2025. Use **granular access tokens** from [npmjs.com/settings/~/tokens](https://www.npmjs.com/settings/~/tokens)\n- The token must have **Read and Write** permissions for `claude-code-templates` and **\"Bypass 2FA\"** enabled\n- Always remove the token from npm config after publishing (`npm config delete`)\n- The repository disables lifecycle scripts by default for security. Pass `--ignore-scripts=false` only to the trusted release commands shown above so their version and prepublish hooks can run.\n- The local `package.json` version may drift from npm if published from CI — always check `npm view claude-code-templates version` first\n- Never hardcode or commit tokens\n\n## API Architecture\n\n### Critical Endpoints\n\nAPI endpoints live as Astro API routes in `dashboard/src/pages/api/`:\n\n**`/api/track-download-supabase`** (CRITICAL)\n- Tracks component downloads for analytics\n- Used by CLI on every installation\n- Database: Supabase (component_downloads table)\n\n**`/api/discord/interactions`**\n- Discord bot slash commands\n- Features: /search, /info, /install, /popular\n\n**`/api/claude-code-check`**\n- Monitors Claude Code releases\n- Triggered every 30 minutes by the `cloudflare-workers/crons` Worker (not a Vercel cron)\n- Database: Neon (claude_code_versions, claude_code_changes, discord_notifications_log, monitoring_metadata tables)\n\n### Shared API Libraries\n\n- `dashboard/src/lib/api/cors.ts` — CORS headers, `corsResponse()`, `jsonResponse()`\n- `dashboard/src/lib/api/neon.ts` — Neon client factory\n- `dashboard/src/lib/api/auth.ts` — Clerk JWT verification\n- `dashboard/src/lib/api/changelog-parser.ts` — Claude Code changelog parser\n\n### Emergency Rollback\n\n```bash\n# List recent Pages deployments\nnpx wrangler pages deployment list --project-name=aitmpl-dashboard\n# Roll back to a previous deployment\nnpx wrangler pages deployment rollback <deployment-id> --project-name=aitmpl-dashboard\n```\n\n## Cloudflare Workers\n\nThe `cloudflare-workers/` directory contains Cloudflare Worker projects that run independently from the dashboard Pages project.\n\n### crons\n\nReplaces the old Vercel cron jobs. On a schedule it calls the dashboard API endpoints (which stay on Cloudflare Pages) with a shared `TRIGGER_SECRET`.\n\n- `*/30 * * * *` → `/api/claude-code-check` (monitors Claude Code npm releases)\n- `0 * * * *` → `/api/health-check` (hourly; was every 15 min on Vercel, reduced to save invocations)\n\nErrors and cron check-ins are reported to Sentry (`sentry.js`, DSN from the `aitmpl-workers` project — see Error Tracking below).\n\n```bash\ncd cloudflare-workers/crons\nnpm run dev          # Local dev\nnpx wrangler deploy  # Deploy\n```\n\n**Secrets (Cloudflare):** `DASHBOARD_URL` (e.g. `https://www.aitmpl.com`), `TRIGGER_SECRET`, `SENTRY_DSN` (optional).\n\n### docs-monitor (DECOMMISSIONED 2026-07)\n\nMonitored https://code.claude.com/docs hourly with Telegram notifications. **Deleted from Cloudflare** to free a cron-trigger slot for the newsletter worker (the account's free plan allows 5 cron triggers total). The code remains in `cloudflare-workers/docs-monitor/` and can be redeployed if a slot frees up (`npx wrangler deploy`).\n\n### pulse (Weekly KPI Report)\n\nCollects metrics from GitHub, Discord, Supabase, npm, and Google Analytics every Sunday at 14:00 UTC and sends a consolidated report via Telegram.\n\n**Architecture:** Single `index.js` file (no npm dependencies at runtime). All source collectors, formatter, and Telegram sender in one file.\n\n**Cron:** `0 14 * * 0` (Sundays 14:00 UTC / 11:00 AM Chile)\n\n```bash\ncd cloudflare-workers/pulse\nnpm run dev          # Local dev\nnpx wrangler deploy  # Deploy\n\n# Manual trigger\ncurl -X POST https://pulse-weekly-report.SUBDOMAIN.workers.dev/trigger \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n\n# Test single source\ncurl -X POST \"https://pulse-weekly-report.SUBDOMAIN.workers.dev/trigger?source=github\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n\n# Dry run (no Telegram)\ncurl -X POST \"https://pulse-weekly-report.SUBDOMAIN.workers.dev/trigger?send=false\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n```\n\n**Secrets (Cloudflare):**\n```bash\nTELEGRAM_BOT_TOKEN          # Shared with docs-monitor\nTELEGRAM_CHAT_ID            # Shared with docs-monitor\nGITHUB_TOKEN                # GitHub PAT (public_repo scope)\nSUPABASE_URL                # Supabase project URL\nSUPABASE_SERVICE_ROLE_KEY   # Supabase service role key\nDISCORD_BOT_TOKEN           # Discord bot token\nDISCORD_GUILD_ID            # Discord server ID\nTRIGGER_SECRET              # For manual /trigger endpoint\nGA_PROPERTY_ID              # GA4 property ID (optional)\nGA_SERVICE_ACCOUNT_JSON     # Base64 service account (optional)\n```\n\n**Graceful degradation:** Each source catches its own errors. Missing secrets or API failures show `⚠️ Unavailable` instead of crashing the report. Failed collectors are also reported to Sentry via `sentry.js` (see Error Tracking below). The Vercel collector was removed (2026-07) since the dashboard no longer deploys to Vercel.\n\n### newsletter (Weekly Community Components Email) — PAUSED 2026-09-20\n\nComposes and sends a simple weekly email via Resend featuring trending components (one Skill, Agent, MCP, Hook and Setting per send, in that fixed order). Selection is weighted-random by recent downloads and the copy (subject, catalog intro, per-component sentences, stats cited, closer) rotates from pools so no two emails read the same. Body is plain text plus a minimal HTML version (bold + underlined component titles, clickable component links). Data comes from the live `trending-data.json` + `components.json`.\n\n**Delivery:** Resend **Broadcast** targeting the segment in `RESEND_SEGMENT_ID` — Resend injects the per-recipient unsubscribe link (`{{{RESEND_UNSUBSCRIBE_URL}}}` placeholder in the body) and manages the suppression list automatically. Replies go to `NEWSLETTER_REPLY_TO`. The segment is the safety gate: point it at a pilot segment for tests or the full-audience segment for community-wide sends. Open/click tracking is enabled on the `aitmpl.com` domain with tracking subdomain `track.aitmpl.com` (metrics per broadcast at resend.com/broadcasts). **The weekly send is paused** (2026-09-20) because the emails were reading as spam; the worker stays deployed and only the automatic send is off. `wrangler.toml` has `crons = []` (was `0 16 * * SUN`, a slot freed by decommissioning docs-monitor) and `NEWSLETTER_ENABLED = \"false\"` makes `scheduled()` a no-op, so restoring the trigger alone does not resume sending. Repo edits do not reach production: no GitHub Action deploys this worker, so a pause or a resume only lands via `npx wrangler deploy` from `cloudflare-workers/newsletter/`. `GET /preview?format=text` composes without sending and `POST /trigger` sends (`?send=false` for dry run) — both still work while paused.\n\n```bash\ncd cloudflare-workers/newsletter\nnpm run dev          # Local dev\nnpx wrangler deploy  # Deploy\n\n# Preview content without sending (repeat to see the copy rotate)\ncurl \"https://aitmpl-newsletter.SUBDOMAIN.workers.dev/preview?format=text\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n\n# Real send: creates + sends a Broadcast to the segment in RESEND_SEGMENT_ID\ncurl -X POST \"https://aitmpl-newsletter.SUBDOMAIN.workers.dev/trigger\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n```\n\n**Secrets (Cloudflare):** `RESEND_API_KEY` (full access — broadcasts/segments), `RESEND_SEGMENT_ID`, `NEWSLETTER_REPLY_TO`, `TRIGGER_SECRET`, `SENTRY_DSN` (optional). Public vars in `wrangler.toml [vars]`: `DASHBOARD_URL`, `RESEND_FROM_EMAIL` (`daniel.avila@aitmpl.com`), `NEWSLETTER_ENABLED` (currently `\"false\"` — the kill switch described above).\n\n## Error Tracking (Sentry)\n\nFree-tier Sentry, added to close the gap where automated cron/worker failures\nwere previously invisible. No official `@sentry/*` SDK is used anywhere —\nevery surface has its own tiny dependency-free client that posts directly to\nthe Sentry envelope API via `fetch()`, matching this repo's zero-dependency\nworker style and avoiding Cloudflare Pages SSR friction with `@sentry/astro`.\n\n**Status as of 2026-07-04: all 3 projects live and verified end-to-end** (each\nconfirmed with a manual test event returning HTTP 200 from Sentry and\nappearing in its Issues dashboard).\n\n- ✅ **Cloudflare Workers** (Sentry project `aitmpl-workers`) — `SENTRY_DSN`\n  secret set on all 3 workers (`aitmpl-crons`, `pulse-weekly-report`,\n  `claude-docs-monitor`) via `wrangler secret put SENTRY_DSN`.\n- ✅ **Dashboard** (Sentry project `aitmpl-dashboard`) — `SENTRY_DSN` set as a\n  Cloudflare Pages secret (`wrangler pages secret put SENTRY_DSN\n  --project-name=aitmpl-dashboard`). Wired into `captureApiError()` calls in\n  `claude-code-check`, `health-check`, and the three `track-*` endpoints.\n- ✅ **CLI** (Sentry project `aitmpl-cli`) — the DSN is public by design\n  (send-only, not a secret) and ships **hardcoded as the default** in\n  `cli-tool/src/error-reporting.js` (`DEFAULT_SENTRY_DSN`, overridable via\n  `CCT_SENTRY_DSN` for testing against a different project). Reporting\n  itself stays **opt-in**: requires the end user to set\n  `CCT_ERROR_REPORTING=true`, and always defers to the existing\n  `CCT_NO_TRACKING`/`CCT_NO_ANALYTICS`/`CI` opt-outs.\n\n**Not yet configured (any surface):** Sentry alert rules to Discord/Telegram,\nand Cron Monitors dashboards for the workers' scheduled check-ins (the\n`checkIn()` calls already send `in_progress`/`ok`/`error` events — a Monitor\njust needs to be created in the Sentry UI with matching slugs:\n`claude-code-check`, `health-check`, `pulse-weekly-report`, `docs-monitor`).\n\n**Files:** `cloudflare-workers/{crons,pulse,docs-monitor}/sentry.js` (workers),\n`dashboard/src/lib/api/error-tracking.ts` (dashboard), `cli-tool/src/error-reporting.js` (CLI).\n\n## Dashboard (www.aitmpl.com)\n\nAstro + React + Tailwind dashboard serving both `www.aitmpl.com` and `app.aitmpl.com`. Clerk auth for user collections. Source lives in `dashboard/`. All API endpoints are Astro API routes in the same project.\n\n### Architecture\n\n- **Framework**: Astro 5 with React islands, Tailwind v4, `output: 'server'`, `@astrojs/cloudflare` adapter (`mode: 'directory'`)\n- **Hosting**: Cloudflare Pages (project `aitmpl-dashboard`), SSR on Workers runtime\n- **Auth**: Clerk (`window.Clerk` global, no ClerkProvider per island)\n- **Data**: `components.json` and `trending-data.json` served from `dashboard/public/` (same-origin)\n- **APIs**: All endpoints in `dashboard/src/pages/api/` (Astro API routes, no separate serverless project)\n\n### Featured Pages (`/featured/[slug]`)\n\nFeatured partner integrations shown on the dashboard homepage. Two files to edit:\n\n**`dashboard/src/lib/constants.ts`** — `FEATURED_ITEMS` array. Each entry has:\n- `name`, `description`, `logo`, `url` (`/featured/slug`), `tag`, `tagColor`, `category`\n- `ctaLabel`, `ctaUrl`, `websiteUrl`\n- `installCommand` — shown in the sidebar Quick Install box\n- `metadata` — key/value pairs shown in the Details sidebar (e.g. `Components: '8'`)\n- `links` — sidebar links list\n\n**`dashboard/src/pages/featured/[slug].astro`** — Content for each slug rendered via `{slug === 'brightdata' && (...)}` blocks. Each block contains the full HTML content for that partner page.\n\n**When adding a skill to a featured page:**\n1. Add a new card `<div class=\"flex gap-3 ...\">` inside the Skills Layer section of the relevant `{slug === '...'}` block\n2. Update `installCommand` in `constants.ts` to include the new skill\n3. Increment `metadata.Components` count in `constants.ts`\n\nCurrent featured slugs: `brightdata`, `neon-instagres`, `claudekit`, `braingrid`\n\n### Cloudflare Pages Project Setup\n\nA single Cloudflare Pages project (`aitmpl-dashboard`) serves all domains. Config lives in `dashboard/wrangler.toml`:\n\n| Project | Domains | Root Directory | Build output |\n|---------|---------|----------------|--------------|\n| `aitmpl-dashboard` | `www.aitmpl.com`, `aitmpl.com` (redirect), `app.aitmpl.com` | `dashboard` | `dist` |\n\n`wrangler.toml` sets `pages_build_output_dir = \"./dist\"`, `compatibility_flags = [\"nodejs_compat\"]`, and the `PUBLIC_*` build-time vars in `[vars]`. Secrets are set via the Cloudflare Dashboard or `wrangler pages secret put`.\n\n### Deployment\n\n**ALWAYS use the deployer agent (`.claude/agents/deployer.md`) for all deployments.** It runs pre-deploy checks (auth, git status, build) and handles the full pipeline safely. Never deploy manually.\n\n```bash\nnpm run deploy             # Build + `wrangler pages deploy dist` for www + app.aitmpl.com\nnpm run deploy:dashboard   # Same as above\n```\n\n**CI/CD**: Pushes to `main` auto-deploy via GitHub Actions (`.github/workflows/deploy.yml`):\n- Changes in `dashboard/**` trigger a build and `wrangler pages deploy dist --project-name=aitmpl-dashboard`\n\n**Required GitHub Secrets** (Settings > Secrets > Actions):\n- `CLOUDFLARE_API_TOKEN` — Cloudflare API token with Pages edit permission\n- `CLOUDFLARE_ACCOUNT_ID` — Cloudflare account ID\n\n### Environment Variables (Cloudflare)\n\n`PUBLIC_*` vars are build-time and live in `dashboard/wrangler.toml` `[vars]` (and are also passed to the GitHub Actions build step). Everything else is a Cloudflare secret (`wrangler pages secret put <NAME>` or the Pages dashboard):\n\n```bash\n# Clerk\nPUBLIC_CLERK_PUBLISHABLE_KEY=xxx   # [vars] — build-time\nCLERK_SECRET_KEY=xxx               # secret\n\n# Data\nPUBLIC_COMPONENTS_JSON_URL=/components.json   # [vars] — build-time\n\n# GitHub OAuth\nPUBLIC_GITHUB_CLIENT_ID=xxx        # [vars] — build-time\nGITHUB_CLIENT_SECRET=xxx           # secret\n\n# Supabase (download tracking)\nSUPABASE_URL=https://xxx.supabase.co        # secret\nSUPABASE_SERVICE_ROLE_KEY=xxx               # secret\n\n# Neon Database\nNEON_DATABASE_URL=postgresql://user:pass@host/db?sslmode=require   # secret\n\n# Discord\nDISCORD_APP_ID=xxx                 # secret\nDISCORD_BOT_TOKEN=xxx              # secret\nDISCORD_PUBLIC_KEY=xxx             # secret\nDISCORD_WEBHOOK_URL_CHANGELOG=https://discord.com/api/webhooks/xxx   # secret\n```\n\n### Known Issues & Solutions\n\n**Node built-ins in SSR**\n- The Cloudflare Workers runtime does not expose Node's `fs`/`path`/etc. by default. `astro.config.mjs` enables `nodejs_compat` (via `wrangler.toml`) and externalizes `node:fs`, `node:path`, `node:url`, `node:stream` in SSR. Avoid adding new hard dependencies on Node-only APIs in server code.\n\n**`react-dom/server` on Cloudflare**\n- `astro.config.mjs` aliases `react-dom/server` to `react-dom/server.node` and marks `react-dom` as `noExternal` at build time so React SSR works on the Workers runtime. Don't remove this alias.\n\n### Local Development\n\n```bash\ncd dashboard\nnpm install\nnpx astro dev --port 4321   # Dashboard + APIs at http://localhost:4321\n```\n\n## Data Files\n\n### Component Catalog\n\n- `docs/components.json` — Full generated catalog (source of truth), keeps `content` and `security` fields (needed by the legacy static site)\n- `dashboard/public/components.json` — Dashboard copy, **without** `content`/`security` (lighter payload; dashboard doesn't need them)\n- `dashboard/public/counts.json` — Per-type counts only (e.g. `{\"agents\": 421, ...}`), used by the sidebar/plugins pages instead of loading the full catalog\n- `dashboard/public/components/{type}.json` — One file per component type (agents.json, commands.json, etc.), loaded on demand by `ComponentGrid.tsx` for the active tab\n- `dashboard/public/search-index.json` — Flat array for `SearchModal.tsx`\n- `dashboard/public/component-content/{type}/{slug}.json` — Full per-component content (incl. markdown body), fetched on demand when a component's detail view or PR flow needs it\n- `dashboard/public/trending-data.json` — Trending/download stats\n- `dashboard/public/api/v1/` — **Catalog API v1, the public contract** (see below)\n\nAll of the above are served as static Cloudflare Pages assets. The catalog\nfiles (`components.json`, `counts.json`, `components/*`, `search-index.json`,\n`component-content/*`) use `max-age=300, stale-while-revalidate=3600`, so a\ndeploy reaches browsers within minutes; `trending-data.json` keeps 1 h (see\n`dashboard/public/_headers`). With the old 24 h `max-age` a browser kept a\nstale `components/{type}.json` for a day after a deploy while `counts.json`\nwas already new: the sidebar counted a component the grid could not find.\n\n### Catalog API v1 (public contract)\n\nOutside code reads the catalog (known: `profullstack/niche-db` reads\n`docs/components.json` from raw.githubusercontent.com; our own\n`mods/productivity/aitmpl` reads `components/{type}.json`, `counts.json` and\n`trending-data.json` from www.aitmpl.com, and installed copies never update).\n`/api/v1/` is the versioned surface for them: `catalog.json`,\n`catalog/{type}.json`, `version.json` (sha256 for cheap polling) and\n`schema.json`, written by `scripts/catalog_api_v1.py` (called at the end of\n`generate_components_json.py`) from `scripts/catalog_api_v1.schema.json`.\nDocs page `/developers/catalog-api`; audit and design in\n`docs/catalog-api-formalization.md`.\n\n- **Within v1, changes are additive only.** Removing or renaming a field,\n  changing a type, the `id` format or a URL needs `/api/v2/` with v1 kept for 6\n  months. Record every format change in `docs/catalog-api/CHANGELOG.md`.\n- Adding a field: add it to the schema (`$defs.component`, also `required`),\n  bump `SCHEMA_VERSION` minor, add a changelog entry. The writer refuses\n  undeclared fields and validates every run (fatal in CI, where `jsonschema`\n  is installed); `catalog-api-v1.yml` runs the tests on PRs.\n- Never add sponsored/placement data to v1, and never order it by anything but `id`.\n- `generatedAt` only moves when the components changed, so a no-op run stays byte-identical.\n- The legacy files stay the site's own data, but treat their current shape as\n  frozen in practice (installed `/aitmpl` mods read them), and do not delete\n  `docs/components.json` in a legacy-site cleanup: niche-db reads it.\n\n### Data Flow\n\n1. `scripts/generate_components_json.py` scans `cli-tool/components/`\n2. Generates `docs/components.json` (full, with `content`/`security`) and the split dashboard artifacts (`dashboard/public/components.json`, `counts.json`, `components/{type}.json`, `search-index.json`, `component-content/{type}/{slug}.json`) — these two writes are decoupled, so the dashboard payload stays lean without touching the legacy catalog — then the Catalog API v1 files under `dashboard/public/api/v1/`\n3. Dashboard islands (`ComponentGrid.tsx`, `SearchModal.tsx`, `Sidebar.astro`, `SendToRepoModal.tsx`) load the split artifacts instead of the full catalog\n4. `scripts/generate_trending_data.py` writes only `docs/trending-data.json`; `update-json-data.yml` copies it to `dashboard/public/trending-data.json`, which is the one `TrendingView.tsx` and the home counters actually fetch\n5. Download tracking via `/api/track-download-supabase`\n\n### Plugins & Marketplaces Catalog\n\n- `scripts/generate_plugins_json.py` — scans the repos listed in `REPOS` via the `gh` CLI (needs `gh auth login`) and writes `dashboard/public/plugins.json`. This is a **manual, offline step** — it does not run during `npm run build` or CI/CD, so re-running it never affects deploy time.\n- For each marketplace it records `plugins_list[].components` (counts per type) and `plugins_list[].components_items` (`{name, description}` per command/agent/skill/hook/mcp/lsp, description parsed from the item's frontmatter). The dashboard's `/plugins/[slug].astro` page renders this through `MarketplacePluginsList.tsx`, which shows a search box and a \"view details\" modal per plugin.\n- **`max_local_scans = 50`** in `extract_marketplace_plugins_detail()` caps how many *locally-sourced* plugins (i.e. `source: \"./plugins/...\"` within the marketplace's own repo) get scanned for real component names/descriptions, per marketplace, to bound GitHub API calls. Plugins beyond that cap (or plugins hosted in an external repo, which are never scanned) fall back to showing only tag badges in the modal, with no itemized breakdown — this is a graceful degradation, not an error.\n  - As of 2026-07-11, `anthropics/claude-plugins-official` alone has 51 locally-sourced plugins (out of 255 total), i.e. already at the edge of this cap. Bump `max_local_scans` if more complete coverage is needed — GitHub's rate limit (5000 req/hour authenticated) is not the constraint, wall-clock run time is (each item now costs 1 extra API call to fetch its file content for the description).\n\n### Legacy Static Site (docs/)\n\nThe `docs/` directory contains the old static HTML site (no longer deployed to www). GitHub Pages still builds it from repository settings and publishes it at `davila7.github.io/claude-code-templates`; nothing in production depends on that, and every article canonicalises to `aitmpl.com`. Exception: `docs/components.json` is read by external code from raw.githubusercontent.com (see Catalog API v1), so keep generating it. Blog articles in `docs/blog/` are still referenced externally.\n\n### Blog Article Creation\n\nUse the CLI skill to create blog articles:\n\n```bash\n/create-blog-article @cli-tool/components/{type}/{category}/{name}.json\n```\n\nThis automatically:\n1. Generates AI cover image\n2. Creates HTML with SEO optimization\n3. Updates `docs/blog/blog-articles.json`\n\n**The site serves `dashboard/public/blog/`, not `docs/blog/`.** The skill writes\nonly the `docs/` copy, so after creating an article you must mirror it across —\nthe article directory, its cover under `assets/`, and the new entry in\n`blog-articles.json` — or it never appears on aitmpl.com. Three articles were\nstranded this way before 2026-09-20. `daily-blog-discord.yml` picks from\n`docs/blog/blog-articles.json` and links to `aitmpl.com/blog/<slug>/`, so an\nunmirrored article is posted to Discord as a 404. Keep the two\n`blog-articles.json` files identical.\n\n## Code Standards\n\n### Path Handling\n- Use relative paths: `.claude/scripts/`, `.claude/hooks/`\n- Never hardcode absolute paths or home directories\n- Use `path.join()` for cross-platform compatibility\n\n### Naming Conventions\n- Files: `kebab-case.js`, `PascalCase.js` (for classes)\n- Functions/Variables: `camelCase`\n- Constants: `UPPER_SNAKE_CASE`\n- Components: `hyphenated-names`\n\n### Error Handling\n- Use try/catch for async operations\n- Provide helpful error messages\n- Log errors with context\n- Implement fallback mechanisms\n\n## Testing\n\n```bash\nnpm test                 # Run all tests\nnpm run test:watch      # Watch mode\nnpm run test:coverage   # Coverage report\n```\n\nAim for 70%+ test coverage. Test critical paths and error handling.\n\n## Common Issues\n\n**API endpoint returns 404 after deploy**\n- API routes must be in `dashboard/src/pages/api/` as Astro API routes\n- Export named HTTP methods: `export const POST: APIRoute`, `export const GET: APIRoute`\n\n**Download tracking not working**\n- Check Cloudflare Pages logs: `npx wrangler pages deployment tail --project-name=aitmpl-dashboard`\n- Verify environment variables / secrets in the Cloudflare Pages dashboard\n- Test endpoint manually with curl\n\n**Components not updating on website**\n- Run `python scripts/generate_components_json.py` (writes both `docs/components.json` and the split `dashboard/public/` artifacts directly — no manual copy step)\n- Deploy, then wait up to 5 min or hard-reload (catalog JSON is browser-cached 5 min, see `dashboard/public/_headers`)\n\n## Important Notes\n\n- **Component catalog**: Always regenerate after adding/modifying components\n- **API tests**: Required before production deploy (breaks download tracking)\n- **Secrets**: Never commit API keys (use environment variables)\n- **Paths**: Use relative paths for all project files\n- **Backwards compatibility**: Don't break existing component installations\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/.claude-plugin/plugin.json)"},"files":{"CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code when working with this repository.\n\n## Project Overview\n\nNode.js CLI tool for managing Claude Code components (agents, commands, MCPs, hooks, settings) with a static website for browsing and installing components. The dashboard and its API routes are deployed on Cloudflare Pages, with supporting cron and monitoring tasks running as Cloudflare Workers.\n\n## Essential Commands\n\n```bash\n# Development\nnpm install                    # Install dependencies\nnpm test                       # Run tests\nnpm version X.Y.Z --ignore-scripts=false  # Sync, commit, and tag all package versions\nnpm run version:set -- X.Y.Z   # Sync versions without creating a commit or tag\nnpm publish --ignore-scripts=false  # Publish and run the trusted prepublish guard\n\n# Component catalog\npython scripts/generate_components_json.py                   # Update docs/components.json (pulls download counts from Supabase: minutes)\npython scripts/generate_components_json.py --skip-downloads  # Same, keeping the counts already in the catalog: seconds\n\n# Dashboard + API (Astro on Cloudflare Pages)\ncd dashboard && npm run build  # Build before deploy\nnpm run deploy                 # Deploy www + app.aitmpl.com via wrangler\n```\n\n> Deploys to production happen automatically via GitHub Actions on push to `main`\n> (changes in `dashboard/**`). Manual deploy uses `wrangler pages deploy`, not Vercel.\n\n## Security Guidelines\n\n### ⛔ CRITICAL: NEVER Hardcode Secrets or IDs\n\n**NEVER write API keys, tokens, passwords, project IDs, org IDs, or any identifier in code.** This includes Cloudflare account/project IDs, Supabase URLs, Discord IDs, database connection strings, and any other infrastructure identifier. ALL must go in `.env` (or Cloudflare secrets via `wrangler secret put`).\n\n```javascript\n// ❌ WRONG\nconst API_KEY = \"AIzaSy...\";\n\n// ✅ CORRECT\nconst API_KEY = process.env.GOOGLE_API_KEY;\n```\n\n**When creating scripts with API keys:**\n1. Use `process.env` (Node.js) or `os.environ.get()` (Python)\n2. Load from `.env` file using `dotenv`\n3. Add variable to `.env.example` with placeholder\n4. Verify `.env` is in `.gitignore`\n\n**If you accidentally commit a secret:**\n1. Revoke the key IMMEDIATELY\n2. Generate new key\n3. Update `.env`\n4. Old key is compromised forever (git history)\n\n## Component System\n\n### Component Types\n\n**Agents** (600+) - AI specialists for development tasks\n**Commands** (200+) - Custom slash commands for workflows\n**MCPs** (55+) - External service integrations\n**Settings** (60+) - Claude Code configuration files\n**Hooks** (39+) - Automation triggers\n**Loops** (18+) - Autonomous agentic workflows (goal + interval + stop condition) that reference other components\n**Mods** (10) - Claude Mods: plugins whose behaviour lives in a function-hooks module (`register(on, options)` hooking engine events as `($, e, next)` middleware). Anthropic's reference is [anthropics/claude-code/mods](https://github.com/anthropics/claude-code/tree/main/mods) (three built-in mods + `mods/types/claude-code.d.ts`); discussion in [anthropics/claude-code#91870](https://github.com/anthropics/claude-code/issues/91870). Mods are official and on by default in Claude Code >= 2.1.287 ([docs](https://code.claude.com/docs/en/plugins/mods/overview)); 2.1.287+ ignores `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS`, builds 2.1.259–2.1.286 still need it set to `1`; the `$` API may change between releases. Each mod is a complete plugin directory in Anthropic's `mods/` layout: `cli-tool/components/mods/{category}/{name}/` with `.claude-plugin/plugin.json` (name, description, `userConfig` — options are read from user/managed settings `pluginConfigs[name].options`, never project settings), `hooks/hooks.json` (`modules`), any number of hooks-modules under `hooks/` (relative imports allowed), optional `commands/*.md` (slash commands the plugin ships; a `skill.prompt` hook may rewrite their prompt at run time), `types/`, `tests/`, and a `README.md` the site shows. The generator uses README.md as content and ships every text file in the per-component content file (`files`), so the site explorer and the send-to-repo flow have the whole plugin. `--mod` (alias `--function-hook`) downloads the directory recursively (like a skill) and writes it verbatim to `.claude/skills/{name}/`, which Claude Code auto-loads as `{name}@skills-dir` — **only in a trusted project** (see \"Debugging a mod that seems silent\" below). Third-party mods are vendored as-is with LICENSE + attribution (e.g. `games/cc-arcade`). **Every module must typecheck against `cli-tool/components/mods/types/claude-code.d.ts`** (`cd cli-tool/components/mods && npx -y -p typescript@5 tsc -p tsconfig.json`; CI runs it in `mods-typecheck.yml`). Rules from the engine: import types only from `'claude-code'`, spell `$` as `$.noun.event(...)` at the call site (never pass `$` to a helper), treat `e` as frozen, deny with `{ deny }` without calling `next` (returning `{}` is fail-open). When Anthropic bumps the API, regenerate the d.ts with `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude -p \"/plugin-types <dir>\"` on a current Claude Code and re-run tsc (the header's first line names the writing version; currently 2.1.301; 2.1.278 added `prompt.attachment` — the per-attachment hook `jev-skill-suggestion` uses to withhold the `skill_listing` system-reminder — and 2.1.283 changed `$.model.complete` from a string and `$.model.fork` from `result | null` to `{ isAnswered, text, usage } | { isAnswered: false, reason }`). Note `/context`'s \"Skills\" row is estimated from the roster and does not reflect that hook; a mod can ship `commands/*.md` and rewrite their prompt in `skill.prompt` (that is how `/jev-skill-suggestion:setup` hides every skill as `user-invocable-only` while the mod injects the chosen `SKILL.md` itself). Typechecking against the d.ts does not prove an older engine draws a mod: Claude Code 2.1.287 skips the whole `ui.render` of a Button with Text children instead of a `label` string, and the Claude Desktop app may run an older bundled build than your terminal: `ultra-config` drew in a 2.1.301 terminal and stayed empty in Desktop with exactly that skip. Test a pane on the oldest supported engine too: `npm i @anthropic-ai/claude-code@2.1.287` in a scratch folder, then run its `claude plugin test {category}/{name}` from `cli-tool/components/mods`. The early-access banner and Beta badge were removed 2026-10-04 (mods are official). Old URLs (`/function-hooks`, `/component/function-hook/*`) redirect via `dashboard/public/_redirects`.\n**Templates** (14+) - Complete project configurations\n\n#### Debugging a mod that seems silent (verified 2026-09-19 on Claude Code 2.1.278)\n\nA mod that \"produces no output\" has, in order of likelihood, these causes — none of which is the mod's code. Check them before touching the module:\n\n1. **The project is not trusted.** Claude Code adopts a plugin from a project's `.claude/skills/{name}/` only when `~/.claude.json` has `projects[<path>].hasTrustDialogAccepted: true`; an untrusted folder's `.claude/` is repository content and is not read at all (`plugin.scope===\"project\"` candidates are dropped and re-discovered \"post-trust\"). `claude -p` never shows the trust prompt, so a headless run in a fresh folder never loads a project mod. Fix: open `claude` interactively in the folder and accept the prompt, or pass `--plugin-dir .claude/skills/{name}` (explicit, loads as `{name}@inline`). `~/.claude/skills/{name}/` (user-level) always loads.\n2. **You are looking in the wrong place.** `$.ui.log` appends a dim transcript line and writes to the debug log; `$.ui.status` pins a line under the prompt. In `claude -p` / the SDK there is no transcript and no status row: the lines are only in `~/.claude/debug/<session-id>.txt` (`.txt`, not `.log`; `latest` symlinks the newest) and reach an SDK host as `ui_log`. The header, statusline and effort box show the *session's* settings and never move: a mod rewrites `model`/`effort` per *request*. The other observable is `\"effort\":\"…\"` in the transcript JSONL under `~/.claude/projects/<slug>/`.\n3. **Options under the wrong key.** `pluginConfigs` is keyed by the plugin's full id: `\"{name}@skills-dir\"` when auto-loaded, `\"{name}\"` with `--plugin-dir`. Under the wrong key every option is its default and the debug log says `plugin {name}: no pluginConfigs[\"{name}@skills-dir\"].options in user, --settings or managed settings`.\n4. **Function hooks off.** On Claude Code < 2.1.287 with `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS` unset/`0` the debug log says `installed plugins' hooks modules not loaded: rollout flag (tengu_plugin_hooks_modules) is off`. Built-in mods (`agents-md@builtin`) load regardless, so their presence proves nothing about ours.\n\nThe lines that settle each question in `claude --debug`: `Found N plugins`, `Read hooks.json for plugin {name}`, `hooks module {name}@skills-dir loaded (worker, environment N, tier user); events: …`, then `hooks module {name}@… <event> settled in Xms`. Positive control: `pacman@skills-dir` loads from `.claude/skills/` in the trusted `claude-code-templates` checkout. Anthropic documents only `claude --plugin-dir mods/<name>` for its reference mods; the `.claude/skills` auto-load is Claude Code 2.1.157+ (CHANGELOG: \"Plugins in `.claude/skills` directories are now automatically loaded\").\n\n### Installation Patterns\n\n```bash\n# Single component\nnpx claude-code-templates@latest --agent frontend-developer\nnpx claude-code-templates@latest --command setup-testing\nnpx claude-code-templates@latest --hook automation/simple-notifications\nnpx claude-code-templates@latest --loop engineering/docs-sweep-loop  # also installs the loop's referenced components\nnpx claude-code-templates@latest --mod security/secret-redactor          # Claude Mod: plugin at .claude/skills/secret-redactor/\n\n# Batch installation\nnpx claude-code-templates@latest --agent security-auditor --command security-audit --setting read-only-mode\n\n# Interactive mode\nnpx claude-code-templates@latest\n```\n\n### Component Development\n\n#### Adding New Components\n\n**CRITICAL: Use the component-reviewer agent for ALL component changes**\n\nWhen adding or modifying components, you MUST use the `component-reviewer` subagent to validate the component before committing:\n\n```\nUse the component-reviewer agent to review [component-path]\n```\n\n**Component Creation Workflow:**\n\n1. Create component file in `cli-tool/components/{type}/{category}/{name}.md`\n2. Use descriptive hyphenated names (kebab-case)\n3. Include clear descriptions and usage examples\n4. **REVIEW with component-reviewer agent** (validates format, security, naming)\n5. Fix any issues identified by the reviewer\n6. **TEST before generating/publishing**: ask the human in the session whether\n   they want to test the newly created/modified component(s) first. Do NOT run\n   `generate_components_json.py`, commit, or publish until testing is confirmed\n   or explicitly skipped by the user.\n7. Run `python scripts/generate_components_json.py` to update catalog\n   (**maintainers only** — see below)\n\n#### Generated catalog files: who regenerates them\n\n`scripts/generate_components_json.py` writes `docs/components.json` and the\nsplit `dashboard/public/` artifacts (`components.json`, `counts.json`,\n`search-index.json`, `components/*.json`, `component-content/**`, `api/v1/**`). These files\nare **generated output, never hand-edited**, and who commits them depends on\nthe workflow:\n\n| Workflow | Regenerate + commit the catalog? |\n|----------|----------------------------------|\n| Maintainer working directly on this repo (local branch, sync PRs, agent-driven migrations) | ✅ Yes — run the script (`--skip-downloads` is enough for a content change) and commit the output with the component |\n| **External contributor PR (fork)** | ❌ **No** — the PR must only contain files under `cli-tool/components/` (plus supporting files). The catalog is regenerated automatically after merge (`update-json-data.yml` daily cron, or a maintainer). |\n\nWhy: the generated JSON files are single-line blobs that change on every\ncomponent/download-count update, so two PRs that both commit them always\nconflict. `.github/workflows/generated-files-guard.yml` fails any\nnon-maintainer PR that touches them and posts revert instructions; the\n`component-pr-welcome.yml` bot also warns about it up front.\n\nTwo workflows regenerate them on `main`: `update-component-content.yml`\nruns on every push that touches `cli-tool/components/**` with\n`--skip-downloads` (content only, seconds) and then dispatches `deploy.yml`\n(its own push uses `GITHUB_TOKEN`, which never triggers other workflows, so\nwithout that the new catalog would not reach aitmpl.com), and `update-json-data.yml`\n(daily cron) refreshes the download counts by pulling the whole\n`component_downloads` table from Supabase (minutes) — that table has one\nrow per download, which is what makes a full run slow.\n\nWhen reviewing a contributor PR that includes these files, ask them to revert\nwith `git checkout origin/main -- docs/components.json dashboard/public/` rather\nthan resolving the conflict by hand.\n\n**Mods (`cli-tool/components/mods/`) are plugin directories, not `.md` files.** Creating one: `mods/{category}/{name}/` with `.claude-plugin/plugin.json`, `hooks/hooks.json`, the hooks-modules under `hooks/`, a `README.md`, optionally `commands/`, `types/` and `tests/`. Before review: `cd cli-tool/components/mods && npx -y -p typescript@5 tsc -p tsconfig.json` and `claude plugin validate cli-tool/components/mods/{category}/{name}`. The component-reviewer applies this checklist to a mod:\n- ✅ `plugin.json` parses, has `name` (= directory name), `description`, `license`, and `author`/`repository` (attribution for vendored code)\n- ✅ `hooks/hooks.json` has a non-empty `modules` list and every entry exists under `hooks/`\n- ✅ Modules import types only from `'claude-code'`, use relative imports, spell `$` as `$.noun.event(...)`, never shadow `h` in a surface module\n- ✅ Options are declared in `plugin.json` `userConfig` (string/number/boolean/directory/file; lists as comma-separated strings)\n- ✅ Guards deny with `{ deny }` without calling `next`; `.catch` where fail-closed matters\n- ✅ No secrets (keys only via options), no absolute paths, no `$.process.run` with a shell\n- ✅ README states the command, controls/options, the minimum Claude Code version and (for `games/`) the attribution\n\n**The component-reviewer agent checks:**\n- ✅ Valid YAML frontmatter and required fields\n- ✅ Proper kebab-case naming conventions\n- ✅ No hardcoded secrets (API keys, tokens, passwords)\n- ✅ Relative paths only (no absolute paths)\n- ✅ Supporting files exist (for hooks with scripts)\n- ✅ Clear, specific descriptions\n- ✅ Correct category placement\n- ✅ Security best practices\n\n**Example Usage:**\n```\n# After creating a new agent\nUse the component-reviewer agent to review cli-tool/components/agents/development-team/react-expert.md\n\n# Before committing hook changes\nUse the component-reviewer agent to review cli-tool/components/hooks/git/prevent-force-push.json\n\n# For PR reviews with multiple components\nUse the component-reviewer agent to review all modified components in cli-tool/components/\n```\n\nThe agent will provide prioritized feedback:\n- **❌ Critical Issues**: Must fix before merge (security, missing fields)\n- **⚠️ Warnings**: Should fix (clarity, best practices)\n- **📋 Suggestions**: Nice to have improvements\n\n#### Skill Security Scanning (SkillSpector)\n\nSkills under `cli-tool/components/skills/**` are scanned for security\nvulnerabilities by [SkillSpector](https://github.com/NVIDIA/skillspector)\n(NVIDIA, Apache-2.0) — a static analyzer with 64 vulnerability patterns\n(prompt injection, data exfiltration, supply chain, dangerous code/AST, taint\ntracking, YARA signatures, etc.). It runs in static-only mode (`--no-llm`), so\nno API key or secret is required.\n\nTwo GitHub Actions drive it, both via the batch orchestrator\n`scripts/skillspector_scan.py`:\n\n- **`.github/workflows/skill-security-scan.yml`** (PR) — scans only the skills\n  changed in the PR (`git diff`), posts an idempotent report comment, and\n  **blocks** the check if any changed skill scores HIGH/CRITICAL (risk score\n  > 50). Uploads an aggregated SARIF to the Security tab.\n- **`.github/workflows/skill-security-scan-all.yml`** (weekly + manual) — scans\n  all skills, reports to the run summary and SARIF, and **never blocks**.\n\n**Catalog traffic light (every component type).** `generate_components_json.py`\nalso runs SkillSpector on every component through `scripts/skillspector_catalog.py`\nand stores a `skillspector` object (score, severity, recommendation, top 5\nfindings, version, file hash) on each entry of `components.json`. The dashboard\nshows a neutral \"Scanned by NVIDIA SkillSpector\" seal on cards and component pages\n(`SkillSpectorBadge.tsx`) and an informative report on the component page\n(`SkillSpectorReport.tsx`, `#security-scan`). Daniel's call (2026-10-21): no\ntraffic light, no red, no severity labels or install verdicts. It is\ndisplay-only: never use it for ordering, search or ranking. Results are cached in the previous\n`docs/components.json` by file hash + SkillSpector version, so a run only scans\nnew or changed components; bumping the pinned tag in `update-component-content.yml`\n/ `update-json-data.yml` rescans everything (~1 h). Without the `skillspector`\nCLI on PATH the previous results are kept. Agents, commands, hooks, MCPs and\nsettings are wrapped in a temporary SKILL.md (SkillSpector requires one), which\nis why JSON components with a script get an \"MCP Least Privilege / no tool scope\"\nfinding. Flags: `--skip-skillspector`, `--skillspector-only TYPE/PATH ...`.\n\nSkillSpector requires Python 3.12+ and is installed from NVIDIA's `main`\nbranch (`pip install git+https://github.com/NVIDIA/skillspector.git@main`); it\nis not published to PyPI. Risk bands: 0-20 LOW, 21-50 MEDIUM, 51-80 HIGH,\n81-100 CRITICAL.\n\n#### Statuslines with Python Scripts\n\nStatuslines can reference Python scripts that are auto-downloaded to `.claude/scripts/`:\n\n```javascript\n// In src/index.js:installIndividualSetting()\nif (settingName.includes('statusline/')) {\n  const pythonFileName = settingName.split('/')[1] + '.py';\n  const pythonUrl = githubUrl.replace('.json', '.py');\n  additionalFiles['.claude/scripts/' + pythonFileName] = {\n    content: pythonContent,\n    executable: true\n  };\n}\n```\n\n### Publishing Workflow\n\n```bash\n# 1. Update component catalog\npython scripts/generate_components_json.py\n\n# 2. Run tests\nnpm test\n\n# 3. Check current npm version, then create the synchronized version commit and tag\nnpm view claude-code-templates version  # check latest on registry\nnpm version X.Y.Z --ignore-scripts=false  # X.Y.Z = one patch above the registry version\nnpm run check:version-sync\n\n# 4. Push the version commit and tag created by npm version\ngit push origin main --follow-tags\n\n# 5. Publish to npm (requires granular access token with \"Bypass 2FA\" enabled)\nnpm config set //registry.npmjs.org/:_authToken=YOUR_GRANULAR_TOKEN\nnpm publish --ignore-scripts=false\nnpm config delete //registry.npmjs.org/:_authToken  # always clean up after\n\n# 6. Deploy website (dashboard on Cloudflare Pages)\n# Automatic on push to main (GitHub Actions). Manual: from dashboard/ run `npm run deploy`\n```\n\n**npm Publishing Notes:**\n- Classic npm tokens were revoked Dec 2025. Use **granular access tokens** from [npmjs.com/settings/~/tokens](https://www.npmjs.com/settings/~/tokens)\n- The token must have **Read and Write** permissions for `claude-code-templates` and **\"Bypass 2FA\"** enabled\n- Always remove the token from npm config after publishing (`npm config delete`)\n- The repository disables lifecycle scripts by default for security. Pass `--ignore-scripts=false` only to the trusted release commands shown above so their version and prepublish hooks can run.\n- The local `package.json` version may drift from npm if published from CI — always check `npm view claude-code-templates version` first\n- Never hardcode or commit tokens\n\n## API Architecture\n\n### Critical Endpoints\n\nAPI endpoints live as Astro API routes in `dashboard/src/pages/api/`:\n\n**`/api/track-download-supabase`** (CRITICAL)\n- Tracks component downloads for analytics\n- Used by CLI on every installation\n- Database: Supabase (component_downloads table)\n\n**`/api/discord/interactions`**\n- Discord bot slash commands\n- Features: /search, /info, /install, /popular\n\n**`/api/claude-code-check`**\n- Monitors Claude Code releases\n- Triggered every 30 minutes by the `cloudflare-workers/crons` Worker (not a Vercel cron)\n- Database: Neon (claude_code_versions, claude_code_changes, discord_notifications_log, monitoring_metadata tables)\n\n### Shared API Libraries\n\n- `dashboard/src/lib/api/cors.ts` — CORS headers, `corsResponse()`, `jsonResponse()`\n- `dashboard/src/lib/api/neon.ts` — Neon client factory\n- `dashboard/src/lib/api/auth.ts` — Clerk JWT verification\n- `dashboard/src/lib/api/changelog-parser.ts` — Claude Code changelog parser\n\n### Emergency Rollback\n\n```bash\n# List recent Pages deployments\nnpx wrangler pages deployment list --project-name=aitmpl-dashboard\n# Roll back to a previous deployment\nnpx wrangler pages deployment rollback <deployment-id> --project-name=aitmpl-dashboard\n```\n\n## Cloudflare Workers\n\nThe `cloudflare-workers/` directory contains Cloudflare Worker projects that run independently from the dashboard Pages project.\n\n### crons\n\nReplaces the old Vercel cron jobs. On a schedule it calls the dashboard API endpoints (which stay on Cloudflare Pages) with a shared `TRIGGER_SECRET`.\n\n- `*/30 * * * *` → `/api/claude-code-check` (monitors Claude Code npm releases)\n- `0 * * * *` → `/api/health-check` (hourly; was every 15 min on Vercel, reduced to save invocations)\n\nErrors and cron check-ins are reported to Sentry (`sentry.js`, DSN from the `aitmpl-workers` project — see Error Tracking below).\n\n```bash\ncd cloudflare-workers/crons\nnpm run dev          # Local dev\nnpx wrangler deploy  # Deploy\n```\n\n**Secrets (Cloudflare):** `DASHBOARD_URL` (e.g. `https://www.aitmpl.com`), `TRIGGER_SECRET`, `SENTRY_DSN` (optional).\n\n### docs-monitor (DECOMMISSIONED 2026-07)\n\nMonitored https://code.claude.com/docs hourly with Telegram notifications. **Deleted from Cloudflare** to free a cron-trigger slot for the newsletter worker (the account's free plan allows 5 cron triggers total). The code remains in `cloudflare-workers/docs-monitor/` and can be redeployed if a slot frees up (`npx wrangler deploy`).\n\n### pulse (Weekly KPI Report)\n\nCollects metrics from GitHub, Discord, Supabase, npm, and Google Analytics every Sunday at 14:00 UTC and sends a consolidated report via Telegram.\n\n**Architecture:** Single `index.js` file (no npm dependencies at runtime). All source collectors, formatter, and Telegram sender in one file.\n\n**Cron:** `0 14 * * 0` (Sundays 14:00 UTC / 11:00 AM Chile)\n\n```bash\ncd cloudflare-workers/pulse\nnpm run dev          # Local dev\nnpx wrangler deploy  # Deploy\n\n# Manual trigger\ncurl -X POST https://pulse-weekly-report.SUBDOMAIN.workers.dev/trigger \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n\n# Test single source\ncurl -X POST \"https://pulse-weekly-report.SUBDOMAIN.workers.dev/trigger?source=github\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n\n# Dry run (no Telegram)\ncurl -X POST \"https://pulse-weekly-report.SUBDOMAIN.workers.dev/trigger?send=false\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n```\n\n**Secrets (Cloudflare):**\n```bash\nTELEGRAM_BOT_TOKEN          # Shared with docs-monitor\nTELEGRAM_CHAT_ID            # Shared with docs-monitor\nGITHUB_TOKEN                # GitHub PAT (public_repo scope)\nSUPABASE_URL                # Supabase project URL\nSUPABASE_SERVICE_ROLE_KEY   # Supabase service role key\nDISCORD_BOT_TOKEN           # Discord bot token\nDISCORD_GUILD_ID            # Discord server ID\nTRIGGER_SECRET              # For manual /trigger endpoint\nGA_PROPERTY_ID              # GA4 property ID (optional)\nGA_SERVICE_ACCOUNT_JSON     # Base64 service account (optional)\n```\n\n**Graceful degradation:** Each source catches its own errors. Missing secrets or API failures show `⚠️ Unavailable` instead of crashing the report. Failed collectors are also reported to Sentry via `sentry.js` (see Error Tracking below). The Vercel collector was removed (2026-07) since the dashboard no longer deploys to Vercel.\n\n### newsletter (Weekly Community Components Email) — PAUSED 2026-09-20\n\nComposes and sends a simple weekly email via Resend featuring trending components (one Skill, Agent, MCP, Hook and Setting per send, in that fixed order). Selection is weighted-random by recent downloads and the copy (subject, catalog intro, per-component sentences, stats cited, closer) rotates from pools so no two emails read the same. Body is plain text plus a minimal HTML version (bold + underlined component titles, clickable component links). Data comes from the live `trending-data.json` + `components.json`.\n\n**Delivery:** Resend **Broadcast** targeting the segment in `RESEND_SEGMENT_ID` — Resend injects the per-recipient unsubscribe link (`{{{RESEND_UNSUBSCRIBE_URL}}}` placeholder in the body) and manages the suppression list automatically. Replies go to `NEWSLETTER_REPLY_TO`. The segment is the safety gate: point it at a pilot segment for tests or the full-audience segment for community-wide sends. Open/click tracking is enabled on the `aitmpl.com` domain with tracking subdomain `track.aitmpl.com` (metrics per broadcast at resend.com/broadcasts). **The weekly send is paused** (2026-09-20) because the emails were reading as spam; the worker stays deployed and only the automatic send is off. `wrangler.toml` has `crons = []` (was `0 16 * * SUN`, a slot freed by decommissioning docs-monitor) and `NEWSLETTER_ENABLED = \"false\"` makes `scheduled()` a no-op, so restoring the trigger alone does not resume sending. Repo edits do not reach production: no GitHub Action deploys this worker, so a pause or a resume only lands via `npx wrangler deploy` from `cloudflare-workers/newsletter/`. `GET /preview?format=text` composes without sending and `POST /trigger` sends (`?send=false` for dry run) — both still work while paused.\n\n```bash\ncd cloudflare-workers/newsletter\nnpm run dev          # Local dev\nnpx wrangler deploy  # Deploy\n\n# Preview content without sending (repeat to see the copy rotate)\ncurl \"https://aitmpl-newsletter.SUBDOMAIN.workers.dev/preview?format=text\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n\n# Real send: creates + sends a Broadcast to the segment in RESEND_SEGMENT_ID\ncurl -X POST \"https://aitmpl-newsletter.SUBDOMAIN.workers.dev/trigger\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n```\n\n**Secrets (Cloudflare):** `RESEND_API_KEY` (full access — broadcasts/segments), `RESEND_SEGMENT_ID`, `NEWSLETTER_REPLY_TO`, `TRIGGER_SECRET`, `SENTRY_DSN` (optional). Public vars in `wrangler.toml [vars]`: `DASHBOARD_URL`, `RESEND_FROM_EMAIL` (`daniel.avila@aitmpl.com`), `NEWSLETTER_ENABLED` (currently `\"false\"` — the kill switch described above).\n\n## Error Tracking (Sentry)\n\nFree-tier Sentry, added to close the gap where automated cron/worker failures\nwere previously invisible. No official `@sentry/*` SDK is used anywhere —\nevery surface has its own tiny dependency-free client that posts directly to\nthe Sentry envelope API via `fetch()`, matching this repo's zero-dependency\nworker style and avoiding Cloudflare Pages SSR friction with `@sentry/astro`.\n\n**Status as of 2026-07-04: all 3 projects live and verified end-to-end** (each\nconfirmed with a manual test event returning HTTP 200 from Sentry and\nappearing in its Issues dashboard).\n\n- ✅ **Cloudflare Workers** (Sentry project `aitmpl-workers`) — `SENTRY_DSN`\n  secret set on all 3 workers (`aitmpl-crons`, `pulse-weekly-report`,\n  `claude-docs-monitor`) via `wrangler secret put SENTRY_DSN`.\n- ✅ **Dashboard** (Sentry project `aitmpl-dashboard`) — `SENTRY_DSN` set as a\n  Cloudflare Pages secret (`wrangler pages secret put SENTRY_DSN\n  --project-name=aitmpl-dashboard`). Wired into `captureApiError()` calls in\n  `claude-code-check`, `health-check`, and the three `track-*` endpoints.\n- ✅ **CLI** (Sentry project `aitmpl-cli`) — the DSN is public by design\n  (send-only, not a secret) and ships **hardcoded as the default** in\n  `cli-tool/src/error-reporting.js` (`DEFAULT_SENTRY_DSN`, overridable via\n  `CCT_SENTRY_DSN` for testing against a different project). Reporting\n  itself stays **opt-in**: requires the end user to set\n  `CCT_ERROR_REPORTING=true`, and always defers to the existing\n  `CCT_NO_TRACKING`/`CCT_NO_ANALYTICS`/`CI` opt-outs.\n\n**Not yet configured (any surface):** Sentry alert rules to Discord/Telegram,\nand Cron Monitors dashboards for the workers' scheduled check-ins (the\n`checkIn()` calls already send `in_progress`/`ok`/`error` events — a Monitor\njust needs to be created in the Sentry UI with matching slugs:\n`claude-code-check`, `health-check`, `pulse-weekly-report`, `docs-monitor`).\n\n**Files:** `cloudflare-workers/{crons,pulse,docs-monitor}/sentry.js` (workers),\n`dashboard/src/lib/api/error-tracking.ts` (dashboard), `cli-tool/src/error-reporting.js` (CLI).\n\n## Dashboard (www.aitmpl.com)\n\nAstro + React + Tailwind dashboard serving both `www.aitmpl.com` and `app.aitmpl.com`. Clerk auth for user collections. Source lives in `dashboard/`. All API endpoints are Astro API routes in the same project.\n\n### Architecture\n\n- **Framework**: Astro 5 with React islands, Tailwind v4, `output: 'server'`, `@astrojs/cloudflare` adapter (`mode: 'directory'`)\n- **Hosting**: Cloudflare Pages (project `aitmpl-dashboard`), SSR on Workers runtime\n- **Auth**: Clerk (`window.Clerk` global, no ClerkProvider per island)\n- **Data**: `components.json` and `trending-data.json` served from `dashboard/public/` (same-origin)\n- **APIs**: All endpoints in `dashboard/src/pages/api/` (Astro API routes, no separate serverless project)\n\n### Featured Pages (`/featured/[slug]`)\n\nFeatured partner integrations shown on the dashboard homepage. Two files to edit:\n\n**`dashboard/src/lib/constants.ts`** — `FEATURED_ITEMS` array. Each entry has:\n- `name`, `description`, `logo`, `url` (`/featured/slug`), `tag`, `tagColor`, `category`\n- `ctaLabel`, `ctaUrl`, `websiteUrl`\n- `installCommand` — shown in the sidebar Quick Install box\n- `metadata` — key/value pairs shown in the Details sidebar (e.g. `Components: '8'`)\n- `links` — sidebar links list\n\n**`dashboard/src/pages/featured/[slug].astro`** — Content for each slug rendered via `{slug === 'brightdata' && (...)}` blocks. Each block contains the full HTML content for that partner page.\n\n**When adding a skill to a featured page:**\n1. Add a new card `<div class=\"flex gap-3 ...\">` inside the Skills Layer section of the relevant `{slug === '...'}` block\n2. Update `installCommand` in `constants.ts` to include the new skill\n3. Increment `metadata.Components` count in `constants.ts`\n\nCurrent featured slugs: `brightdata`, `neon-instagres`, `claudekit`, `braingrid`\n\n### Cloudflare Pages Project Setup\n\nA single Cloudflare Pages project (`aitmpl-dashboard`) serves all domains. Config lives in `dashboard/wrangler.toml`:\n\n| Project | Domains | Root Directory | Build output |\n|---------|---------|----------------|--------------|\n| `aitmpl-dashboard` | `www.aitmpl.com`, `aitmpl.com` (redirect), `app.aitmpl.com` | `dashboard` | `dist` |\n\n`wrangler.toml` sets `pages_build_output_dir = \"./dist\"`, `compatibility_flags = [\"nodejs_compat\"]`, and the `PUBLIC_*` build-time vars in `[vars]`. Secrets are set via the Cloudflare Dashboard or `wrangler pages secret put`.\n\n### Deployment\n\n**ALWAYS use the deployer agent (`.claude/agents/deployer.md`) for all deployments.** It runs pre-deploy checks (auth, git status, build) and handles the full pipeline safely. Never deploy manually.\n\n```bash\nnpm run deploy             # Build + `wrangler pages deploy dist` for www + app.aitmpl.com\nnpm run deploy:dashboard   # Same as above\n```\n\n**CI/CD**: Pushes to `main` auto-deploy via GitHub Actions (`.github/workflows/deploy.yml`):\n- Changes in `dashboard/**` trigger a build and `wrangler pages deploy dist --project-name=aitmpl-dashboard`\n\n**Required GitHub Secrets** (Settings > Secrets > Actions):\n- `CLOUDFLARE_API_TOKEN` — Cloudflare API token with Pages edit permission\n- `CLOUDFLARE_ACCOUNT_ID` — Cloudflare account ID\n\n### Environment Variables (Cloudflare)\n\n`PUBLIC_*` vars are build-time and live in `dashboard/wrangler.toml` `[vars]` (and are also passed to the GitHub Actions build step). Everything else is a Cloudflare secret (`wrangler pages secret put <NAME>` or the Pages dashboard):\n\n```bash\n# Clerk\nPUBLIC_CLERK_PUBLISHABLE_KEY=xxx   # [vars] — build-time\nCLERK_SECRET_KEY=xxx               # secret\n\n# Data\nPUBLIC_COMPONENTS_JSON_URL=/components.json   # [vars] — build-time\n\n# GitHub OAuth\nPUBLIC_GITHUB_CLIENT_ID=xxx        # [vars] — build-time\nGITHUB_CLIENT_SECRET=xxx           # secret\n\n# Supabase (download tracking)\nSUPABASE_URL=https://xxx.supabase.co        # secret\nSUPABASE_SERVICE_ROLE_KEY=xxx               # secret\n\n# Neon Database\nNEON_DATABASE_URL=postgresql://user:pass@host/db?sslmode=require   # secret\n\n# Discord\nDISCORD_APP_ID=xxx                 # secret\nDISCORD_BOT_TOKEN=xxx              # secret\nDISCORD_PUBLIC_KEY=xxx             # secret\nDISCORD_WEBHOOK_URL_CHANGELOG=https://discord.com/api/webhooks/xxx   # secret\n```\n\n### Known Issues & Solutions\n\n**Node built-ins in SSR**\n- The Cloudflare Workers runtime does not expose Node's `fs`/`path`/etc. by default. `astro.config.mjs` enables `nodejs_compat` (via `wrangler.toml`) and externalizes `node:fs`, `node:path`, `node:url`, `node:stream` in SSR. Avoid adding new hard dependencies on Node-only APIs in server code.\n\n**`react-dom/server` on Cloudflare**\n- `astro.config.mjs` aliases `react-dom/server` to `react-dom/server.node` and marks `react-dom` as `noExternal` at build time so React SSR works on the Workers runtime. Don't remove this alias.\n\n### Local Development\n\n```bash\ncd dashboard\nnpm install\nnpx astro dev --port 4321   # Dashboard + APIs at http://localhost:4321\n```\n\n## Data Files\n\n### Component Catalog\n\n- `docs/components.json` — Full generated catalog (source of truth), keeps `content` and `security` fields (needed by the legacy static site)\n- `dashboard/public/components.json` — Dashboard copy, **without** `content`/`security` (lighter payload; dashboard doesn't need them)\n- `dashboard/public/counts.json` — Per-type counts only (e.g. `{\"agents\": 421, ...}`), used by the sidebar/plugins pages instead of loading the full catalog\n- `dashboard/public/components/{type}.json` — One file per component type (agents.json, commands.json, etc.), loaded on demand by `ComponentGrid.tsx` for the active tab\n- `dashboard/public/search-index.json` — Flat array for `SearchModal.tsx`\n- `dashboard/public/component-content/{type}/{slug}.json` — Full per-component content (incl. markdown body), fetched on demand when a component's detail view or PR flow needs it\n- `dashboard/public/trending-data.json` — Trending/download stats\n- `dashboard/public/api/v1/` — **Catalog API v1, the public contract** (see below)\n\nAll of the above are served as static Cloudflare Pages assets. The catalog\nfiles (`components.json`, `counts.json`, `components/*`, `search-index.json`,\n`component-content/*`) use `max-age=300, stale-while-revalidate=3600`, so a\ndeploy reaches browsers within minutes; `trending-data.json` keeps 1 h (see\n`dashboard/public/_headers`). With the old 24 h `max-age` a browser kept a\nstale `components/{type}.json` for a day after a deploy while `counts.json`\nwas already new: the sidebar counted a component the grid could not find.\n\n### Catalog API v1 (public contract)\n\nOutside code reads the catalog (known: `profullstack/niche-db` reads\n`docs/components.json` from raw.githubusercontent.com; our own\n`mods/productivity/aitmpl` reads `components/{type}.json`, `counts.json` and\n`trending-data.json` from www.aitmpl.com, and installed copies never update).\n`/api/v1/` is the versioned surface for them: `catalog.json`,\n`catalog/{type}.json`, `version.json` (sha256 for cheap polling) and\n`schema.json`, written by `scripts/catalog_api_v1.py` (called at the end of\n`generate_components_json.py`) from `scripts/catalog_api_v1.schema.json`.\nDocs page `/developers/catalog-api`; audit and design in\n`docs/catalog-api-formalization.md`.\n\n- **Within v1, changes are additive only.** Removing or renaming a field,\n  changing a type, the `id` format or a URL needs `/api/v2/` with v1 kept for 6\n  months. Record every format change in `docs/catalog-api/CHANGELOG.md`.\n- Adding a field: add it to the schema (`$defs.component`, also `required`),\n  bump `SCHEMA_VERSION` minor, add a changelog entry. The writer refuses\n  undeclared fields and validates every run (fatal in CI, where `jsonschema`\n  is installed); `catalog-api-v1.yml` runs the tests on PRs.\n- Never add sponsored/placement data to v1, and never order it by anything but `id`.\n- `generatedAt` only moves when the components changed, so a no-op run stays byte-identical.\n- The legacy files stay the site's own data, but treat their current shape as\n  frozen in practice (installed `/aitmpl` mods read them), and do not delete\n  `docs/components.json` in a legacy-site cleanup: niche-db reads it.\n\n### Data Flow\n\n1. `scripts/generate_components_json.py` scans `cli-tool/components/`\n2. Generates `docs/components.json` (full, with `content`/`security`) and the split dashboard artifacts (`dashboard/public/components.json`, `counts.json`, `components/{type}.json`, `search-index.json`, `component-content/{type}/{slug}.json`) — these two writes are decoupled, so the dashboard payload stays lean without touching the legacy catalog — then the Catalog API v1 files under `dashboard/public/api/v1/`\n3. Dashboard islands (`ComponentGrid.tsx`, `SearchModal.tsx`, `Sidebar.astro`, `SendToRepoModal.tsx`) load the split artifacts instead of the full catalog\n4. `scripts/generate_trending_data.py` writes only `docs/trending-data.json`; `update-json-data.yml` copies it to `dashboard/public/trending-data.json`, which is the one `TrendingView.tsx` and the home counters actually fetch\n5. Download tracking via `/api/track-download-supabase`\n\n### Plugins & Marketplaces Catalog\n\n- `scripts/generate_plugins_json.py` — scans the repos listed in `REPOS` via the `gh` CLI (needs `gh auth login`) and writes `dashboard/public/plugins.json`. This is a **manual, offline step** — it does not run during `npm run build` or CI/CD, so re-running it never affects deploy time.\n- For each marketplace it records `plugins_list[].components` (counts per type) and `plugins_list[].components_items` (`{name, description}` per command/agent/skill/hook/mcp/lsp, description parsed from the item's frontmatter). The dashboard's `/plugins/[slug].astro` page renders this through `MarketplacePluginsList.tsx`, which shows a search box and a \"view details\" modal per plugin.\n- **`max_local_scans = 50`** in `extract_marketplace_plugins_detail()` caps how many *locally-sourced* plugins (i.e. `source: \"./plugins/...\"` within the marketplace's own repo) get scanned for real component names/descriptions, per marketplace, to bound GitHub API calls. Plugins beyond that cap (or plugins hosted in an external repo, which are never scanned) fall back to showing only tag badges in the modal, with no itemized breakdown — this is a graceful degradation, not an error.\n  - As of 2026-07-11, `anthropics/claude-plugins-official` alone has 51 locally-sourced plugins (out of 255 total), i.e. already at the edge of this cap. Bump `max_local_scans` if more complete coverage is needed — GitHub's rate limit (5000 req/hour authenticated) is not the constraint, wall-clock run time is (each item now costs 1 extra API call to fetch its file content for the description).\n\n### Legacy Static Site (docs/)\n\nThe `docs/` directory contains the old static HTML site (no longer deployed to www). GitHub Pages still builds it from repository settings and publishes it at `davila7.github.io/claude-code-templates`; nothing in production depends on that, and every article canonicalises to `aitmpl.com`. Exception: `docs/components.json` is read by external code from raw.githubusercontent.com (see Catalog API v1), so keep generating it. Blog articles in `docs/blog/` are still referenced externally.\n\n### Blog Article Creation\n\nUse the CLI skill to create blog articles:\n\n```bash\n/create-blog-article @cli-tool/components/{type}/{category}/{name}.json\n```\n\nThis automatically:\n1. Generates AI cover image\n2. Creates HTML with SEO optimization\n3. Updates `docs/blog/blog-articles.json`\n\n**The site serves `dashboard/public/blog/`, not `docs/blog/`.** The skill writes\nonly the `docs/` copy, so after creating an article you must mirror it across —\nthe article directory, its cover under `assets/`, and the new entry in\n`blog-articles.json` — or it never appears on aitmpl.com. Three articles were\nstranded this way before 2026-09-20. `daily-blog-discord.yml` picks from\n`docs/blog/blog-articles.json` and links to `aitmpl.com/blog/<slug>/`, so an\nunmirrored article is posted to Discord as a 404. Keep the two\n`blog-articles.json` files identical.\n\n## Code Standards\n\n### Path Handling\n- Use relative paths: `.claude/scripts/`, `.claude/hooks/`\n- Never hardcode absolute paths or home directories\n- Use `path.join()` for cross-platform compatibility\n\n### Naming Conventions\n- Files: `kebab-case.js`, `PascalCase.js` (for classes)\n- Functions/Variables: `camelCase`\n- Constants: `UPPER_SNAKE_CASE`\n- Components: `hyphenated-names`\n\n### Error Handling\n- Use try/catch for async operations\n- Provide helpful error messages\n- Log errors with context\n- Implement fallback mechanisms\n\n## Testing\n\n```bash\nnpm test                 # Run all tests\nnpm run test:watch      # Watch mode\nnpm run test:coverage   # Coverage report\n```\n\nAim for 70%+ test coverage. Test critical paths and error handling.\n\n## Common Issues\n\n**API endpoint returns 404 after deploy**\n- API routes must be in `dashboard/src/pages/api/` as Astro API routes\n- Export named HTTP methods: `export const POST: APIRoute`, `export const GET: APIRoute`\n\n**Download tracking not working**\n- Check Cloudflare Pages logs: `npx wrangler pages deployment tail --project-name=aitmpl-dashboard`\n- Verify environment variables / secrets in the Cloudflare Pages dashboard\n- Test endpoint manually with curl\n\n**Components not updating on website**\n- Run `python scripts/generate_components_json.py` (writes both `docs/components.json` and the split `dashboard/public/` artifacts directly — no manual copy step)\n- Deploy, then wait up to 5 min or hard-reload (catalog JSON is browser-cached 5 min, see `dashboard/public/_headers`)\n\n## Important Notes\n\n- **Component catalog**: Always regenerate after adding/modifying components\n- **API tests**: Required before production deploy (breaks download tracking)\n- **Secrets**: Never commit API keys (use environment variables)\n- **Paths**: Use relative paths for all project files\n- **Backwards compatibility**: Don't break existing component installations\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/.claude-plugin/plugin.json)"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CLAUDE.md\n\nThis file provides guidance to Claude Code when working with this repository.\n\n## Project Overview\n\nNode.js CLI tool for managing Claude Code components (agents, commands, MCPs, hooks, settings) with a static website for browsing and installing components. The dashboard and its API routes are deployed on Cloudflare Pages, with supporting cron and monitoring tasks running as Cloudflare Workers.\n\n## Essential Commands\n\n```bash\n# Development\nnpm install                    # Install dependencies\nnpm test                       # Run tests\nnpm version X.Y.Z --ignore-scripts=false  # Sync, commit, and tag all package versions\nnpm run version:set -- X.Y.Z   # Sync versions without creating a commit or tag\nnpm publish --ignore-scripts=false  # Publish and run the trusted prepublish guard\n\n# Component catalog\npython scripts/generate_components_json.py                   # Update docs/components.json (pulls download counts from Supabase: minutes)\npython scripts/generate_components_json.py --skip-downloads  # Same, keeping the counts already in the catalog: seconds\n\n# Dashboard + API (Astro on Cloudflare Pages)\ncd dashboard && npm run build  # Build before deploy\nnpm run deploy                 # Deploy www + app.aitmpl.com via wrangler\n```\n\n> Deploys to production happen automatically via GitHub Actions on push to `main`\n> (changes in `dashboard/**`). Manual deploy uses `wrangler pages deploy`, not Vercel.\n\n## Security Guidelines\n\n### ⛔ CRITICAL: NEVER Hardcode Secrets or IDs\n\n**NEVER write API keys, tokens, passwords, project IDs, org IDs, or any identifier in code.** This includes Cloudflare account/project IDs, Supabase URLs, Discord IDs, database connection strings, and any other infrastructure identifier. ALL must go in `.env` (or Cloudflare secrets via `wrangler secret put`).\n\n```javascript\n// ❌ WRONG\nconst API_KEY = \"AIzaSy...\";\n\n// ✅ CORRECT\nconst API_KEY = process.env.GOOGLE_API_KEY;\n```\n\n**When creating scripts with API keys:**\n1. Use `process.env` (Node.js) or `os.environ.get()` (Python)\n2. Load from `.env` file using `dotenv`\n3. Add variable to `.env.example` with placeholder\n4. Verify `.env` is in `.gitignore`\n\n**If you accidentally commit a secret:**\n1. Revoke the key IMMEDIATELY\n2. Generate new key\n3. Update `.env`\n4. Old key is compromised forever (git history)\n\n## Component System\n\n### Component Types\n\n**Agents** (600+) - AI specialists for development tasks\n**Commands** (200+) - Custom slash commands for workflows\n**MCPs** (55+) - External service integrations\n**Settings** (60+) - Claude Code configuration files\n**Hooks** (39+) - Automation triggers\n**Loops** (18+) - Autonomous agentic workflows (goal + interval + stop condition) that reference other components\n**Mods** (10) - Claude Mods: plugins whose behaviour lives in a function-hooks module (`register(on, options)` hooking engine events as `($, e, next)` middleware). Anthropic's reference is [anthropics/claude-code/mods](https://github.com/anthropics/claude-code/tree/main/mods) (three built-in mods + `mods/types/claude-code.d.ts`); discussion in [anthropics/claude-code#91870](https://github.com/anthropics/claude-code/issues/91870). Mods are official and on by default in Claude Code >= 2.1.287 ([docs](https://code.claude.com/docs/en/plugins/mods/overview)); 2.1.287+ ignores `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS`, builds 2.1.259–2.1.286 still need it set to `1`; the `$` API may change between releases. Each mod is a complete plugin directory in Anthropic's `mods/` layout: `cli-tool/components/mods/{category}/{name}/` with `.claude-plugin/plugin.json` (name, description, `userConfig` — options are read from user/managed settings `pluginConfigs[name].options`, never project settings), `hooks/hooks.json` (`modules`), any number of hooks-modules under `hooks/` (relative imports allowed), optional `commands/*.md` (slash commands the plugin ships; a `skill.prompt` hook may rewrite their prompt at run time), `types/`, `tests/`, and a `README.md` the site shows. The generator uses README.md as content and ships every text file in the per-component content file (`files`), so the site explorer and the send-to-repo flow have the whole plugin. `--mod` (alias `--function-hook`) downloads the directory recursively (like a skill) and writes it verbatim to `.claude/skills/{name}/`, which Claude Code auto-loads as `{name}@skills-dir` — **only in a trusted project** (see \"Debugging a mod that seems silent\" below). Third-party mods are vendored as-is with LICENSE + attribution (e.g. `games/cc-arcade`). **Every module must typecheck against `cli-tool/components/mods/types/claude-code.d.ts`** (`cd cli-tool/components/mods && npx -y -p typescript@5 tsc -p tsconfig.json`; CI runs it in `mods-typecheck.yml`). Rules from the engine: import types only from `'claude-code'`, spell `$` as `$.noun.event(...)` at the call site (never pass `$` to a helper), treat `e` as frozen, deny with `{ deny }` without calling `next` (returning `{}` is fail-open). When Anthropic bumps the API, regenerate the d.ts with `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude -p \"/plugin-types <dir>\"` on a current Claude Code and re-run tsc (the header's first line names the writing version; currently 2.1.301; 2.1.278 added `prompt.attachment` — the per-attachment hook `jev-skill-suggestion` uses to withhold the `skill_listing` system-reminder — and 2.1.283 changed `$.model.complete` from a string and `$.model.fork` from `result | null` to `{ isAnswered, text, usage } | { isAnswered: false, reason }`). Note `/context`'s \"Skills\" row is estimated from the roster and does not reflect that hook; a mod can ship `commands/*.md` and rewrite their prompt in `skill.prompt` (that is how `/jev-skill-suggestion:setup` hides every skill as `user-invocable-only` while the mod injects the chosen `SKILL.md` itself). Typechecking against the d.ts does not prove an older engine draws a mod: Claude Code 2.1.287 skips the whole `ui.render` of a Button with Text children instead of a `label` string, and the Claude Desktop app may run an older bundled build than your terminal: `ultra-config` drew in a 2.1.301 terminal and stayed empty in Desktop with exactly that skip. Test a pane on the oldest supported engine too: `npm i @anthropic-ai/claude-code@2.1.287` in a scratch folder, then run its `claude plugin test {category}/{name}` from `cli-tool/components/mods`. The early-access banner and Beta badge were removed 2026-10-04 (mods are official). Old URLs (`/function-hooks`, `/component/function-hook/*`) redirect via `dashboard/public/_redirects`.\n**Templates** (14+) - Complete project configurations\n\n#### Debugging a mod that seems silent (verified 2026-09-19 on Claude Code 2.1.278)\n\nA mod that \"produces no output\" has, in order of likelihood, these causes — none of which is the mod's code. Check them before touching the module:\n\n1. **The project is not trusted.** Claude Code adopts a plugin from a project's `.claude/skills/{name}/` only when `~/.claude.json` has `projects[<path>].hasTrustDialogAccepted: true`; an untrusted folder's `.claude/` is repository content and is not read at all (`plugin.scope===\"project\"` candidates are dropped and re-discovered \"post-trust\"). `claude -p` never shows the trust prompt, so a headless run in a fresh folder never loads a project mod. Fix: open `claude` interactively in the folder and accept the prompt, or pass `--plugin-dir .claude/skills/{name}` (explicit, loads as `{name}@inline`). `~/.claude/skills/{name}/` (user-level) always loads.\n2. **You are looking in the wrong place.** `$.ui.log` appends a dim transcript line and writes to the debug log; `$.ui.status` pins a line under the prompt. In `claude -p` / the SDK there is no transcript and no status row: the lines are only in `~/.claude/debug/<session-id>.txt` (`.txt`, not `.log`; `latest` symlinks the newest) and reach an SDK host as `ui_log`. The header, statusline and effort box show the *session's* settings and never move: a mod rewrites `model`/`effort` per *request*. The other observable is `\"effort\":\"…\"` in the transcript JSONL under `~/.claude/projects/<slug>/`.\n3. **Options under the wrong key.** `pluginConfigs` is keyed by the plugin's full id: `\"{name}@skills-dir\"` when auto-loaded, `\"{name}\"` with `--plugin-dir`. Under the wrong key every option is its default and the debug log says `plugin {name}: no pluginConfigs[\"{name}@skills-dir\"].options in user, --settings or managed settings`.\n4. **Function hooks off.** On Claude Code < 2.1.287 with `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS` unset/`0` the debug log says `installed plugins' hooks modules not loaded: rollout flag (tengu_plugin_hooks_modules) is off`. Built-in mods (`agents-md@builtin`) load regardless, so their presence proves nothing about ours.\n\nThe lines that settle each question in `claude --debug`: `Found N plugins`, `Read hooks.json for plugin {name}`, `hooks module {name}@skills-dir loaded (worker, environment N, tier user); events: …`, then `hooks module {name}@… <event> settled in Xms`. Positive control: `pacman@skills-dir` loads from `.claude/skills/` in the trusted `claude-code-templates` checkout. Anthropic documents only `claude --plugin-dir mods/<name>` for its reference mods; the `.claude/skills` auto-load is Claude Code 2.1.157+ (CHANGELOG: \"Plugins in `.claude/skills` directories are now automatically loaded\").\n\n### Installation Patterns\n\n```bash\n# Single component\nnpx claude-code-templates@latest --agent frontend-developer\nnpx claude-code-templates@latest --command setup-testing\nnpx claude-code-templates@latest --hook automation/simple-notifications\nnpx claude-code-templates@latest --loop engineering/docs-sweep-loop  # also installs the loop's referenced components\nnpx claude-code-templates@latest --mod security/secret-redactor          # Claude Mod: plugin at .claude/skills/secret-redactor/\n\n# Batch installation\nnpx claude-code-templates@latest --agent security-auditor --command security-audit --setting read-only-mode\n\n# Interactive mode\nnpx claude-code-templates@latest\n```\n\n### Component Development\n\n#### Adding New Components\n\n**CRITICAL: Use the component-reviewer agent for ALL component changes**\n\nWhen adding or modifying components, you MUST use the `component-reviewer` subagent to validate the component before committing:\n\n```\nUse the component-reviewer agent to review [component-path]\n```\n\n**Component Creation Workflow:**\n\n1. Create component file in `cli-tool/components/{type}/{category}/{name}.md`\n2. Use descriptive hyphenated names (kebab-case)\n3. Include clear descriptions and usage examples\n4. **REVIEW with component-reviewer agent** (validates format, security, naming)\n5. Fix any issues identified by the reviewer\n6. **TEST before generating/publishing**: ask the human in the session whether\n   they want to test the newly created/modified component(s) first. Do NOT run\n   `generate_components_json.py`, commit, or publish until testing is confirmed\n   or explicitly skipped by the user.\n7. Run `python scripts/generate_components_json.py` to update catalog\n   (**maintainers only** — see below)\n\n#### Generated catalog files: who regenerates them\n\n`scripts/generate_components_json.py` writes `docs/components.json` and the\nsplit `dashboard/public/` artifacts (`components.json`, `counts.json`,\n`search-index.json`, `components/*.json`, `component-content/**`, `api/v1/**`). These files\nare **generated output, never hand-edited**, and who commits them depends on\nthe workflow:\n\n| Workflow | Regenerate + commit the catalog? |\n|----------|----------------------------------|\n| Maintainer working directly on this repo (local branch, sync PRs, agent-driven migrations) | ✅ Yes — run the script (`--skip-downloads` is enough for a content change) and commit the output with the component |\n| **External contributor PR (fork)** | ❌ **No** — the PR must only contain files under `cli-tool/components/` (plus supporting files). The catalog is regenerated automatically after merge (`update-json-data.yml` daily cron, or a maintainer). |\n\nWhy: the generated JSON files are single-line blobs that change on every\ncomponent/download-count update, so two PRs that both commit them always\nconflict. `.github/workflows/generated-files-guard.yml` fails any\nnon-maintainer PR that touches them and posts revert instructions; the\n`component-pr-welcome.yml` bot also warns about it up front.\n\nTwo workflows regenerate them on `main`: `update-component-content.yml`\nruns on every push that touches `cli-tool/components/**` with\n`--skip-downloads` (content only, seconds) and then dispatches `deploy.yml`\n(its own push uses `GITHUB_TOKEN`, which never triggers other workflows, so\nwithout that the new catalog would not reach aitmpl.com), and `update-json-data.yml`\n(daily cron) refreshes the download counts by pulling the whole\n`component_downloads` table from Supabase (minutes) — that table has one\nrow per download, which is what makes a full run slow.\n\nWhen reviewing a contributor PR that includes these files, ask them to revert\nwith `git checkout origin/main -- docs/components.json dashboard/public/` rather\nthan resolving the conflict by hand.\n\n**Mods (`cli-tool/components/mods/`) are plugin directories, not `.md` files.** Creating one: `mods/{category}/{name}/` with `.claude-plugin/plugin.json`, `hooks/hooks.json`, the hooks-modules under `hooks/`, a `README.md`, optionally `commands/`, `types/` and `tests/`. Before review: `cd cli-tool/components/mods && npx -y -p typescript@5 tsc -p tsconfig.json` and `claude plugin validate cli-tool/components/mods/{category}/{name}`. The component-reviewer applies this checklist to a mod:\n- ✅ `plugin.json` parses, has `name` (= directory name), `description`, `license`, and `author`/`repository` (attribution for vendored code)\n- ✅ `hooks/hooks.json` has a non-empty `modules` list and every entry exists under `hooks/`\n- ✅ Modules import types only from `'claude-code'`, use relative imports, spell `$` as `$.noun.event(...)`, never shadow `h` in a surface module\n- ✅ Options are declared in `plugin.json` `userConfig` (string/number/boolean/directory/file; lists as comma-separated strings)\n- ✅ Guards deny with `{ deny }` without calling `next`; `.catch` where fail-closed matters\n- ✅ No secrets (keys only via options), no absolute paths, no `$.process.run` with a shell\n- ✅ README states the command, controls/options, the minimum Claude Code version and (for `games/`) the attribution\n\n**The component-reviewer agent checks:**\n- ✅ Valid YAML frontmatter and required fields\n- ✅ Proper kebab-case naming conventions\n- ✅ No hardcoded secrets (API keys, tokens, passwords)\n- ✅ Relative paths only (no absolute paths)\n- ✅ Supporting files exist (for hooks with scripts)\n- ✅ Clear, specific descriptions\n- ✅ Correct category placement\n- ✅ Security best practices\n\n**Example Usage:**\n```\n# After creating a new agent\nUse the component-reviewer agent to review cli-tool/components/agents/development-team/react-expert.md\n\n# Before committing hook changes\nUse the component-reviewer agent to review cli-tool/components/hooks/git/prevent-force-push.json\n\n# For PR reviews with multiple components\nUse the component-reviewer agent to review all modified components in cli-tool/components/\n```\n\nThe agent will provide prioritized feedback:\n- **❌ Critical Issues**: Must fix before merge (security, missing fields)\n- **⚠️ Warnings**: Should fix (clarity, best practices)\n- **📋 Suggestions**: Nice to have improvements\n\n#### Skill Security Scanning (SkillSpector)\n\nSkills under `cli-tool/components/skills/**` are scanned for security\nvulnerabilities by [SkillSpector](https://github.com/NVIDIA/skillspector)\n(NVIDIA, Apache-2.0) — a static analyzer with 64 vulnerability patterns\n(prompt injection, data exfiltration, supply chain, dangerous code/AST, taint\ntracking, YARA signatures, etc.). It runs in static-only mode (`--no-llm`), so\nno API key or secret is required.\n\nTwo GitHub Actions drive it, both via the batch orchestrator\n`scripts/skillspector_scan.py`:\n\n- **`.github/workflows/skill-security-scan.yml`** (PR) — scans only the skills\n  changed in the PR (`git diff`), posts an idempotent report comment, and\n  **blocks** the check if any changed skill scores HIGH/CRITICAL (risk score\n  > 50). Uploads an aggregated SARIF to the Security tab.\n- **`.github/workflows/skill-security-scan-all.yml`** (weekly + manual) — scans\n  all skills, reports to the run summary and SARIF, and **never blocks**.\n\n**Catalog traffic light (every component type).** `generate_components_json.py`\nalso runs SkillSpector on every component through `scripts/skillspector_catalog.py`\nand stores a `skillspector` object (score, severity, recommendation, top 5\nfindings, version, file hash) on each entry of `components.json`. The dashboard\nshows a neutral \"Scanned by NVIDIA SkillSpector\" seal on cards and component pages\n(`SkillSpectorBadge.tsx`) and an informative report on the component page\n(`SkillSpectorReport.tsx`, `#security-scan`). Daniel's call (2026-10-21): no\ntraffic light, no red, no severity labels or install verdicts. It is\ndisplay-only: never use it for ordering, search or ranking. Results are cached in the previous\n`docs/components.json` by file hash + SkillSpector version, so a run only scans\nnew or changed components; bumping the pinned tag in `update-component-content.yml`\n/ `update-json-data.yml` rescans everything (~1 h). Without the `skillspector`\nCLI on PATH the previous results are kept. Agents, commands, hooks, MCPs and\nsettings are wrapped in a temporary SKILL.md (SkillSpector requires one), which\nis why JSON components with a script get an \"MCP Least Privilege / no tool scope\"\nfinding. Flags: `--skip-skillspector`, `--skillspector-only TYPE/PATH ...`.\n\nSkillSpector requires Python 3.12+ and is installed from NVIDIA's `main`\nbranch (`pip install git+https://github.com/NVIDIA/skillspector.git@main`); it\nis not published to PyPI. Risk bands: 0-20 LOW, 21-50 MEDIUM, 51-80 HIGH,\n81-100 CRITICAL.\n\n#### Statuslines with Python Scripts\n\nStatuslines can reference Python scripts that are auto-downloaded to `.claude/scripts/`:\n\n```javascript\n// In src/index.js:installIndividualSetting()\nif (settingName.includes('statusline/')) {\n  const pythonFileName = settingName.split('/')[1] + '.py';\n  const pythonUrl = githubUrl.replace('.json', '.py');\n  additionalFiles['.claude/scripts/' + pythonFileName] = {\n    content: pythonContent,\n    executable: true\n  };\n}\n```\n\n### Publishing Workflow\n\n```bash\n# 1. Update component catalog\npython scripts/generate_components_json.py\n\n# 2. Run tests\nnpm test\n\n# 3. Check current npm version, then create the synchronized version commit and tag\nnpm view claude-code-templates version  # check latest on registry\nnpm version X.Y.Z --ignore-scripts=false  # X.Y.Z = one patch above the registry version\nnpm run check:version-sync\n\n# 4. Push the version commit and tag created by npm version\ngit push origin main --follow-tags\n\n# 5. Publish to npm (requires granular access token with \"Bypass 2FA\" enabled)\nnpm config set //registry.npmjs.org/:_authToken=YOUR_GRANULAR_TOKEN\nnpm publish --ignore-scripts=false\nnpm config delete //registry.npmjs.org/:_authToken  # always clean up after\n\n# 6. Deploy website (dashboard on Cloudflare Pages)\n# Automatic on push to main (GitHub Actions). Manual: from dashboard/ run `npm run deploy`\n```\n\n**npm Publishing Notes:**\n- Classic npm tokens were revoked Dec 2025. Use **granular access tokens** from [npmjs.com/settings/~/tokens](https://www.npmjs.com/settings/~/tokens)\n- The token must have **Read and Write** permissions for `claude-code-templates` and **\"Bypass 2FA\"** enabled\n- Always remove the token from npm config after publishing (`npm config delete`)\n- The repository disables lifecycle scripts by default for security. Pass `--ignore-scripts=false` only to the trusted release commands shown above so their version and prepublish hooks can run.\n- The local `package.json` version may drift from npm if published from CI — always check `npm view claude-code-templates version` first\n- Never hardcode or commit tokens\n\n## API Architecture\n\n### Critical Endpoints\n\nAPI endpoints live as Astro API routes in `dashboard/src/pages/api/`:\n\n**`/api/track-download-supabase`** (CRITICAL)\n- Tracks component downloads for analytics\n- Used by CLI on every installation\n- Database: Supabase (component_downloads table)\n\n**`/api/discord/interactions`**\n- Discord bot slash commands\n- Features: /search, /info, /install, /popular\n\n**`/api/claude-code-check`**\n- Monitors Claude Code releases\n- Triggered every 30 minutes by the `cloudflare-workers/crons` Worker (not a Vercel cron)\n- Database: Neon (claude_code_versions, claude_code_changes, discord_notifications_log, monitoring_metadata tables)\n\n### Shared API Libraries\n\n- `dashboard/src/lib/api/cors.ts` — CORS headers, `corsResponse()`, `jsonResponse()`\n- `dashboard/src/lib/api/neon.ts` — Neon client factory\n- `dashboard/src/lib/api/auth.ts` — Clerk JWT verification\n- `dashboard/src/lib/api/changelog-parser.ts` — Claude Code changelog parser\n\n### Emergency Rollback\n\n```bash\n# List recent Pages deployments\nnpx wrangler pages deployment list --project-name=aitmpl-dashboard\n# Roll back to a previous deployment\nnpx wrangler pages deployment rollback <deployment-id> --project-name=aitmpl-dashboard\n```\n\n## Cloudflare Workers\n\nThe `cloudflare-workers/` directory contains Cloudflare Worker projects that run independently from the dashboard Pages project.\n\n### crons\n\nReplaces the old Vercel cron jobs. On a schedule it calls the dashboard API endpoints (which stay on Cloudflare Pages) with a shared `TRIGGER_SECRET`.\n\n- `*/30 * * * *` → `/api/claude-code-check` (monitors Claude Code npm releases)\n- `0 * * * *` → `/api/health-check` (hourly; was every 15 min on Vercel, reduced to save invocations)\n\nErrors and cron check-ins are reported to Sentry (`sentry.js`, DSN from the `aitmpl-workers` project — see Error Tracking below).\n\n```bash\ncd cloudflare-workers/crons\nnpm run dev          # Local dev\nnpx wrangler deploy  # Deploy\n```\n\n**Secrets (Cloudflare):** `DASHBOARD_URL` (e.g. `https://www.aitmpl.com`), `TRIGGER_SECRET`, `SENTRY_DSN` (optional).\n\n### docs-monitor (DECOMMISSIONED 2026-07)\n\nMonitored https://code.claude.com/docs hourly with Telegram notifications. **Deleted from Cloudflare** to free a cron-trigger slot for the newsletter worker (the account's free plan allows 5 cron triggers total). The code remains in `cloudflare-workers/docs-monitor/` and can be redeployed if a slot frees up (`npx wrangler deploy`).\n\n### pulse (Weekly KPI Report)\n\nCollects metrics from GitHub, Discord, Supabase, npm, and Google Analytics every Sunday at 14:00 UTC and sends a consolidated report via Telegram.\n\n**Architecture:** Single `index.js` file (no npm dependencies at runtime). All source collectors, formatter, and Telegram sender in one file.\n\n**Cron:** `0 14 * * 0` (Sundays 14:00 UTC / 11:00 AM Chile)\n\n```bash\ncd cloudflare-workers/pulse\nnpm run dev          # Local dev\nnpx wrangler deploy  # Deploy\n\n# Manual trigger\ncurl -X POST https://pulse-weekly-report.SUBDOMAIN.workers.dev/trigger \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n\n# Test single source\ncurl -X POST \"https://pulse-weekly-report.SUBDOMAIN.workers.dev/trigger?source=github\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n\n# Dry run (no Telegram)\ncurl -X POST \"https://pulse-weekly-report.SUBDOMAIN.workers.dev/trigger?send=false\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n```\n\n**Secrets (Cloudflare):**\n```bash\nTELEGRAM_BOT_TOKEN          # Shared with docs-monitor\nTELEGRAM_CHAT_ID            # Shared with docs-monitor\nGITHUB_TOKEN                # GitHub PAT (public_repo scope)\nSUPABASE_URL                # Supabase project URL\nSUPABASE_SERVICE_ROLE_KEY   # Supabase service role key\nDISCORD_BOT_TOKEN           # Discord bot token\nDISCORD_GUILD_ID            # Discord server ID\nTRIGGER_SECRET              # For manual /trigger endpoint\nGA_PROPERTY_ID              # GA4 property ID (optional)\nGA_SERVICE_ACCOUNT_JSON     # Base64 service account (optional)\n```\n\n**Graceful degradation:** Each source catches its own errors. Missing secrets or API failures show `⚠️ Unavailable` instead of crashing the report. Failed collectors are also reported to Sentry via `sentry.js` (see Error Tracking below). The Vercel collector was removed (2026-07) since the dashboard no longer deploys to Vercel.\n\n### newsletter (Weekly Community Components Email) — PAUSED 2026-09-20\n\nComposes and sends a simple weekly email via Resend featuring trending components (one Skill, Agent, MCP, Hook and Setting per send, in that fixed order). Selection is weighted-random by recent downloads and the copy (subject, catalog intro, per-component sentences, stats cited, closer) rotates from pools so no two emails read the same. Body is plain text plus a minimal HTML version (bold + underlined component titles, clickable component links). Data comes from the live `trending-data.json` + `components.json`.\n\n**Delivery:** Resend **Broadcast** targeting the segment in `RESEND_SEGMENT_ID` — Resend injects the per-recipient unsubscribe link (`{{{RESEND_UNSUBSCRIBE_URL}}}` placeholder in the body) and manages the suppression list automatically. Replies go to `NEWSLETTER_REPLY_TO`. The segment is the safety gate: point it at a pilot segment for tests or the full-audience segment for community-wide sends. Open/click tracking is enabled on the `aitmpl.com` domain with tracking subdomain `track.aitmpl.com` (metrics per broadcast at resend.com/broadcasts). **The weekly send is paused** (2026-09-20) because the emails were reading as spam; the worker stays deployed and only the automatic send is off. `wrangler.toml` has `crons = []` (was `0 16 * * SUN`, a slot freed by decommissioning docs-monitor) and `NEWSLETTER_ENABLED = \"false\"` makes `scheduled()` a no-op, so restoring the trigger alone does not resume sending. Repo edits do not reach production: no GitHub Action deploys this worker, so a pause or a resume only lands via `npx wrangler deploy` from `cloudflare-workers/newsletter/`. `GET /preview?format=text` composes without sending and `POST /trigger` sends (`?send=false` for dry run) — both still work while paused.\n\n```bash\ncd cloudflare-workers/newsletter\nnpm run dev          # Local dev\nnpx wrangler deploy  # Deploy\n\n# Preview content without sending (repeat to see the copy rotate)\ncurl \"https://aitmpl-newsletter.SUBDOMAIN.workers.dev/preview?format=text\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n\n# Real send: creates + sends a Broadcast to the segment in RESEND_SEGMENT_ID\ncurl -X POST \"https://aitmpl-newsletter.SUBDOMAIN.workers.dev/trigger\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n```\n\n**Secrets (Cloudflare):** `RESEND_API_KEY` (full access — broadcasts/segments), `RESEND_SEGMENT_ID`, `NEWSLETTER_REPLY_TO`, `TRIGGER_SECRET`, `SENTRY_DSN` (optional). Public vars in `wrangler.toml [vars]`: `DASHBOARD_URL`, `RESEND_FROM_EMAIL` (`daniel.avila@aitmpl.com`), `NEWSLETTER_ENABLED` (currently `\"false\"` — the kill switch described above).\n\n## Error Tracking (Sentry)\n\nFree-tier Sentry, added to close the gap where automated cron/worker failures\nwere previously invisible. No official `@sentry/*` SDK is used anywhere —\nevery surface has its own tiny dependency-free client that posts directly to\nthe Sentry envelope API via `fetch()`, matching this repo's zero-dependency\nworker style and avoiding Cloudflare Pages SSR friction with `@sentry/astro`.\n\n**Status as of 2026-07-04: all 3 projects live and verified end-to-end** (each\nconfirmed with a manual test event returning HTTP 200 from Sentry and\nappearing in its Issues dashboard).\n\n- ✅ **Cloudflare Workers** (Sentry project `aitmpl-workers`) — `SENTRY_DSN`\n  secret set on all 3 workers (`aitmpl-crons`, `pulse-weekly-report`,\n  `claude-docs-monitor`) via `wrangler secret put SENTRY_DSN`.\n- ✅ **Dashboard** (Sentry project `aitmpl-dashboard`) — `SENTRY_DSN` set as a\n  Cloudflare Pages secret (`wrangler pages secret put SENTRY_DSN\n  --project-name=aitmpl-dashboard`). Wired into `captureApiError()` calls in\n  `claude-code-check`, `health-check`, and the three `track-*` endpoints.\n- ✅ **CLI** (Sentry project `aitmpl-cli`) — the DSN is public by design\n  (send-only, not a secret) and ships **hardcoded as the default** in\n  `cli-tool/src/error-reporting.js` (`DEFAULT_SENTRY_DSN`, overridable via\n  `CCT_SENTRY_DSN` for testing against a different project). Reporting\n  itself stays **opt-in**: requires the end user to set\n  `CCT_ERROR_REPORTING=true`, and always defers to the existing\n  `CCT_NO_TRACKING`/`CCT_NO_ANALYTICS`/`CI` opt-outs.\n\n**Not yet configured (any surface):** Sentry alert rules to Discord/Telegram,\nand Cron Monitors dashboards for the workers' scheduled check-ins (the\n`checkIn()` calls already send `in_progress`/`ok`/`error` events — a Monitor\njust needs to be created in the Sentry UI with matching slugs:\n`claude-code-check`, `health-check`, `pulse-weekly-report`, `docs-monitor`).\n\n**Files:** `cloudflare-workers/{crons,pulse,docs-monitor}/sentry.js` (workers),\n`dashboard/src/lib/api/error-tracking.ts` (dashboard), `cli-tool/src/error-reporting.js` (CLI).\n\n## Dashboard (www.aitmpl.com)\n\nAstro + React + Tailwind dashboard serving both `www.aitmpl.com` and `app.aitmpl.com`. Clerk auth for user collections. Source lives in `dashboard/`. All API endpoints are Astro API routes in the same project.\n\n### Architecture\n\n- **Framework**: Astro 5 with React islands, Tailwind v4, `output: 'server'`, `@astrojs/cloudflare` adapter (`mode: 'directory'`)\n- **Hosting**: Cloudflare Pages (project `aitmpl-dashboard`), SSR on Workers runtime\n- **Auth**: Clerk (`window.Clerk` global, no ClerkProvider per island)\n- **Data**: `components.json` and `trending-data.json` served from `dashboard/public/` (same-origin)\n- **APIs**: All endpoints in `dashboard/src/pages/api/` (Astro API routes, no separate serverless project)\n\n### Featured Pages (`/featured/[slug]`)\n\nFeatured partner integrations shown on the dashboard homepage. Two files to edit:\n\n**`dashboard/src/lib/constants.ts`** — `FEATURED_ITEMS` array. Each entry has:\n- `name`, `description`, `logo`, `url` (`/featured/slug`), `tag`, `tagColor`, `category`\n- `ctaLabel`, `ctaUrl`, `websiteUrl`\n- `installCommand` — shown in the sidebar Quick Install box\n- `metadata` — key/value pairs shown in the Details sidebar (e.g. `Components: '8'`)\n- `links` — sidebar links list\n\n**`dashboard/src/pages/featured/[slug].astro`** — Content for each slug rendered via `{slug === 'brightdata' && (...)}` blocks. Each block contains the full HTML content for that partner page.\n\n**When adding a skill to a featured page:**\n1. Add a new card `<div class=\"flex gap-3 ...\">` inside the Skills Layer section of the relevant `{slug === '...'}` block\n2. Update `installCommand` in `constants.ts` to include the new skill\n3. Increment `metadata.Components` count in `constants.ts`\n\nCurrent featured slugs: `brightdata`, `neon-instagres`, `claudekit`, `braingrid`\n\n### Cloudflare Pages Project Setup\n\nA single Cloudflare Pages project (`aitmpl-dashboard`) serves all domains. Config lives in `dashboard/wrangler.toml`:\n\n| Project | Domains | Root Directory | Build output |\n|---------|---------|----------------|--------------|\n| `aitmpl-dashboard` | `www.aitmpl.com`, `aitmpl.com` (redirect), `app.aitmpl.com` | `dashboard` | `dist` |\n\n`wrangler.toml` sets `pages_build_output_dir = \"./dist\"`, `compatibility_flags = [\"nodejs_compat\"]`, and the `PUBLIC_*` build-time vars in `[vars]`. Secrets are set via the Cloudflare Dashboard or `wrangler pages secret put`.\n\n### Deployment\n\n**ALWAYS use the deployer agent (`.claude/agents/deployer.md`) for all deployments.** It runs pre-deploy checks (auth, git status, build) and handles the full pipeline safely. Never deploy manually.\n\n```bash\nnpm run deploy             # Build + `wrangler pages deploy dist` for www + app.aitmpl.com\nnpm run deploy:dashboard   # Same as above\n```\n\n**CI/CD**: Pushes to `main` auto-deploy via GitHub Actions (`.github/workflows/deploy.yml`):\n- Changes in `dashboard/**` trigger a build and `wrangler pages deploy dist --project-name=aitmpl-dashboard`\n\n**Required GitHub Secrets** (Settings > Secrets > Actions):\n- `CLOUDFLARE_API_TOKEN` — Cloudflare API token with Pages edit permission\n- `CLOUDFLARE_ACCOUNT_ID` — Cloudflare account ID\n\n### Environment Variables (Cloudflare)\n\n`PUBLIC_*` vars are build-time and live in `dashboard/wrangler.toml` `[vars]` (and are also passed to the GitHub Actions build step). Everything else is a Cloudflare secret (`wrangler pages secret put <NAME>` or the Pages dashboard):\n\n```bash\n# Clerk\nPUBLIC_CLERK_PUBLISHABLE_KEY=xxx   # [vars] — build-time\nCLERK_SECRET_KEY=xxx               # secret\n\n# Data\nPUBLIC_COMPONENTS_JSON_URL=/components.json   # [vars] — build-time\n\n# GitHub OAuth\nPUBLIC_GITHUB_CLIENT_ID=xxx        # [vars] — build-time\nGITHUB_CLIENT_SECRET=xxx           # secret\n\n# Supabase (download tracking)\nSUPABASE_URL=https://xxx.supabase.co        # secret\nSUPABASE_SERVICE_ROLE_KEY=xxx               # secret\n\n# Neon Database\nNEON_DATABASE_URL=postgresql://user:pass@host/db?sslmode=require   # secret\n\n# Discord\nDISCORD_APP_ID=xxx                 # secret\nDISCORD_BOT_TOKEN=xxx              # secret\nDISCORD_PUBLIC_KEY=xxx             # secret\nDISCORD_WEBHOOK_URL_CHANGELOG=https://discord.com/api/webhooks/xxx   # secret\n```\n\n### Known Issues & Solutions\n\n**Node built-ins in SSR**\n- The Cloudflare Workers runtime does not expose Node's `fs`/`path`/etc. by default. `astro.config.mjs` enables `nodejs_compat` (via `wrangler.toml`) and externalizes `node:fs`, `node:path`, `node:url`, `node:stream` in SSR. Avoid adding new hard dependencies on Node-only APIs in server code.\n\n**`react-dom/server` on Cloudflare**\n- `astro.config.mjs` aliases `react-dom/server` to `react-dom/server.node` and marks `react-dom` as `noExternal` at build time so React SSR works on the Workers runtime. Don't remove this alias.\n\n### Local Development\n\n```bash\ncd dashboard\nnpm install\nnpx astro dev --port 4321   # Dashboard + APIs at http://localhost:4321\n```\n\n## Data Files\n\n### Component Catalog\n\n- `docs/components.json` — Full generated catalog (source of truth), keeps `content` and `security` fields (needed by the legacy static site)\n- `dashboard/public/components.json` — Dashboard copy, **without** `content`/`security` (lighter payload; dashboard doesn't need them)\n- `dashboard/public/counts.json` — Per-type counts only (e.g. `{\"agents\": 421, ...}`), used by the sidebar/plugins pages instead of loading the full catalog\n- `dashboard/public/components/{type}.json` — One file per component type (agents.json, commands.json, etc.), loaded on demand by `ComponentGrid.tsx` for the active tab\n- `dashboard/public/search-index.json` — Flat array for `SearchModal.tsx`\n- `dashboard/public/component-content/{type}/{slug}.json` — Full per-component content (incl. markdown body), fetched on demand when a component's detail view or PR flow needs it\n- `dashboard/public/trending-data.json` — Trending/download stats\n- `dashboard/public/api/v1/` — **Catalog API v1, the public contract** (see below)\n\nAll of the above are served as static Cloudflare Pages assets. The catalog\nfiles (`components.json`, `counts.json`, `components/*`, `search-index.json`,\n`component-content/*`) use `max-age=300, stale-while-revalidate=3600`, so a\ndeploy reaches browsers within minutes; `trending-data.json` keeps 1 h (see\n`dashboard/public/_headers`). With the old 24 h `max-age` a browser kept a\nstale `components/{type}.json` for a day after a deploy while `counts.json`\nwas already new: the sidebar counted a component the grid could not find.\n\n### Catalog API v1 (public contract)\n\nOutside code reads the catalog (known: `profullstack/niche-db` reads\n`docs/components.json` from raw.githubusercontent.com; our own\n`mods/productivity/aitmpl` reads `components/{type}.json`, `counts.json` and\n`trending-data.json` from www.aitmpl.com, and installed copies never update).\n`/api/v1/` is the versioned surface for them: `catalog.json`,\n`catalog/{type}.json`, `version.json` (sha256 for cheap polling) and\n`schema.json`, written by `scripts/catalog_api_v1.py` (called at the end of\n`generate_components_json.py`) from `scripts/catalog_api_v1.schema.json`.\nDocs page `/developers/catalog-api`; audit and design in\n`docs/catalog-api-formalization.md`.\n\n- **Within v1, changes are additive only.** Removing or renaming a field,\n  changing a type, the `id` format or a URL needs `/api/v2/` with v1 kept for 6\n  months. Record every format change in `docs/catalog-api/CHANGELOG.md`.\n- Adding a field: add it to the schema (`$defs.component`, also `required`),\n  bump `SCHEMA_VERSION` minor, add a changelog entry. The writer refuses\n  undeclared fields and validates every run (fatal in CI, where `jsonschema`\n  is installed); `catalog-api-v1.yml` runs the tests on PRs.\n- Never add sponsored/placement data to v1, and never order it by anything but `id`.\n- `generatedAt` only moves when the components changed, so a no-op run stays byte-identical.\n- The legacy files stay the site's own data, but treat their current shape as\n  frozen in practice (installed `/aitmpl` mods read them), and do not delete\n  `docs/components.json` in a legacy-site cleanup: niche-db reads it.\n\n### Data Flow\n\n1. `scripts/generate_components_json.py` scans `cli-tool/components/`\n2. Generates `docs/components.json` (full, with `content`/`security`) and the split dashboard artifacts (`dashboard/public/components.json`, `counts.json`, `components/{type}.json`, `search-index.json`, `component-content/{type}/{slug}.json`) — these two writes are decoupled, so the dashboard payload stays lean without touching the legacy catalog — then the Catalog API v1 files under `dashboard/public/api/v1/`\n3. Dashboard islands (`ComponentGrid.tsx`, `SearchModal.tsx`, `Sidebar.astro`, `SendToRepoModal.tsx`) load the split artifacts instead of the full catalog\n4. `scripts/generate_trending_data.py` writes only `docs/trending-data.json`; `update-json-data.yml` copies it to `dashboard/public/trending-data.json`, which is the one `TrendingView.tsx` and the home counters actually fetch\n5. Download tracking via `/api/track-download-supabase`\n\n### Plugins & Marketplaces Catalog\n\n- `scripts/generate_plugins_json.py` — scans the repos listed in `REPOS` via the `gh` CLI (needs `gh auth login`) and writes `dashboard/public/plugins.json`. This is a **manual, offline step** — it does not run during `npm run build` or CI/CD, so re-running it never affects deploy time.\n- For each marketplace it records `plugins_list[].components` (counts per type) and `plugins_list[].components_items` (`{name, description}` per command/agent/skill/hook/mcp/lsp, description parsed from the item's frontmatter). The dashboard's `/plugins/[slug].astro` page renders this through `MarketplacePluginsList.tsx`, which shows a search box and a \"view details\" modal per plugin.\n- **`max_local_scans = 50`** in `extract_marketplace_plugins_detail()` caps how many *locally-sourced* plugins (i.e. `source: \"./plugins/...\"` within the marketplace's own repo) get scanned for real component names/descriptions, per marketplace, to bound GitHub API calls. Plugins beyond that cap (or plugins hosted in an external repo, which are never scanned) fall back to showing only tag badges in the modal, with no itemized breakdown — this is a graceful degradation, not an error.\n  - As of 2026-07-11, `anthropics/claude-plugins-official` alone has 51 locally-sourced plugins (out of 255 total), i.e. already at the edge of this cap. Bump `max_local_scans` if more complete coverage is needed — GitHub's rate limit (5000 req/hour authenticated) is not the constraint, wall-clock run time is (each item now costs 1 extra API call to fetch its file content for the description).\n\n### Legacy Static Site (docs/)\n\nThe `docs/` directory contains the old static HTML site (no longer deployed to www). GitHub Pages still builds it from repository settings and publishes it at `davila7.github.io/claude-code-templates`; nothing in production depends on that, and every article canonicalises to `aitmpl.com`. Exception: `docs/components.json` is read by external code from raw.githubusercontent.com (see Catalog API v1), so keep generating it. Blog articles in `docs/blog/` are still referenced externally.\n\n### Blog Article Creation\n\nUse the CLI skill to create blog articles:\n\n```bash\n/create-blog-article @cli-tool/components/{type}/{category}/{name}.json\n```\n\nThis automatically:\n1. Generates AI cover image\n2. Creates HTML with SEO optimization\n3. Updates `docs/blog/blog-articles.json`\n\n**The site serves `dashboard/public/blog/`, not `docs/blog/`.** The skill writes\nonly the `docs/` copy, so after creating an article you must mirror it across —\nthe article directory, its cover under `assets/`, and the new entry in\n`blog-articles.json` — or it never appears on aitmpl.com. Three articles were\nstranded this way before 2026-09-20. `daily-blog-discord.yml` picks from\n`docs/blog/blog-articles.json` and links to `aitmpl.com/blog/<slug>/`, so an\nunmirrored article is posted to Discord as a 404. Keep the two\n`blog-articles.json` files identical.\n\n## Code Standards\n\n### Path Handling\n- Use relative paths: `.claude/scripts/`, `.claude/hooks/`\n- Never hardcode absolute paths or home directories\n- Use `path.join()` for cross-platform compatibility\n\n### Naming Conventions\n- Files: `kebab-case.js`, `PascalCase.js` (for classes)\n- Functions/Variables: `camelCase`\n- Constants: `UPPER_SNAKE_CASE`\n- Components: `hyphenated-names`\n\n### Error Handling\n- Use try/catch for async operations\n- Provide helpful error messages\n- Log errors with context\n- Implement fallback mechanisms\n\n## Testing\n\n```bash\nnpm test                 # Run all tests\nnpm run test:watch      # Watch mode\nnpm run test:coverage   # Coverage report\n```\n\nAim for 70%+ test coverage. Test critical paths and error handling.\n\n## Common Issues\n\n**API endpoint returns 404 after deploy**\n- API routes must be in `dashboard/src/pages/api/` as Astro API routes\n- Export named HTTP methods: `export const POST: APIRoute`, `export const GET: APIRoute`\n\n**Download tracking not working**\n- Check Cloudflare Pages logs: `npx wrangler pages deployment tail --project-name=aitmpl-dashboard`\n- Verify environment variables / secrets in the Cloudflare Pages dashboard\n- Test endpoint manually with curl\n\n**Components not updating on website**\n- Run `python scripts/generate_components_json.py` (writes both `docs/components.json` and the split `dashboard/public/` artifacts directly — no manual copy step)\n- Deploy, then wait up to 5 min or hard-reload (catalog JSON is browser-cached 5 min, see `dashboard/public/_headers`)\n\n## Important Notes\n\n- **Component catalog**: Always regenerate after adding/modifying components\n- **API tests**: Required before production deploy (breaks download tracking)\n- **Secrets**: Never commit API keys (use environment variables)\n- **Paths**: Use relative paths for all project files\n- **Backwards compatibility**: Don't break existing component installations\n","isInternal":false,"tokens":10616,"sizeBytes":43266},{"name":"marketplace.json","path":".claude-plugin/marketplace.json","rawUrl":"https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/.claude-plugin/marketplace.json","title":"Claude Plugin Marketplace Catalog","category":"marketplace","format":"json","content":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/.claude-plugin/marketplace.json)","tokens":300,"sizeBytes":208},{"name":"plugin.json","path":".claude-plugin/plugin.json","rawUrl":"https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/.claude-plugin/plugin.json","title":"claude-plugin Plugin Manifest","category":"plugin-manifest","format":"json","content":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/.claude-plugin/plugin.json)","tokens":300,"sizeBytes":194},{"name":"mcp.json","path":".cursor/mcp.json","rawUrl":"https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/.cursor/mcp.json","title":"Model Context Protocol (MCP) Configuration","category":"mcp-config","format":"json","content":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/.cursor/mcp.json)","tokens":300,"sizeBytes":187}],"systemPromptSnippet":"<yakaai_skills repo=\"davila7/claude-code-templates\">\n<!-- File: CLAUDE.md (Tokens: ~10616 | Category: claude-rule) -->\n# CLAUDE.md\n\nThis file provides guidance to Claude Code when working with this repository.\n\n## Project Overview\n\nNode.js CLI tool for managing Claude Code components (agents, commands, MCPs, hooks, settings) with a static website for browsing and installing components. The dashboard and its API routes are deployed on Cloudflare Pages, with supporting cron and monitoring tasks running as Cloudflare Workers.\n\n## Essential Commands\n\n```bash\n# Development\nnpm install                    # Install dependencies\nnpm test                       # Run tests\nnpm version X.Y.Z --ignore-scripts=false  # Sync, commit, and tag all package versions\nnpm run version:set -- X.Y.Z   # Sync versions without creating a commit or tag\nnpm publish --ignore-scripts=false  # Publish and run the trusted prepublish guard\n\n# Component catalog\npython scripts/generate_components_json.py                   # Update docs/components.json (pulls download counts from Supabase: minutes)\npython scripts/generate_components_json.py --skip-downloads  # Same, keeping the counts already in the catalog: seconds\n\n# Dashboard + API (Astro on Cloudflare Pages)\ncd dashboard && npm run build  # Build before deploy\nnpm run deploy                 # Deploy www + app.aitmpl.com via wrangler\n```\n\n> Deploys to production happen automatically via GitHub Actions on push to `main`\n> (changes in `dashboard/**`). Manual deploy uses `wrangler pages deploy`, not Vercel.\n\n## Security Guidelines\n\n### ⛔ CRITICAL: NEVER Hardcode Secrets or IDs\n\n**NEVER write API keys, tokens, passwords, project IDs, org IDs, or any identifier in code.** This includes Cloudflare account/project IDs, Supabase URLs, Discord IDs, database connection strings, and any other infrastructure identifier. ALL must go in `.env` (or Cloudflare secrets via `wrangler secret put`).\n\n```javascript\n// ❌ WRONG\nconst API_KEY = \"AIzaSy...\";\n\n// ✅ CORRECT\nconst API_KEY = process.env.GOOGLE_API_KEY;\n```\n\n**When creating scripts with API keys:**\n1. Use `process.env` (Node.js) or `os.environ.get()` (Python)\n2. Load from `.env` file using `dotenv`\n3. Add variable to `.env.example` with placeholder\n4. Verify `.env` is in `.gitignore`\n\n**If you accidentally commit a secret:**\n1. Revoke the key IMMEDIATELY\n2. Generate new key\n3. Update `.env`\n4. Old key is compromised forever (git history)\n\n## Component System\n\n### Component Types\n\n**Agents** (600+) - AI specialists for development tasks\n**Commands** (200+) - Custom slash commands for workflows\n**MCPs** (55+) - External service integrations\n**Settings** (60+) - Claude Code configuration files\n**Hooks** (39+) - Automation triggers\n**Loops** (18+) - Autonomous agentic workflows (goal + interval + stop condition) that reference other components\n**Mods** (10) - Claude Mods: plugins whose behaviour lives in a function-hooks module (`register(on, options)` hooking engine events as `($, e, next)` middleware). Anthropic's reference is [anthropics/claude-code/mods](https://github.com/anthropics/claude-code/tree/main/mods) (three built-in mods + `mods/types/claude-code.d.ts`); discussion in [anthropics/claude-code#91870](https://github.com/anthropics/claude-code/issues/91870). Mods are official and on by default in Claude Code >= 2.1.287 ([docs](https://code.claude.com/docs/en/plugins/mods/overview)); 2.1.287+ ignores `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS`, builds 2.1.259–2.1.286 still need it set to `1`; the `$` API may change between releases. Each mod is a complete plugin directory in Anthropic's `mods/` layout: `cli-tool/components/mods/{category}/{name}/` with `.claude-plugin/plugin.json` (name, description, `userConfig` — options are read from user/managed settings `pluginConfigs[name].options`, never project settings), `hooks/hooks.json` (`modules`), any number of hooks-modules under `hooks/` (relative imports allowed), optional `commands/*.md` (slash commands the plugin ships; a `skill.prompt` hook may rewrite their prompt at run time), `types/`, `tests/`, and a `README.md` the site shows. The generator uses README.md as content and ships every text file in the per-component content file (`files`), so the site explorer and the send-to-repo flow have the whole plugin. `--mod` (alias `--function-hook`) downloads the directory recursively (like a skill) and writes it verbatim to `.claude/skills/{name}/`, which Claude Code auto-loads as `{name}@skills-dir` — **only in a trusted project** (see \"Debugging a mod that seems silent\" below). Third-party mods are vendored as-is with LICENSE + attribution (e.g. `games/cc-arcade`). **Every module must typecheck against `cli-tool/components/mods/types/claude-code.d.ts`** (`cd cli-tool/components/mods && npx -y -p typescript@5 tsc -p tsconfig.json`; CI runs it in `mods-typecheck.yml`). Rules from the engine: import types only from `'claude-code'`, spell `$` as `$.noun.event(...)` at the call site (never pass `$` to a helper), treat `e` as frozen, deny with `{ deny }` without calling `next` (returning `{}` is fail-open). When Anthropic bumps the API, regenerate the d.ts with `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude -p \"/plugin-types <dir>\"` on a current Claude Code and re-run tsc (the header's first line names the writing version; currently 2.1.301; 2.1.278 added `prompt.attachment` — the per-attachment hook `jev-skill-suggestion` uses to withhold the `skill_listing` system-reminder — and 2.1.283 changed `$.model.complete` from a string and `$.model.fork` from `result | null` to `{ isAnswered, text, usage } | { isAnswered: false, reason }`). Note `/context`'s \"Skills\" row is estimated from the roster and does not reflect that hook; a mod can ship `commands/*.md` and rewrite their prompt in `skill.prompt` (that is how `/jev-skill-suggestion:setup` hides every skill as `user-invocable-only` while the mod injects the chosen `SKILL.md` itself). Typechecking against the d.ts does not prove an older engine draws a mod: Claude Code 2.1.287 skips the whole `ui.render` of a Button with Text children instead of a `label` string, and the Claude Desktop app may run an older bundled build than your terminal: `ultra-config` drew in a 2.1.301 terminal and stayed empty in Desktop with exactly that skip. Test a pane on the oldest supported engine too: `npm i @anthropic-ai/claude-code@2.1.287` in a scratch folder, then run its `claude plugin test {category}/{name}` from `cli-tool/components/mods`. The early-access banner and Beta badge were removed 2026-10-04 (mods are official). Old URLs (`/function-hooks`, `/component/function-hook/*`) redirect via `dashboard/public/_redirects`.\n**Templates** (14+) - Complete project configurations\n\n#### Debugging a mod that seems silent (verified 2026-09-19 on Claude Code 2.1.278)\n\nA mod that \"produces no output\" has, in order of likelihood, these causes — none of which is the mod's code. Check them before touching the module:\n\n1. **The project is not trusted.** Claude Code adopts a plugin from a project's `.claude/skills/{name}/` only when `~/.claude.json` has `projects[<path>].hasTrustDialogAccepted: true`; an untrusted folder's `.claude/` is repository content and is not read at all (`plugin.scope===\"project\"` candidates are dropped and re-discovered \"post-trust\"). `claude -p` never shows the trust prompt, so a headless run in a fresh folder never loads a project mod. Fix: open `claude` interactively in the folder and accept the prompt, or pass `--plugin-dir .claude/skills/{name}` (explicit, loads as `{name}@inline`). `~/.claude/skills/{name}/` (user-level) always loads.\n2. **You are looking in the wrong place.** `$.ui.log` appends a dim transcript line and writes to the debug log; `$.ui.status` pins a line under the prompt. In `claude -p` / the SDK there is no transcript and no status row: the lines are only in `~/.claude/debug/<session-id>.txt` (`.txt`, not `.log`; `latest` symlinks the newest) and reach an SDK host as `ui_log`. The header, statusline and effort box show the *session's* settings and never move: a mod rewrites `model`/`effort` per *request*. The other observable is `\"effort\":\"…\"` in the transcript JSONL under `~/.claude/projects/<slug>/`.\n3. **Options under the wrong key.** `pluginConfigs` is keyed by the plugin's full id: `\"{name}@skills-dir\"` when auto-loaded, `\"{name}\"` with `--plugin-dir`. Under the wrong key every option is its default and the debug log says `plugin {name}: no pluginConfigs[\"{name}@skills-dir\"].options in user, --settings or managed settings`.\n4. **Function hooks off.** On Claude Code < 2.1.287 with `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS` unset/`0` the debug log says `installed plugins' hooks modules not loaded: rollout flag (tengu_plugin_hooks_modules) is off`. Built-in mods (`agents-md@builtin`) load regardless, so their presence proves nothing about ours.\n\nThe lines that settle each question in `claude --debug`: `Found N plugins`, `Read hooks.json for plugin {name}`, `hooks module {name}@skills-dir loaded (worker, environment N, tier user); events: …`, then `hooks module {name}@… <event> settled in Xms`. Positive control: `pacman@skills-dir` loads from `.claude/skills/` in the trusted `claude-code-templates` checkout. Anthropic documents only `claude --plugin-dir mods/<name>` for its reference mods; the `.claude/skills` auto-load is Claude Code 2.1.157+ (CHANGELOG: \"Plugins in `.claude/skills` directories are now automatically loaded\").\n\n### Installation Patterns\n\n```bash\n# Single component\nnpx claude-code-templates@latest --agent frontend-developer\nnpx claude-code-templates@latest --command setup-testing\nnpx claude-code-templates@latest --hook automation/simple-notifications\nnpx claude-code-templates@latest --loop engineering/docs-sweep-loop  # also installs the loop's referenced components\nnpx claude-code-templates@latest --mod security/secret-redactor          # Claude Mod: plugin at .claude/skills/secret-redactor/\n\n# Batch installation\nnpx claude-code-templates@latest --agent security-auditor --command security-audit --setting read-only-mode\n\n# Interactive mode\nnpx claude-code-templates@latest\n```\n\n### Component Development\n\n#### Adding New Components\n\n**CRITICAL: Use the component-reviewer agent for ALL component changes**\n\nWhen adding or modifying components, you MUST use the `component-reviewer` subagent to validate the component before committing:\n\n```\nUse the component-reviewer agent to review [component-path]\n```\n\n**Component Creation Workflow:**\n\n1. Create component file in `cli-tool/components/{type}/{category}/{name}.md`\n2. Use descriptive hyphenated names (kebab-case)\n3. Include clear descriptions and usage examples\n4. **REVIEW with component-reviewer agent** (validates format, security, naming)\n5. Fix any issues identified by the reviewer\n6. **TEST before generating/publishing**: ask the human in the session whether\n   they want to test the newly created/modified component(s) first. Do NOT run\n   `generate_components_json.py`, commit, or publish until testing is confirmed\n   or explicitly skipped by the user.\n7. Run `python scripts/generate_components_json.py` to update catalog\n   (**maintainers only** — see below)\n\n#### Generated catalog files: who regenerates them\n\n`scripts/generate_components_json.py` writes `docs/components.json` and the\nsplit `dashboard/public/` artifacts (`components.json`, `counts.json`,\n`search-index.json`, `components/*.json`, `component-content/**`, `api/v1/**`). These files\nare **generated output, never hand-edited**, and who commits them depends on\nthe workflow:\n\n| Workflow | Regenerate + commit the catalog? |\n|----------|----------------------------------|\n| Maintainer working directly on this repo (local branch, sync PRs, agent-driven migrations) | ✅ Yes — run the script (`--skip-downloads` is enough for a content change) and commit the output with the component |\n| **External contributor PR (fork)** | ❌ **No** — the PR must only contain files under `cli-tool/components/` (plus supporting files). The catalog is regenerated automatically after merge (`update-json-data.yml` daily cron, or a maintainer). |\n\nWhy: the generated JSON files are single-line blobs that change on every\ncomponent/download-count update, so two PRs that both commit them always\nconflict. `.github/workflows/generated-files-guard.yml` fails any\nnon-maintainer PR that touches them and posts revert instructions; the\n`component-pr-welcome.yml` bot also warns about it up front.\n\nTwo workflows regenerate them on `main`: `update-component-content.yml`\nruns on every push that touches `cli-tool/components/**` with\n`--skip-downloads` (content only, seconds) and then dispatches `deploy.yml`\n(its own push uses `GITHUB_TOKEN`, which never triggers other workflows, so\nwithout that the new catalog would not reach aitmpl.com), and `update-json-data.yml`\n(daily cron) refreshes the download counts by pulling the whole\n`component_downloads` table from Supabase (minutes) — that table has one\nrow per download, which is what makes a full run slow.\n\nWhen reviewing a contributor PR that includes these files, ask them to revert\nwith `git checkout origin/main -- docs/components.json dashboard/public/` rather\nthan resolving the conflict by hand.\n\n**Mods (`cli-tool/components/mods/`) are plugin directories, not `.md` files.** Creating one: `mods/{category}/{name}/` with `.claude-plugin/plugin.json`, `hooks/hooks.json`, the hooks-modules under `hooks/`, a `README.md`, optionally `commands/`, `types/` and `tests/`. Before review: `cd cli-tool/components/mods && npx -y -p typescript@5 tsc -p tsconfig.json` and `claude plugin validate cli-tool/components/mods/{category}/{name}`. The component-reviewer applies this checklist to a mod:\n- ✅ `plugin.json` parses, has `name` (= directory name), `description`, `license`, and `author`/`repository` (attribution for vendored code)\n- ✅ `hooks/hooks.json` has a non-empty `modules` list and every entry exists under `hooks/`\n- ✅ Modules import types only from `'claude-code'`, use relative imports, spell `$` as `$.noun.event(...)`, never shadow `h` in a surface module\n- ✅ Options are declared in `plugin.json` `userConfig` (string/number/boolean/directory/file; lists as comma-separated strings)\n- ✅ Guards deny with `{ deny }` without calling `next`; `.catch` where fail-closed matters\n- ✅ No secrets (keys only via options), no absolute paths, no `$.process.run` with a shell\n- ✅ README states the command, controls/options, the minimum Claude Code version and (for `games/`) the attribution\n\n**The component-reviewer agent checks:**\n- ✅ Valid YAML frontmatter and required fields\n- ✅ Proper kebab-case naming conventions\n- ✅ No hardcoded secrets (API keys, tokens, passwords)\n- ✅ Relative paths only (no absolute paths)\n- ✅ Supporting files exist (for hooks with scripts)\n- ✅ Clear, specific descriptions\n- ✅ Correct category placement\n- ✅ Security best practices\n\n**Example Usage:**\n```\n# After creating a new agent\nUse the component-reviewer agent to review cli-tool/components/agents/development-team/react-expert.md\n\n# Before committing hook changes\nUse the component-reviewer agent to review cli-tool/components/hooks/git/prevent-force-push.json\n\n# For PR reviews with multiple components\nUse the component-reviewer agent to review all modified components in cli-tool/components/\n```\n\nThe agent will provide prioritized feedback:\n- **❌ Critical Issues**: Must fix before merge (security, missing fields)\n- **⚠️ Warnings**: Should fix (clarity, best practices)\n- **📋 Suggestions**: Nice to have improvements\n\n#### Skill Security Scanning (SkillSpector)\n\nSkills under `cli-tool/components/skills/**` are scanned for security\nvulnerabilities by [SkillSpector](https://github.com/NVIDIA/skillspector)\n(NVIDIA, Apache-2.0) — a static analyzer with 64 vulnerability patterns\n(prompt injection, data exfiltration, supply chain, dangerous code/AST, taint\ntracking, YARA signatures, etc.). It runs in static-only mode (`--no-llm`), so\nno API key or secret is required.\n\nTwo GitHub Actions drive it, both via the batch orchestrator\n`scripts/skillspector_scan.py`:\n\n- **`.github/workflows/skill-security-scan.yml`** (PR) — scans only the skills\n  changed in the PR (`git diff`), posts an idempotent report comment, and\n  **blocks** the check if any changed skill scores HIGH/CRITICAL (risk score\n  > 50). Uploads an aggregated SARIF to the Security tab.\n- **`.github/workflows/skill-security-scan-all.yml`** (weekly + manual) — scans\n  all skills, reports to the run summary and SARIF, and **never blocks**.\n\n**Catalog traffic light (every component type).** `generate_components_json.py`\nalso runs SkillSpector on every component through `scripts/skillspector_catalog.py`\nand stores a `skillspector` object (score, severity, recommendation, top 5\nfindings, version, file hash) on each entry of `components.json`. The dashboard\nshows a neutral \"Scanned by NVIDIA SkillSpector\" seal on cards and component pages\n(`SkillSpectorBadge.tsx`) and an informative report on the component page\n(`SkillSpectorReport.tsx`, `#security-scan`). Daniel's call (2026-10-21): no\ntraffic light, no red, no severity labels or install verdicts. It is\ndisplay-only: never use it for ordering, search or ranking. Results are cached in the previous\n`docs/components.json` by file hash + SkillSpector version, so a run only scans\nnew or changed components; bumping the pinned tag in `update-component-content.yml`\n/ `update-json-data.yml` rescans everything (~1 h). Without the `skillspector`\nCLI on PATH the previous results are kept. Agents, commands, hooks, MCPs and\nsettings are wrapped in a temporary SKILL.md (SkillSpector requires one), which\nis why JSON components with a script get an \"MCP Least Privilege / no tool scope\"\nfinding. Flags: `--skip-skillspector`, `--skillspector-only TYPE/PATH ...`.\n\nSkillSpector requires Python 3.12+ and is installed from NVIDIA's `main`\nbranch (`pip install git+https://github.com/NVIDIA/skillspector.git@main`); it\nis not published to PyPI. Risk bands: 0-20 LOW, 21-50 MEDIUM, 51-80 HIGH,\n81-100 CRITICAL.\n\n#### Statuslines with Python Scripts\n\nStatuslines can reference Python scripts that are auto-downloaded to `.claude/scripts/`:\n\n```javascript\n// In src/index.js:installIndividualSetting()\nif (settingName.includes('statusline/')) {\n  const pythonFileName = settingName.split('/')[1] + '.py';\n  const pythonUrl = githubUrl.replace('.json', '.py');\n  additionalFiles['.claude/scripts/' + pythonFileName] = {\n    content: pythonContent,\n    executable: true\n  };\n}\n```\n\n### Publishing Workflow\n\n```bash\n# 1. Update component catalog\npython scripts/generate_components_json.py\n\n# 2. Run tests\nnpm test\n\n# 3. Check current npm version, then create the synchronized version commit and tag\nnpm view claude-code-templates version  # check latest on registry\nnpm version X.Y.Z --ignore-scripts=false  # X.Y.Z = one patch above the registry version\nnpm run check:version-sync\n\n# 4. Push the version commit and tag created by npm version\ngit push origin main --follow-tags\n\n# 5. Publish to npm (requires granular access token with \"Bypass 2FA\" enabled)\nnpm config set //registry.npmjs.org/:_authToken=YOUR_GRANULAR_TOKEN\nnpm publish --ignore-scripts=false\nnpm config delete //registry.npmjs.org/:_authToken  # always clean up after\n\n# 6. Deploy website (dashboard on Cloudflare Pages)\n# Automatic on push to main (GitHub Actions). Manual: from dashboard/ run `npm run deploy`\n```\n\n**npm Publishing Notes:**\n- Classic npm tokens were revoked Dec 2025. Use **granular access tokens** from [npmjs.com/settings/~/tokens](https://www.npmjs.com/settings/~/tokens)\n- The token must have **Read and Write** permissions for `claude-code-templates` and **\"Bypass 2FA\"** enabled\n- Always remove the token from npm config after publishing (`npm config delete`)\n- The repository disables lifecycle scripts by default for security. Pass `--ignore-scripts=false` only to the trusted release commands shown above so their version and prepublish hooks can run.\n- The local `package.json` version may drift from npm if published from CI — always check `npm view claude-code-templates version` first\n- Never hardcode or commit tokens\n\n## API Architecture\n\n### Critical Endpoints\n\nAPI endpoints live as Astro API routes in `dashboard/src/pages/api/`:\n\n**`/api/track-download-supabase`** (CRITICAL)\n- Tracks component downloads for analytics\n- Used by CLI on every installation\n- Database: Supabase (component_downloads table)\n\n**`/api/discord/interactions`**\n- Discord bot slash commands\n- Features: /search, /info, /install, /popular\n\n**`/api/claude-code-check`**\n- Monitors Claude Code releases\n- Triggered every 30 minutes by the `cloudflare-workers/crons` Worker (not a Vercel cron)\n- Database: Neon (claude_code_versions, claude_code_changes, discord_notifications_log, monitoring_metadata tables)\n\n### Shared API Libraries\n\n- `dashboard/src/lib/api/cors.ts` — CORS headers, `corsResponse()`, `jsonResponse()`\n- `dashboard/src/lib/api/neon.ts` — Neon client factory\n- `dashboard/src/lib/api/auth.ts` — Clerk JWT verification\n- `dashboard/src/lib/api/changelog-parser.ts` — Claude Code changelog parser\n\n### Emergency Rollback\n\n```bash\n# List recent Pages deployments\nnpx wrangler pages deployment list --project-name=aitmpl-dashboard\n# Roll back to a previous deployment\nnpx wrangler pages deployment rollback <deployment-id> --project-name=aitmpl-dashboard\n```\n\n## Cloudflare Workers\n\nThe `cloudflare-workers/` directory contains Cloudflare Worker projects that run independently from the dashboard Pages project.\n\n### crons\n\nReplaces the old Vercel cron jobs. On a schedule it calls the dashboard API endpoints (which stay on Cloudflare Pages) with a shared `TRIGGER_SECRET`.\n\n- `*/30 * * * *` → `/api/claude-code-check` (monitors Claude Code npm releases)\n- `0 * * * *` → `/api/health-check` (hourly; was every 15 min on Vercel, reduced to save invocations)\n\nErrors and cron check-ins are reported to Sentry (`sentry.js`, DSN from the `aitmpl-workers` project — see Error Tracking below).\n\n```bash\ncd cloudflare-workers/crons\nnpm run dev          # Local dev\nnpx wrangler deploy  # Deploy\n```\n\n**Secrets (Cloudflare):** `DASHBOARD_URL` (e.g. `https://www.aitmpl.com`), `TRIGGER_SECRET`, `SENTRY_DSN` (optional).\n\n### docs-monitor (DECOMMISSIONED 2026-07)\n\nMonitored https://code.claude.com/docs hourly with Telegram notifications. **Deleted from Cloudflare** to free a cron-trigger slot for the newsletter worker (the account's free plan allows 5 cron triggers total). The code remains in `cloudflare-workers/docs-monitor/` and can be redeployed if a slot frees up (`npx wrangler deploy`).\n\n### pulse (Weekly KPI Report)\n\nCollects metrics from GitHub, Discord, Supabase, npm, and Google Analytics every Sunday at 14:00 UTC and sends a consolidated report via Telegram.\n\n**Architecture:** Single `index.js` file (no npm dependencies at runtime). All source collectors, formatter, and Telegram sender in one file.\n\n**Cron:** `0 14 * * 0` (Sundays 14:00 UTC / 11:00 AM Chile)\n\n```bash\ncd cloudflare-workers/pulse\nnpm run dev          # Local dev\nnpx wrangler deploy  # Deploy\n\n# Manual trigger\ncurl -X POST https://pulse-weekly-report.SUBDOMAIN.workers.dev/trigger \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n\n# Test single source\ncurl -X POST \"https://pulse-weekly-report.SUBDOMAIN.workers.dev/trigger?source=github\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n\n# Dry run (no Telegram)\ncurl -X POST \"https://pulse-weekly-report.SUBDOMAIN.workers.dev/trigger?send=false\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n```\n\n**Secrets (Cloudflare):**\n```bash\nTELEGRAM_BOT_TOKEN          # Shared with docs-monitor\nTELEGRAM_CHAT_ID            # Shared with docs-monitor\nGITHUB_TOKEN                # GitHub PAT (public_repo scope)\nSUPABASE_URL                # Supabase project URL\nSUPABASE_SERVICE_ROLE_KEY   # Supabase service role key\nDISCORD_BOT_TOKEN           # Discord bot token\nDISCORD_GUILD_ID            # Discord server ID\nTRIGGER_SECRET              # For manual /trigger endpoint\nGA_PROPERTY_ID              # GA4 property ID (optional)\nGA_SERVICE_ACCOUNT_JSON     # Base64 service account (optional)\n```\n\n**Graceful degradation:** Each source catches its own errors. Missing secrets or API failures show `⚠️ Unavailable` instead of crashing the report. Failed collectors are also reported to Sentry via `sentry.js` (see Error Tracking below). The Vercel collector was removed (2026-07) since the dashboard no longer deploys to Vercel.\n\n### newsletter (Weekly Community Components Email) — PAUSED 2026-09-20\n\nComposes and sends a simple weekly email via Resend featuring trending components (one Skill, Agent, MCP, Hook and Setting per send, in that fixed order). Selection is weighted-random by recent downloads and the copy (subject, catalog intro, per-component sentences, stats cited, closer) rotates from pools so no two emails read the same. Body is plain text plus a minimal HTML version (bold + underlined component titles, clickable component links). Data comes from the live `trending-data.json` + `components.json`.\n\n**Delivery:** Resend **Broadcast** targeting the segment in `RESEND_SEGMENT_ID` — Resend injects the per-recipient unsubscribe link (`{{{RESEND_UNSUBSCRIBE_URL}}}` placeholder in the body) and manages the suppression list automatically. Replies go to `NEWSLETTER_REPLY_TO`. The segment is the safety gate: point it at a pilot segment for tests or the full-audience segment for community-wide sends. Open/click tracking is enabled on the `aitmpl.com` domain with tracking subdomain `track.aitmpl.com` (metrics per broadcast at resend.com/broadcasts). **The weekly send is paused** (2026-09-20) because the emails were reading as spam; the worker stays deployed and only the automatic send is off. `wrangler.toml` has `crons = []` (was `0 16 * * SUN`, a slot freed by decommissioning docs-monitor) and `NEWSLETTER_ENABLED = \"false\"` makes `scheduled()` a no-op, so restoring the trigger alone does not resume sending. Repo edits do not reach production: no GitHub Action deploys this worker, so a pause or a resume only lands via `npx wrangler deploy` from `cloudflare-workers/newsletter/`. `GET /preview?format=text` composes without sending and `POST /trigger` sends (`?send=false` for dry run) — both still work while paused.\n\n```bash\ncd cloudflare-workers/newsletter\nnpm run dev          # Local dev\nnpx wrangler deploy  # Deploy\n\n# Preview content without sending (repeat to see the copy rotate)\ncurl \"https://aitmpl-newsletter.SUBDOMAIN.workers.dev/preview?format=text\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n\n# Real send: creates + sends a Broadcast to the segment in RESEND_SEGMENT_ID\ncurl -X POST \"https://aitmpl-newsletter.SUBDOMAIN.workers.dev/trigger\" \\\n  -H \"Authorization: Bearer $TRIGGER_SECRET\"\n```\n\n**Secrets (Cloudflare):** `RESEND_API_KEY` (full access — broadcasts/segments), `RESEND_SEGMENT_ID`, `NEWSLETTER_REPLY_TO`, `TRIGGER_SECRET`, `SENTRY_DSN` (optional). Public vars in `wrangler.toml [vars]`: `DASHBOARD_URL`, `RESEND_FROM_EMAIL` (`daniel.avila@aitmpl.com`), `NEWSLETTER_ENABLED` (currently `\"false\"` — the kill switch described above).\n\n## Error Tracking (Sentry)\n\nFree-tier Sentry, added to close the gap where automated cron/worker failures\nwere previously invisible. No official `@sentry/*` SDK is used anywhere —\nevery surface has its own tiny dependency-free client that posts directly to\nthe Sentry envelope API via `fetch()`, matching this repo's zero-dependency\nworker style and avoiding Cloudflare Pages SSR friction with `@sentry/astro`.\n\n**Status as of 2026-07-04: all 3 projects live and verified end-to-end** (each\nconfirmed with a manual test event returning HTTP 200 from Sentry and\nappearing in its Issues dashboard).\n\n- ✅ **Cloudflare Workers** (Sentry project `aitmpl-workers`) — `SENTRY_DSN`\n  secret set on all 3 workers (`aitmpl-crons`, `pulse-weekly-report`,\n  `claude-docs-monitor`) via `wrangler secret put SENTRY_DSN`.\n- ✅ **Dashboard** (Sentry project `aitmpl-dashboard`) — `SENTRY_DSN` set as a\n  Cloudflare Pages secret (`wrangler pages secret put SENTRY_DSN\n  --project-name=aitmpl-dashboard`). Wired into `captureApiError()` calls in\n  `claude-code-check`, `health-check`, and the three `track-*` endpoints.\n- ✅ **CLI** (Sentry project `aitmpl-cli`) — the DSN is public by design\n  (send-only, not a secret) and ships **hardcoded as the default** in\n  `cli-tool/src/error-reporting.js` (`DEFAULT_SENTRY_DSN`, overridable via\n  `CCT_SENTRY_DSN` for testing against a different project). Reporting\n  itself stays **opt-in**: requires the end user to set\n  `CCT_ERROR_REPORTING=true`, and always defers to the existing\n  `CCT_NO_TRACKING`/`CCT_NO_ANALYTICS`/`CI` opt-outs.\n\n**Not yet configured (any surface):** Sentry alert rules to Discord/Telegram,\nand Cron Monitors dashboards for the workers' scheduled check-ins (the\n`checkIn()` calls already send `in_progress`/`ok`/`error` events — a Monitor\njust needs to be created in the Sentry UI with matching slugs:\n`claude-code-check`, `health-check`, `pulse-weekly-report`, `docs-monitor`).\n\n**Files:** `cloudflare-workers/{crons,pulse,docs-monitor}/sentry.js` (workers),\n`dashboard/src/lib/api/error-tracking.ts` (dashboard), `cli-tool/src/error-reporting.js` (CLI).\n\n## Dashboard (www.aitmpl.com)\n\nAstro + React + Tailwind dashboard serving both `www.aitmpl.com` and `app.aitmpl.com`. Clerk auth for user collections. Source lives in `dashboard/`. All API endpoints are Astro API routes in the same project.\n\n### Architecture\n\n- **Framework**: Astro 5 with React islands, Tailwind v4, `output: 'server'`, `@astrojs/cloudflare` adapter (`mode: 'directory'`)\n- **Hosting**: Cloudflare Pages (project `aitmpl-dashboard`), SSR on Workers runtime\n- **Auth**: Clerk (`window.Clerk` global, no ClerkProvider per island)\n- **Data**: `components.json` and `trending-data.json` served from `dashboard/public/` (same-origin)\n- **APIs**: All endpoints in `dashboard/src/pages/api/` (Astro API routes, no separate serverless project)\n\n### Featured Pages (`/featured/[slug]`)\n\nFeatured partner integrations shown on the dashboard homepage. Two files to edit:\n\n**`dashboard/src/lib/constants.ts`** — `FEATURED_ITEMS` array. Each entry has:\n- `name`, `description`, `logo`, `url` (`/featured/slug`), `tag`, `tagColor`, `category`\n- `ctaLabel`, `ctaUrl`, `websiteUrl`\n- `installCommand` — shown in the sidebar Quick Install box\n- `metadata` — key/value pairs shown in the Details sidebar (e.g. `Components: '8'`)\n- `links` — sidebar links list\n\n**`dashboard/src/pages/featured/[slug].astro`** — Content for each slug rendered via `{slug === 'brightdata' && (...)}` blocks. Each block contains the full HTML content for that partner page.\n\n**When adding a skill to a featured page:**\n1. Add a new card `<div class=\"flex gap-3 ...\">` inside the Skills Layer section of the relevant `{slug === '...'}` block\n2. Update `installCommand` in `constants.ts` to include the new skill\n3. Increment `metadata.Components` count in `constants.ts`\n\nCurrent featured slugs: `brightdata`, `neon-instagres`, `claudekit`, `braingrid`\n\n### Cloudflare Pages Project Setup\n\nA single Cloudflare Pages project (`aitmpl-dashboard`) serves all domains. Config lives in `dashboard/wrangler.toml`:\n\n| Project | Domains | Root Directory | Build output |\n|---------|---------|----------------|--------------|\n| `aitmpl-dashboard` | `www.aitmpl.com`, `aitmpl.com` (redirect), `app.aitmpl.com` | `dashboard` | `dist` |\n\n`wrangler.toml` sets `pages_build_output_dir = \"./dist\"`, `compatibility_flags = [\"nodejs_compat\"]`, and the `PUBLIC_*` build-time vars in `[vars]`. Secrets are set via the Cloudflare Dashboard or `wrangler pages secret put`.\n\n### Deployment\n\n**ALWAYS use the deployer agent (`.claude/agents/deployer.md`) for all deployments.** It runs pre-deploy checks (auth, git status, build) and handles the full pipeline safely. Never deploy manually.\n\n```bash\nnpm run deploy             # Build + `wrangler pages deploy dist` for www + app.aitmpl.com\nnpm run deploy:dashboard   # Same as above\n```\n\n**CI/CD**: Pushes to `main` auto-deploy via GitHub Actions (`.github/workflows/deploy.yml`):\n- Changes in `dashboard/**` trigger a build and `wrangler pages deploy dist --project-name=aitmpl-dashboard`\n\n**Required GitHub Secrets** (Settings > Secrets > Actions):\n- `CLOUDFLARE_API_TOKEN` — Cloudflare API token with Pages edit permission\n- `CLOUDFLARE_ACCOUNT_ID` — Cloudflare account ID\n\n### Environment Variables (Cloudflare)\n\n`PUBLIC_*` vars are build-time and live in `dashboard/wrangler.toml` `[vars]` (and are also passed to the GitHub Actions build step). Everything else is a Cloudflare secret (`wrangler pages secret put <NAME>` or the Pages dashboard):\n\n```bash\n# Clerk\nPUBLIC_CLERK_PUBLISHABLE_KEY=xxx   # [vars] — build-time\nCLERK_SECRET_KEY=xxx               # secret\n\n# Data\nPUBLIC_COMPONENTS_JSON_URL=/components.json   # [vars] — build-time\n\n# GitHub OAuth\nPUBLIC_GITHUB_CLIENT_ID=xxx        # [vars] — build-time\nGITHUB_CLIENT_SECRET=xxx           # secret\n\n# Supabase (download tracking)\nSUPABASE_URL=https://xxx.supabase.co        # secret\nSUPABASE_SERVICE_ROLE_KEY=xxx               # secret\n\n# Neon Database\nNEON_DATABASE_URL=postgresql://user:pass@host/db?sslmode=require   # secret\n\n# Discord\nDISCORD_APP_ID=xxx                 # secret\nDISCORD_BOT_TOKEN=xxx              # secret\nDISCORD_PUBLIC_KEY=xxx             # secret\nDISCORD_WEBHOOK_URL_CHANGELOG=https://discord.com/api/webhooks/xxx   # secret\n```\n\n### Known Issues & Solutions\n\n**Node built-ins in SSR**\n- The Cloudflare Workers runtime does not expose Node's `fs`/`path`/etc. by default. `astro.config.mjs` enables `nodejs_compat` (via `wrangler.toml`) and externalizes `node:fs`, `node:path`, `node:url`, `node:stream` in SSR. Avoid adding new hard dependencies on Node-only APIs in server code.\n\n**`react-dom/server` on Cloudflare**\n- `astro.config.mjs` aliases `react-dom/server` to `react-dom/server.node` and marks `react-dom` as `noExternal` at build time so React SSR works on the Workers runtime. Don't remove this alias.\n\n### Local Development\n\n```bash\ncd dashboard\nnpm install\nnpx astro dev --port 4321   # Dashboard + APIs at http://localhost:4321\n```\n\n## Data Files\n\n### Component Catalog\n\n- `docs/components.json` — Full generated catalog (source of truth), keeps `content` and `security` fields (needed by the legacy static site)\n- `dashboard/public/components.json` — Dashboard copy, **without** `content`/`security` (lighter payload; dashboard doesn't need them)\n- `dashboard/public/counts.json` — Per-type counts only (e.g. `{\"agents\": 421, ...}`), used by the sidebar/plugins pages instead of loading the full catalog\n- `dashboard/public/components/{type}.json` — One file per component type (agents.json, commands.json, etc.), loaded on demand by `ComponentGrid.tsx` for the active tab\n- `dashboard/public/search-index.json` — Flat array for `SearchModal.tsx`\n- `dashboard/public/component-content/{type}/{slug}.json` — Full per-component content (incl. markdown body), fetched on demand when a component's detail view or PR flow needs it\n- `dashboard/public/trending-data.json` — Trending/download stats\n- `dashboard/public/api/v1/` — **Catalog API v1, the public contract** (see below)\n\nAll of the above are served as static Cloudflare Pages assets. The catalog\nfiles (`components.json`, `counts.json`, `components/*`, `search-index.json`,\n`component-content/*`) use `max-age=300, stale-while-revalidate=3600`, so a\ndeploy reaches browsers within minutes; `trending-data.json` keeps 1 h (see\n`dashboard/public/_headers`). With the old 24 h `max-age` a browser kept a\nstale `components/{type}.json` for a day after a deploy while `counts.json`\nwas already new: the sidebar counted a component the grid could not find.\n\n### Catalog API v1 (public contract)\n\nOutside code reads the catalog (known: `profullstack/niche-db` reads\n`docs/components.json` from raw.githubusercontent.com; our own\n`mods/productivity/aitmpl` reads `components/{type}.json`, `counts.json` and\n`trending-data.json` from www.aitmpl.com, and installed copies never update).\n`/api/v1/` is the versioned surface for them: `catalog.json`,\n`catalog/{type}.json`, `version.json` (sha256 for cheap polling) and\n`schema.json`, written by `scripts/catalog_api_v1.py` (called at the end of\n`generate_components_json.py`) from `scripts/catalog_api_v1.schema.json`.\nDocs page `/developers/catalog-api`; audit and design in\n`docs/catalog-api-formalization.md`.\n\n- **Within v1, changes are additive only.** Removing or renaming a field,\n  changing a type, the `id` format or a URL needs `/api/v2/` with v1 kept for 6\n  months. Record every format change in `docs/catalog-api/CHANGELOG.md`.\n- Adding a field: add it to the schema (`$defs.component`, also `required`),\n  bump `SCHEMA_VERSION` minor, add a changelog entry. The writer refuses\n  undeclared fields and validates every run (fatal in CI, where `jsonschema`\n  is installed); `catalog-api-v1.yml` runs the tests on PRs.\n- Never add sponsored/placement data to v1, and never order it by anything but `id`.\n- `generatedAt` only moves when the components changed, so a no-op run stays byte-identical.\n- The legacy files stay the site's own data, but treat their current shape as\n  frozen in practice (installed `/aitmpl` mods read them), and do not delete\n  `docs/components.json` in a legacy-site cleanup: niche-db reads it.\n\n### Data Flow\n\n1. `scripts/generate_components_json.py` scans `cli-tool/components/`\n2. Generates `docs/components.json` (full, with `content`/`security`) and the split dashboard artifacts (`dashboard/public/components.json`, `counts.json`, `components/{type}.json`, `search-index.json`, `component-content/{type}/{slug}.json`) — these two writes are decoupled, so the dashboard payload stays lean without touching the legacy catalog — then the Catalog API v1 files under `dashboard/public/api/v1/`\n3. Dashboard islands (`ComponentGrid.tsx`, `SearchModal.tsx`, `Sidebar.astro`, `SendToRepoModal.tsx`) load the split artifacts instead of the full catalog\n4. `scripts/generate_trending_data.py` writes only `docs/trending-data.json`; `update-json-data.yml` copies it to `dashboard/public/trending-data.json`, which is the one `TrendingView.tsx` and the home counters actually fetch\n5. Download tracking via `/api/track-download-supabase`\n\n### Plugins & Marketplaces Catalog\n\n- `scripts/generate_plugins_json.py` — scans the repos listed in `REPOS` via the `gh` CLI (needs `gh auth login`) and writes `dashboard/public/plugins.json`. This is a **manual, offline step** — it does not run during `npm run build` or CI/CD, so re-running it never affects deploy time.\n- For each marketplace it records `plugins_list[].components` (counts per type) and `plugins_list[].components_items` (`{name, description}` per command/agent/skill/hook/mcp/lsp, description parsed from the item's frontmatter). The dashboard's `/plugins/[slug].astro` page renders this through `MarketplacePluginsList.tsx`, which shows a search box and a \"view details\" modal per plugin.\n- **`max_local_scans = 50`** in `extract_marketplace_plugins_detail()` caps how many *locally-sourced* plugins (i.e. `source: \"./plugins/...\"` within the marketplace's own repo) get scanned for real component names/descriptions, per marketplace, to bound GitHub API calls. Plugins beyond that cap (or plugins hosted in an external repo, which are never scanned) fall back to showing only tag badges in the modal, with no itemized breakdown — this is a graceful degradation, not an error.\n  - As of 2026-07-11, `anthropics/claude-plugins-official` alone has 51 locally-sourced plugins (out of 255 total), i.e. already at the edge of this cap. Bump `max_local_scans` if more complete coverage is needed — GitHub's rate limit (5000 req/hour authenticated) is not the constraint, wall-clock run time is (each item now costs 1 extra API call to fetch its file content for the description).\n\n### Legacy Static Site (docs/)\n\nThe `docs/` directory contains the old static HTML site (no longer deployed to www). GitHub Pages still builds it from repository settings and publishes it at `davila7.github.io/claude-code-templates`; nothing in production depends on that, and every article canonicalises to `aitmpl.com`. Exception: `docs/components.json` is read by external code from raw.githubusercontent.com (see Catalog API v1), so keep generating it. Blog articles in `docs/blog/` are still referenced externally.\n\n### Blog Article Creation\n\nUse the CLI skill to create blog articles:\n\n```bash\n/create-blog-article @cli-tool/components/{type}/{category}/{name}.json\n```\n\nThis automatically:\n1. Generates AI cover image\n2. Creates HTML with SEO optimization\n3. Updates `docs/blog/blog-articles.json`\n\n**The site serves `dashboard/public/blog/`, not `docs/blog/`.** The skill writes\nonly the `docs/` copy, so after creating an article you must mirror it across —\nthe article directory, its cover under `assets/`, and the new entry in\n`blog-articles.json` — or it never appears on aitmpl.com. Three articles were\nstranded this way before 2026-09-20. `daily-blog-discord.yml` picks from\n`docs/blog/blog-articles.json` and links to `aitmpl.com/blog/<slug>/`, so an\nunmirrored article is posted to Discord as a 404. Keep the two\n`blog-articles.json` files identical.\n\n## Code Standards\n\n### Path Handling\n- Use relative paths: `.claude/scripts/`, `.claude/hooks/`\n- Never hardcode absolute paths or home directories\n- Use `path.join()` for cross-platform compatibility\n\n### Naming Conventions\n- Files: `kebab-case.js`, `PascalCase.js` (for classes)\n- Functions/Variables: `camelCase`\n- Constants: `UPPER_SNAKE_CASE`\n- Components: `hyphenated-names`\n\n### Error Handling\n- Use try/catch for async operations\n- Provide helpful error messages\n- Log errors with context\n- Implement fallback mechanisms\n\n## Testing\n\n```bash\nnpm test                 # Run all tests\nnpm run test:watch      # Watch mode\nnpm run test:coverage   # Coverage report\n```\n\nAim for 70%+ test coverage. Test critical paths and error handling.\n\n## Common Issues\n\n**API endpoint returns 404 after deploy**\n- API routes must be in `dashboard/src/pages/api/` as Astro API routes\n- Export named HTTP methods: `export const POST: APIRoute`, `export const GET: APIRoute`\n\n**Download tracking not working**\n- Check Cloudflare Pages logs: `npx wrangler pages deployment tail --project-name=aitmpl-dashboard`\n- Verify environment variables / secrets in the Cloudflare Pages dashboard\n- Test endpoint manually with curl\n\n**Components not updating on website**\n- Run `python scripts/generate_components_json.py` (writes both `docs/components.json` and the split `dashboard/public/` artifacts directly — no manual copy step)\n- Deploy, then wait up to 5 min or hard-reload (catalog JSON is browser-cached 5 min, see `dashboard/public/_headers`)\n\n## Important Notes\n\n- **Component catalog**: Always regenerate after adding/modifying components\n- **API tests**: Required before production deploy (breaks download tracking)\n- **Secrets**: Never commit API keys (use environment variables)\n- **Paths**: Use relative paths for all project files\n- **Backwards compatibility**: Don't break existing component installations\n\n<!-- File: .claude-plugin/marketplace.json (Tokens: ~300 | Category: marketplace) -->\n# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/.claude-plugin/marketplace.json)\n\n<!-- File: .claude-plugin/plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/.claude-plugin/plugin.json)\n\n<!-- File: .cursor/mcp.json (Tokens: ~300 | Category: mcp-config) -->\n# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/davila7/claude-code-templates/HEAD/.cursor/mcp.json)\n</yakaai_skills>"}