skills-ws 1.5.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -5
  3. package/bin/cli.mjs +93 -115
  4. package/package.json +18 -29
  5. package/CHANGELOG.md +0 -39
  6. package/REVIEW.md +0 -516
  7. package/app/cli/page.tsx +0 -100
  8. package/app/docs/page.tsx +0 -117
  9. package/app/faq/page.tsx +0 -92
  10. package/app/globals.css +0 -25
  11. package/app/layout.tsx +0 -248
  12. package/app/not-found.tsx +0 -22
  13. package/app/page.tsx +0 -141
  14. package/app/sitemap.ts +0 -25
  15. package/app/skills/[name]/page.tsx +0 -192
  16. package/components/AsciiBackground.tsx +0 -207
  17. package/components/FaqAccordion.tsx +0 -46
  18. package/components/InstallBox.tsx +0 -43
  19. package/components/NpmDownloads.tsx +0 -43
  20. package/components/SkillContent.tsx +0 -99
  21. package/components/SkillsGrid.tsx +0 -132
  22. package/lib/skills.ts +0 -47
  23. package/next-env.d.ts +0 -5
  24. package/next.config.mjs +0 -7
  25. package/npm-package.json +0 -56
  26. package/postcss.config.mjs +0 -8
  27. package/public/favicon.ico +0 -0
  28. package/public/favicon.svg +0 -4
  29. package/public/install.sh +0 -111
  30. package/public/llms-full.txt +0 -1000
  31. package/public/llms.txt +0 -83
  32. package/public/og.png +0 -0
  33. package/public/robots.txt +0 -51
  34. package/public/skills.json +0 -2444
  35. package/skills-data/ab-testing/SKILL.md +0 -171
  36. package/skills-data/accounting-finance/SKILL.md +0 -139
  37. package/skills-data/affiliate-marketing/SKILL.md +0 -152
  38. package/skills-data/ai-agent-design/SKILL.md +0 -207
  39. package/skills-data/api-design/SKILL.md +0 -226
  40. package/skills-data/ascii-banner/SKILL.md +0 -317
  41. package/skills-data/bing-webmaster/SKILL.md +0 -130
  42. package/skills-data/blog-engine/SKILL.md +0 -91
  43. package/skills-data/brand-strategy/SKILL.md +0 -205
  44. package/skills-data/business-development/SKILL.md +0 -140
  45. package/skills-data/cicd-pipelines/SKILL.md +0 -232
  46. package/skills-data/cold-outreach/SKILL.md +0 -169
  47. package/skills-data/community-building/SKILL.md +0 -144
  48. package/skills-data/competitor-intelligence/SKILL.md +0 -145
  49. package/skills-data/content-strategy/SKILL.md +0 -85
  50. package/skills-data/copywriting/SKILL.md +0 -88
  51. package/skills-data/crm-builder/SKILL.md +0 -86
  52. package/skills-data/crm-operations/SKILL.md +0 -148
  53. package/skills-data/customer-acquisition/SKILL.md +0 -133
  54. package/skills-data/customer-feedback/SKILL.md +0 -140
  55. package/skills-data/data-analytics/SKILL.md +0 -203
  56. package/skills-data/data-management/SKILL.md +0 -199
  57. package/skills-data/database-design/SKILL.md +0 -158
  58. package/skills-data/email-sequence/SKILL.md +0 -81
  59. package/skills-data/eu-legal-compliance/SKILL.md +0 -156
  60. package/skills-data/git-workflow/SKILL.md +0 -200
  61. package/skills-data/google-analytics/SKILL.md +0 -188
  62. package/skills-data/growth-hacking/SKILL.md +0 -75
  63. package/skills-data/hiring-team-building/SKILL.md +0 -172
  64. package/skills-data/influencer-marketing/SKILL.md +0 -181
  65. package/skills-data/landing-page-builder/SKILL.md +0 -93
  66. package/skills-data/lead-scoring/SKILL.md +0 -64
  67. package/skills-data/local-seo/SKILL.md +0 -70
  68. package/skills-data/marketing-analytics/SKILL.md +0 -106
  69. package/skills-data/mvp-launcher/SKILL.md +0 -145
  70. package/skills-data/nextjs-stack/SKILL.md +0 -231
  71. package/skills-data/page-cro/SKILL.md +0 -80
  72. package/skills-data/paid-ads/SKILL.md +0 -110
  73. package/skills-data/popup-cro/SKILL.md +0 -52
  74. package/skills-data/pr-media-outreach/SKILL.md +0 -140
  75. package/skills-data/pricing-optimization/SKILL.md +0 -316
  76. package/skills-data/programmatic-seo/SKILL.md +0 -65
  77. package/skills-data/project-management/SKILL.md +0 -161
  78. package/skills-data/prompt-engineering/SKILL.md +0 -189
  79. package/skills-data/retention-analytics/SKILL.md +0 -161
  80. package/skills-data/revenue-operations/SKILL.md +0 -162
  81. package/skills-data/sales-funnel/SKILL.md +0 -56
  82. package/skills-data/search-console/SKILL.md +0 -159
  83. package/skills-data/security-hardening/SKILL.md +0 -176
  84. package/skills-data/seo-geo/SKILL.md +0 -138
  85. package/skills-data/signup-flow-cro/SKILL.md +0 -64
  86. package/skills-data/smart-contract-auditor/SKILL.md +0 -64
  87. package/skills-data/social-media-growth/SKILL.md +0 -146
  88. package/skills-data/social-media-kit/SKILL.md +0 -56
  89. package/skills-data/testing-strategy/SKILL.md +0 -344
  90. package/skills-data/ui-ux-pro-max/SKILL.md +0 -72
  91. package/skills-data/virustotal/SKILL.md +0 -108
  92. package/skills-data/web-performance/SKILL.md +0 -219
  93. package/skills-data/webinar-events/SKILL.md +0 -148
  94. package/skills-data/yandex-webmaster/SKILL.md +0 -159
  95. package/skills.json +0 -2444
  96. package/tailwind.config.ts +0 -45
  97. package/tsconfig.json +0 -26
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Commit Media SARL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # skills.ws
2
2
 
3
- Agent skills for AI coding assistants. 77 skills across 8 categories — built for OpenClaw, Claude Code, Cursor, Codex, and any agent that supports the SKILL.md format.
3
+ Agent skills for AI coding assistants. 81 skills across 8 categories — built for OpenClaw, Claude Code, Cursor, Codex, and any agent that supports the SKILL.md format.
4
4
 
5
5
  **Website:** [skills.ws](https://skills.ws) | **npm:** [skills-ws](https://www.npmjs.com/package/skills-ws) | **Docs:** [llms-full.txt](https://skills.ws/llms-full.txt)
6
6
 
@@ -19,7 +19,7 @@ Skills are `SKILL.md` files that give AI coding assistants specialized knowledge
19
19
 
20
20
  ---
21
21
 
22
- ## Skills (77 across 8 categories)
22
+ ## Skills (81 across 8 categories)
23
23
 
24
24
  ### Marketing (15)
25
25
  SEO/GEO, content strategy, copywriting, paid ads, email sequences, PR/media, influencer marketing, brand strategy, webinars, blog engine, and more.
@@ -156,9 +156,9 @@ skills-ws/
156
156
  │ └── NpmDownloads.tsx # Live npm download counter
157
157
  ├── lib/
158
158
  │ └── skills.ts # Skill data access + TypeScript interfaces
159
- ├── skills/ # Raw SKILL.md files (77 directories)
159
+ ├── skills/ # Raw SKILL.md files (81 directories)
160
160
  ├── public/
161
- │ ├── skills.json # Skills database (77 skills, all metadata + content)
161
+ │ ├── skills.json # Skills database (81 skills, all metadata + content)
162
162
  │ ├── llms.txt # LLM-readable skill index
163
163
  │ ├── llms-full.txt # Full content dump for LLMs
164
164
  │ ├── robots.txt # Crawl directives
@@ -170,7 +170,7 @@ skills-ws/
170
170
 
171
171
  Static export generates ~85 pages:
172
172
  - Homepage + docs + CLI + FAQ + 404
173
- - 77 individual skill detail pages
173
+ - 81 individual skill detail pages
174
174
  - XML sitemap
175
175
 
176
176
  No server needed — deploy to any static host (Vercel, Netlify, GitHub Pages, S3).
package/bin/cli.mjs CHANGED
@@ -2,9 +2,8 @@
2
2
 
3
3
  import { readdir, readFile, copyFile, mkdir, stat } from "node:fs/promises";
4
4
  import { join, dirname } from "node:path";
5
- import { createInterface } from "node:readline";
6
- import { fileURLToPath } from "node:url";
7
5
  import readline from "node:readline";
6
+ import { fileURLToPath } from "node:url";
8
7
 
9
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
9
  const SKILLS_DIR = join(__dirname, "..", "skills");
@@ -266,142 +265,121 @@ async function detectTarget() {
266
265
  return defaultTarget;
267
266
  }
268
267
 
269
- // ── Main ─────────────────────────────────────────────────────
270
-
271
- async function main() {
272
- const args = process.argv.slice(2);
273
- const skills = await getSkills();
274
-
275
- await playBanner();
268
+ // ── Interactive Picker ───────────────────────────────────────
276
269
 
277
- if (args[0] === "list" || args[0] === "ls") {
278
- for (const s of skills) {
279
- process.stdout.write(` ${GREEN}${s.name.padEnd(24)}${R}${DIM}${s.desc.slice(0, 55)}${R}\n`);
280
- }
281
- process.stdout.write(`\n ${DIM}${skills.length} skills | npx skills-ws install <name>${R}\n\n`);
282
- return;
270
+ async function interactivePick(skills) {
271
+ for (let i = 0; i < skills.length; i++) {
272
+ process.stdout.write(
273
+ ` ${GRAY}${String(i + 1).padStart(2)}${R} ${GREEN}${skills[i].name.padEnd(24)}${R}${DIM}${skills[i].desc.slice(0, 50)}${R}\n`
274
+ );
283
275
  }
284
-
285
- if (args[0] === "install" || args[0] === "add") {
286
- const names = args.slice(1);
287
-
288
- if (names.length === 0) {
289
- for (let i = 0; i < skills.length; i++) {
290
- process.stdout.write(
291
- ` ${GRAY}${String(i + 1).padStart(2)}${R} ${GREEN}${skills[i].name.padEnd(24)}${R}${DIM}${skills[i].desc.slice(0, 50)}${R}\n`
292
- );
293
- }
294
- process.stdout.write(`\n ${YELLOW}Enter numbers or names (comma-separated), or 'all':${R}\n`);
295
-
296
- const rl = createInterface({ input: process.stdin, output: process.stdout });
297
- const answer = await new Promise((resolve) => rl.question(` ${CYAN}> ${R}`, resolve));
298
- rl.close();
299
-
300
- if (answer.trim().toLowerCase() === "all") {
301
- names.push(...skills.map((s) => s.name));
302
- } else {
303
- for (const part of answer.split(",").map((s) => s.trim()).filter(Boolean)) {
304
- const num = parseInt(part);
305
- if (!isNaN(num) && num >= 1 && num <= skills.length) names.push(skills[num - 1].name);
306
- else names.push(part);
307
- }
308
- }
276
+ process.stdout.write(`\n ${YELLOW}Enter numbers or names (comma-separated), or 'all':${R}\n`);
277
+
278
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
279
+ const answer = await new Promise((resolve) => rl.question(` ${CYAN}> ${R}`, resolve));
280
+ rl.close();
281
+
282
+ const names = [];
283
+ if (answer.trim().toLowerCase() === "all") {
284
+ names.push(...skills.map((s) => s.name));
285
+ } else {
286
+ for (const part of answer.split(",").map((s) => s.trim()).filter(Boolean)) {
287
+ const num = parseInt(part);
288
+ if (!isNaN(num) && num >= 1 && num <= skills.length) names.push(skills[num - 1].name);
289
+ else names.push(part);
309
290
  }
291
+ }
292
+ return names;
293
+ }
310
294
 
311
- if (names.length === 0) {
312
- process.stdout.write(` ${DIM}Nothing selected.${R}\n`);
313
- return;
314
- }
295
+ async function installSkills(names, skills, customDir) {
296
+ if (names.length === 0) {
297
+ process.stdout.write(` ${DIM}Nothing selected.${R}\n`);
298
+ return;
299
+ }
315
300
 
316
- const target = await detectTarget();
317
- process.stdout.write(`\n ${DIM}${target}${R}\n\n`);
301
+ const target = customDir || await detectTarget();
302
+ process.stdout.write(`\n ${DIM}${target}${R}\n\n`);
318
303
 
319
- let installed = 0;
320
- for (const name of names) {
321
- const skill = skills.find((s) => s.name === name);
322
- if (!skill) {
323
- process.stdout.write(` ${YELLOW}skip${R} ${name} ${DIM}(not found)${R}\n`);
324
- continue;
325
- }
326
- await copyDir(skill.dir, join(target, name));
327
- installed++;
304
+ let installed = 0;
305
+ for (const name of names) {
306
+ const skill = skills.find((s) => s.name === name);
307
+ if (!skill) {
308
+ process.stdout.write(` ${YELLOW}skip${R} ${name} ${DIM}(not found)${R}\n`);
309
+ continue;
328
310
  }
311
+ await copyDir(skill.dir, join(target, name));
312
+ installed++;
313
+ }
329
314
 
330
- await playInstallProgress(installed);
331
- process.stdout.write("\n");
315
+ await playInstallProgress(installed);
316
+ process.stdout.write("\n");
332
317
 
333
- for (const name of names) {
334
- if (skills.find((s) => s.name === name)) {
335
- process.stdout.write(` ${GREEN}+${R} ${name}\n`);
336
- }
318
+ for (const name of names) {
319
+ if (skills.find((s) => s.name === name)) {
320
+ process.stdout.write(` ${GREEN}+${R} ${name}\n`);
337
321
  }
338
- process.stdout.write(`\n ${DIM}skills.ws${R}\n\n`);
339
- return;
340
322
  }
323
+ process.stdout.write(`\n ${DIM}skills.ws${R}\n\n`);
324
+ }
341
325
 
342
- // No args = interactive install (same as `install`)
343
- if (!args[0] || args[0] === "help" || args[0] === "-h" || args[0] === "--help") {
344
- if (args[0] === "help" || args[0] === "-h" || args[0] === "--help") {
345
- process.stdout.write(` ${B}Usage:${R}\n\n`);
346
- process.stdout.write(` ${CYAN}npx skills-ws${R} Interactive picker\n`);
347
- process.stdout.write(` ${CYAN}npx skills-ws list${R} List all skills\n`);
348
- process.stdout.write(` ${CYAN}npx skills-ws install <name>${R} Install specific skill(s)\n`);
349
- process.stdout.write(` ${CYAN}npx skills-ws install all${R} Install everything\n`);
350
- process.stdout.write(`\n ${DIM}${skills.length} skills | skills.ws${R}\n\n`);
351
- return;
352
- }
353
-
354
- // Interactive picker
355
- for (let i = 0; i < skills.length; i++) {
356
- process.stdout.write(
357
- ` ${GRAY}${String(i + 1).padStart(2)}${R} ${GREEN}${skills[i].name.padEnd(24)}${R}${DIM}${skills[i].desc.slice(0, 50)}${R}\n`
358
- );
359
- }
360
- process.stdout.write(`\n ${YELLOW}Enter numbers or names (comma-separated), or 'all':${R}\n`);
326
+ // ── Main ─────────────────────────────────────────────────────
361
327
 
362
- const rl = createInterface({ input: process.stdin, output: process.stdout });
363
- const answer = await new Promise((resolve) => rl.question(` ${CYAN}> ${R}`, resolve));
364
- rl.close();
328
+ async function main() {
329
+ const rawArgs = process.argv.slice(2);
330
+ const skills = await getSkills();
365
331
 
366
- const names = [];
367
- if (answer.trim().toLowerCase() === "all") {
368
- names.push(...skills.map((s) => s.name));
332
+ // Parse --dir flag from anywhere in args
333
+ let customDir = null;
334
+ const args = [];
335
+ for (let i = 0; i < rawArgs.length; i++) {
336
+ if (rawArgs[i] === "--dir" && i + 1 < rawArgs.length) {
337
+ customDir = rawArgs[i + 1];
338
+ i++; // skip next
369
339
  } else {
370
- for (const part of answer.split(",").map((s) => s.trim()).filter(Boolean)) {
371
- const num = parseInt(part);
372
- if (!isNaN(num) && num >= 1 && num <= skills.length) names.push(skills[num - 1].name);
373
- else names.push(part);
374
- }
340
+ args.push(rawArgs[i]);
375
341
  }
342
+ }
376
343
 
377
- if (names.length === 0) {
378
- process.stdout.write(` ${DIM}Nothing selected.${R}\n`);
379
- return;
344
+ await playBanner();
345
+
346
+ if (args[0] === "list" || args[0] === "ls") {
347
+ for (const s of skills) {
348
+ process.stdout.write(` ${GREEN}${s.name.padEnd(24)}${R}${DIM}${s.desc.slice(0, 55)}${R}\n`);
380
349
  }
350
+ process.stdout.write(`\n ${DIM}${skills.length} skills | npx skills-ws install <name>${R}\n\n`);
351
+ return;
352
+ }
381
353
 
382
- const target = await detectTarget();
383
- process.stdout.write(`\n ${DIM}${target}${R}\n\n`);
354
+ if (args[0] === "install" || args[0] === "add") {
355
+ let names = args.slice(1);
384
356
 
385
- let installed = 0;
386
- for (const name of names) {
387
- const skill = skills.find((s) => s.name === name);
388
- if (!skill) {
389
- process.stdout.write(` ${YELLOW}skip${R} ${name} ${DIM}(not found)${R}\n`);
390
- continue;
391
- }
392
- await copyDir(skill.dir, join(target, name));
393
- installed++;
357
+ // Handle "all" keyword
358
+ if (names.includes("all")) {
359
+ names = skills.map((s) => s.name);
360
+ } else if (names.length === 0) {
361
+ names = await interactivePick(skills);
394
362
  }
395
363
 
396
- await playInstallProgress(installed);
397
- process.stdout.write("\n");
364
+ await installSkills(names, skills, customDir);
365
+ return;
366
+ }
398
367
 
399
- for (const name of names) {
400
- if (skills.find((s) => s.name === name)) {
401
- process.stdout.write(` ${GREEN}+${R} ${name}\n`);
402
- }
403
- }
404
- process.stdout.write(`\n ${DIM}skills.ws${R}\n\n`);
368
+ if (args[0] === "help" || args[0] === "-h" || args[0] === "--help") {
369
+ process.stdout.write(` ${B}Usage:${R}\n\n`);
370
+ process.stdout.write(` ${CYAN}npx skills-ws${R} Interactive picker\n`);
371
+ process.stdout.write(` ${CYAN}npx skills-ws list${R} List all skills\n`);
372
+ process.stdout.write(` ${CYAN}npx skills-ws install <name>${R} Install specific skill(s)\n`);
373
+ process.stdout.write(` ${CYAN}npx skills-ws install all${R} Install everything\n`);
374
+ process.stdout.write(` ${CYAN}npx skills-ws install --dir .${R} Install to custom directory\n`);
375
+ process.stdout.write(`\n ${DIM}${skills.length} skills | skills.ws${R}\n\n`);
376
+ return;
377
+ }
378
+
379
+ // No args = interactive install
380
+ if (!args[0]) {
381
+ const names = await interactivePick(skills);
382
+ await installSkills(names, skills, customDir);
405
383
  return;
406
384
  }
407
385
  }
package/package.json CHANGED
@@ -1,33 +1,9 @@
1
1
  {
2
2
  "name": "skills-ws",
3
- "version": "1.5.0",
4
- "scripts": {
5
- "dev": "next dev",
6
- "build": "next build",
7
- "start": "next start",
8
- "lint": "next lint"
9
- },
10
- "dependencies": {
11
- "@vercel/analytics": "^1.6.1",
12
- "next": "14.2.35",
13
- "react": "^18",
14
- "react-dom": "^18",
15
- "react-markdown": "^10.1.0",
16
- "remark-gfm": "^4.0.1",
17
- "three": "^0.183.1"
18
- },
19
- "devDependencies": {
20
- "@types/node": "^20",
21
- "@types/react": "^18",
22
- "@types/react-dom": "^18",
23
- "@types/three": "^0.183.1",
24
- "postcss": "^8",
25
- "tailwindcss": "^3.4.1",
26
- "typescript": "^5"
27
- },
28
- "description": "77 expert agent skills for AI coding assistants — marketing, growth, web3, dev, design & operations. Built for OpenClaw, Claude Code, Cursor, and Codex.",
3
+ "version": "1.5.1",
4
+ "description": "81 agent skills for AI coding assistants \u2014 marketing, growth, web3, dev, design & operations. Built for OpenClaw, Claude Code, Cursor, and Codex.",
29
5
  "bin": {
30
- "skills-ws": "bin/cli.mjs"
6
+ "skills-ws": "./bin/cli.mjs"
31
7
  },
32
8
  "keywords": [
33
9
  "ai",
@@ -57,11 +33,24 @@
57
33
  "design-system",
58
34
  "auth"
59
35
  ],
36
+ "author": "Commit Media <bob@openletz.com> (https://openletz.com)",
60
37
  "license": "MIT",
61
38
  "repository": {
62
39
  "type": "git",
63
40
  "url": "https://github.com/san-npm/skills-ws"
64
41
  },
65
42
  "homepage": "https://skills.ws",
66
- "author": "skills.ws"
67
- }
43
+ "bugs": {
44
+ "url": "https://github.com/san-npm/skills-ws/issues"
45
+ },
46
+ "files": [
47
+ "bin/",
48
+ "skills/",
49
+ "README.md",
50
+ "LICENSE",
51
+ "SECURITY.md"
52
+ ],
53
+ "engines": {
54
+ "node": ">=18"
55
+ }
56
+ }
package/CHANGELOG.md DELETED
@@ -1,39 +0,0 @@
1
- # Changelog
2
-
3
- ## [Unreleased] — 2026-02-28
4
-
5
- ### Added — Premium Skills Tier
6
-
7
- **10 premium skills added:**
8
- - `aws-production-deploy` — ECS, RDS, CloudFront, Route53, SSL, monitoring, CDK/Terraform
9
- - `stripe-billing` — Subscriptions, usage-based billing, webhooks, customer portal, metering
10
- - `security-hardening` — OWASP Top 10, CSP, rate limiting, auth, pentesting checklist
11
- - `ai-agent-building` — CrewAI, LangGraph, tool use, memory systems, multi-agent orchestration
12
- - `nextjs-performance` — Core Web Vitals, ISR/SSG, edge functions, bundle analysis
13
- - `postgres-mastery` — Indexes, query optimization, partitioning, pgvector, migrations
14
- - `docker-production` — Multi-stage builds, compose, secrets, health checks, security
15
- - `api-design` — REST best practices, versioning, pagination, OpenAPI, rate limiting
16
- - `monitoring-observability` — Prometheus, Grafana, Datadog, SLOs, OpenTelemetry
17
- - `ci-cd-pipeline` — GitHub Actions, testing pyramid, deployment gates, feature flags
18
-
19
- **UI changes:**
20
- - Premium skills show 🔒 lock icon and amber "Premium" badge on skill cards
21
- - Premium skill detail pages show 30-line preview with gradient fade, then paywall CTA
22
- - CTA button: "Get all premium skills — $49/year" linking to `NEXT_PUBLIC_PREMIUM_URL` env var
23
- - New "★ Premium" filter button in skill category bar
24
- - Premium skill count shown in footer stats (amber colored)
25
- - Free skills remain fully accessible — no changes
26
-
27
- **Files changed:**
28
- - `lib/skills.ts` — Added `premium?: boolean` to Skill interface
29
- - `skills.json` + `public/skills.json` — 10 new skills with `"premium": true`
30
- - `components/SkillsGrid.tsx` — Premium badge, lock icon, premium filter button
31
- - `components/PremiumGate.tsx` — New component: preview + paywall CTA
32
- - `app/skills/[name]/page.tsx` — Premium badge on detail page, PremiumGate for premium content
33
- - `app/page.tsx` — Premium skills count in stats section
34
-
35
- **Notes:**
36
- - Total skills: 80 (70 free + 10 premium)
37
- - Static export — paywall is client-side only (intentionally bypassable for v1)
38
- - CLI package unchanged — premium skills are web-only
39
- - Build passes: 86 static pages generated