Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aea984a2e3 | ||
|
|
30e267cdcd | ||
|
|
37349607ae | ||
|
|
2382b7439c | ||
|
|
4e87d6e393 | ||
|
|
a4c74c869d | ||
|
|
f4a2f1d23d | ||
|
|
8e82b5e0f6 | ||
|
|
7c6abdfb72 | ||
|
|
768290ebd1 | ||
|
|
5ac9d8b9b7 |
@@ -2,7 +2,7 @@
|
||||
|
||||
Generated at: 2026-02-08T00:00:00.000Z
|
||||
|
||||
Total skills: 856
|
||||
Total skills: 857
|
||||
|
||||
## architecture (64)
|
||||
|
||||
@@ -300,7 +300,7 @@ Use when creating container-based agents that run custom code in Azure ... | hos
|
||||
| `xlsx-official` | Comprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work ... | xlsx, official | xlsx, official, spreadsheet, creation, editing, analysis, formulas, formatting, data, visualization, claude, work |
|
||||
| `youtube-automation` | Automate YouTube tasks via Rube MCP (Composio): upload videos, manage playlists, search content, get analytics, and handle comments. Always search tools firs... | youtube | youtube, automation, automate, tasks, via, rube, mcp, composio, upload, videos, playlists, search |
|
||||
|
||||
## development (127)
|
||||
## development (128)
|
||||
|
||||
| Skill | Description | Tags | Triggers |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -392,6 +392,7 @@ Triggers: "queue storage", "QueueServic... | azure, storage, queue, py | azure,
|
||||
| `gemini-api-dev` | Use this skill when building applications with Gemini models, Gemini API, working with multimodal content (text, images, audio, video), implementing function... | gemini, api, dev | gemini, api, dev, skill, building, applications, models, working, multimodal, content, text, images |
|
||||
| `go-concurrency-patterns` | Master Go concurrency with goroutines, channels, sync primitives, and context. Use when building concurrent Go applications, implementing worker pools, or de... | go, concurrency | go, concurrency, goroutines, channels, sync, primitives, context, building, concurrent, applications, implementing, worker |
|
||||
| `go-playwright` | Expert capability for robust, stealthy, and efficient browser automation using Playwright Go. | go, playwright | go, playwright, capability, robust, stealthy, efficient, browser, automation |
|
||||
| `go-rod-master` | Comprehensive guide for browser automation and web scraping with go-rod (Chrome DevTools Protocol) including stealth anti-bot-detection patterns. | go, rod, master | go, rod, master, browser, automation, web, scraping, chrome, devtools, protocol, including, stealth |
|
||||
| `golang-pro` | Master Go 1.21+ with modern patterns, advanced concurrency, performance optimization, and production-ready microservices. Expert in the latest Go ecosystem i... | golang | golang, pro, go, 21, concurrency, performance, optimization, microservices, latest, ecosystem, including, generics |
|
||||
| `hubspot-integration` | Expert patterns for HubSpot CRM integration including OAuth authentication, CRM objects, associations, batch operations, webhooks, and custom objects. Covers... | hubspot, integration | hubspot, integration, crm, including, oauth, authentication, objects, associations, batch, operations, webhooks, custom |
|
||||
| `javascript-mastery` | Comprehensive JavaScript reference covering 33+ essential concepts every developer should know. From fundamentals like primitives and closures to advanced pa... | javascript, mastery | javascript, mastery, reference, covering, 33, essential, concepts, every, developer, should, know, fundamentals |
|
||||
@@ -570,7 +571,7 @@ TRIGGER: "shopify", "shopify app", "checkout extension",... | shopify | shopify,
|
||||
| `subagent-driven-development` | Use when executing implementation plans with independent tasks in the current session | subagent, driven | subagent, driven, development, executing, plans, independent, tasks, current, session |
|
||||
| `superpowers-lab` | Lab environment for Claude superpowers | superpowers, lab | superpowers, lab, environment, claude |
|
||||
| `theme-factory` | Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors... | theme, factory | theme, factory, toolkit, styling, artifacts, these, slides, docs, reportings, html, landing, pages |
|
||||
| `threejs-skills` | Three.js skills for creating 3D elements and interactive experiences | threejs, skills | threejs, skills, three, js, creating, 3d, elements, interactive, experiences |
|
||||
| `threejs-skills` | Create 3D scenes, interactive experiences, and visual effects using Three.js. Use when user requests 3D graphics, WebGL experiences, 3D visualizations, anima... | threejs, skills | threejs, skills, 3d, scenes, interactive, experiences, visual, effects, three, js, user, requests |
|
||||
| `turborepo-caching` | Configure Turborepo for efficient monorepo builds with local and remote caching. Use when setting up Turborepo, optimizing build pipelines, or implementing d... | turborepo, caching | turborepo, caching, configure, efficient, monorepo, local, remote, setting, up, optimizing, pipelines, implementing |
|
||||
| `tutorial-engineer` | Creates step-by-step tutorials and educational content from code. Transforms complex concepts into progressive learning experiences with hands-on examples. U... | tutorial | tutorial, engineer, creates, step, tutorials, educational, content, code, transforms, complex, concepts, progressive |
|
||||
| `ui-skills` | Opinionated, evolving constraints to guide agents when building interfaces | ui, skills | ui, skills, opinionated, evolving, constraints, agents, building, interfaces |
|
||||
|
||||
59
CHANGELOG.md
59
CHANGELOG.md
@@ -7,7 +7,64 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
---
|
||||
|
||||
## [5.2.0] - 2026-02-13 - "Podcast Generation & Azure Expansion"
|
||||
## [5.4.0] - 2026-02-16 - "CursorRules Pro & Go-Rod"
|
||||
|
||||
> **Community contributions: CursorRules Pro in credits and go-rod-master skill for browser automation with Go.**
|
||||
|
||||
This release adds CursorRules Pro to Community Contributors and a new skill for browser automation and web scraping with go-rod (Chrome DevTools Protocol) in Golang, including stealth and anti-bot-detection patterns.
|
||||
|
||||
### New Skills
|
||||
|
||||
#### go-rod-master ([skills/go-rod-master/](skills/go-rod-master/))
|
||||
|
||||
**Browser automation and web scraping with Go and Chrome DevTools Protocol.**
|
||||
Comprehensive guide for the go-rod library: launch and page lifecycle, Must vs error patterns, context and timeouts, element selectors, auto-wait, and integration with go-rod/stealth for anti-bot detection.
|
||||
|
||||
- **Key features**: CDP-native driver, thread-safe operations, stealth plugin, request hijacking, concurrent page pools.
|
||||
- **When to use**: Scraping or automating sites with Go, headless browser for SPAs, stealth/anti-bot needs, migrating from chromedp or Playwright Go.
|
||||
|
||||
> **Try it:** "Automate logging into example.com with Go using go-rod and stealth."
|
||||
|
||||
### Registry
|
||||
|
||||
- **Total Skills**: 857 (from 856).
|
||||
- **Generated files**: README, skills_index.json, catalog, and bundles synced.
|
||||
|
||||
### Credits
|
||||
|
||||
- **[@Wittlesus](https://github.com/Wittlesus)** - CursorRules Pro in Community Contributors (PR #81).
|
||||
- **[@8hrsk](https://github.com/8hrsk)** - go-rod-master skill (PR #83).
|
||||
|
||||
---
|
||||
|
||||
_Upgrade now: `git pull origin main` to fetch the latest skills._
|
||||
|
||||
---
|
||||
|
||||
## [5.3.0] - 2026-02-13 - "Advanced Three.js & Modern Graphics"
|
||||
|
||||
> **Enhanced Three.js patterns: performance, visual polish, and production practices.**
|
||||
|
||||
This release significantly upgrades our 3D visualization capabilities with a comprehensive Three.js skill upgrade, focusing on CDN-compatible patterns, performance optimizations, and modern graphics techniques like shadows, fog, and GSAP integration.
|
||||
|
||||
### Added
|
||||
|
||||
- **Modern Three.js Patterns**: Comprehensive guide for `r128` (CDN) and production environments.
|
||||
- **Visual Polish**: Advanced sections for shadows, environment maps, and tone mapping.
|
||||
- **Interaction Models**: Custom camera controls (OrbitControls alternative) and raycasting for object selection.
|
||||
- **Production Readiness**: Integration patterns for GSAP, scroll-based animations, and build tool optimizations.
|
||||
|
||||
### Registry
|
||||
|
||||
- **Total Skills**: 856.
|
||||
- **Metadata**: Fixed missing source and risk fields for `threejs-skills`.
|
||||
- **Sync**: All discovery artifacts (README, Catalog, Index) updated and synced.
|
||||
|
||||
### Contributors
|
||||
|
||||
- **[@Krishna-hehe](https://github.com/Krishna-hehe)** - Advanced Three.js skill overhaul (PR #78).
|
||||
|
||||
---
|
||||
|
||||
> **New AI capabilities: Podcast Generation, Azure Identity, and Self-Evolving Agents.**
|
||||
|
||||
|
||||
13
README.md
13
README.md
@@ -1,6 +1,6 @@
|
||||
# 🌌 Antigravity Awesome Skills: 856+ Agentic Skills for Claude Code, Gemini CLI, Cursor, Copilot & More
|
||||
# 🌌 Antigravity Awesome Skills: 857+ Agentic Skills for Claude Code, Gemini CLI, Cursor, Copilot & More
|
||||
|
||||
> **The Ultimate Collection of 856+ Universal Agentic Skills for AI Coding Assistants — Claude Code, Gemini CLI, Codex CLI, Antigravity IDE, GitHub Copilot, Cursor, OpenCode, AdaL**
|
||||
> **The Ultimate Collection of 857+ Universal Agentic Skills for AI Coding Assistants — Claude Code, Gemini CLI, Codex CLI, Antigravity IDE, GitHub Copilot, Cursor, OpenCode, AdaL**
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://claude.ai)
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
If this project helps you, you can [support it here](https://buymeacoffee.com/sickn33) or simply ⭐ the repo.
|
||||
|
||||
**Antigravity Awesome Skills** is a curated, battle-tested library of **856 high-performance agentic skills** designed to work seamlessly across all major AI coding assistants:
|
||||
**Antigravity Awesome Skills** is a curated, battle-tested library of **857 high-performance agentic skills** designed to work seamlessly across all major AI coding assistants:
|
||||
|
||||
- 🟣 **Claude Code** (Anthropic CLI)
|
||||
- 🔵 **Gemini CLI** (Google DeepMind)
|
||||
@@ -38,7 +38,7 @@ This repository provides essential skills to transform your AI assistant into a
|
||||
- [🎁 Curated Collections (Bundles)](#curated-collections)
|
||||
- [🧭 Antigravity Workflows](#antigravity-workflows)
|
||||
- [📦 Features & Categories](#features--categories)
|
||||
- [📚 Browse 856+ Skills](#browse-856-skills)
|
||||
- [📚 Browse 857+ Skills](#browse-857-skills)
|
||||
- [🤝 How to Contribute](#how-to-contribute)
|
||||
- [🤝 Community](#community)
|
||||
- [☕ Support the Project](#support-the-project)
|
||||
@@ -280,7 +280,7 @@ The repository is organized into specialized domains to transform your AI into a
|
||||
|
||||
Counts change as new skills are added. For the current full registry, see [CATALOG.md](CATALOG.md).
|
||||
|
||||
## Browse 856+ Skills
|
||||
## Browse 857+ Skills
|
||||
|
||||
We have moved the full skill registry to a dedicated catalog to keep this README clean.
|
||||
|
||||
@@ -379,6 +379,7 @@ This collection would not be possible without the incredible work of the Claude
|
||||
- **[whatiskadudoing/fp-ts-skills](https://github.com/whatiskadudoing/fp-ts-skills)**: Practical fp-ts skills for TypeScript – fp-ts-pragmatic, fp-ts-react, fp-ts-errors (v4.4.0).
|
||||
- **[webzler/agentMemory](https://github.com/webzler/agentMemory)**: Source for the agent-memory-mcp skill.
|
||||
- **[sstklen/claude-api-cost-optimization](https://github.com/sstklen/claude-api-cost-optimization)**: Save 50-90% on Claude API costs with smart optimization strategies (MIT).
|
||||
- **[Wittlesus/cursorrules-pro](https://github.com/Wittlesus/cursorrules-pro)**: Professional .cursorrules configurations for 8 frameworks - Next.js, React, Python, Go, Rust, and more. Works with Cursor, Claude Code, and Windsurf.
|
||||
|
||||
### Inspirations
|
||||
|
||||
@@ -437,6 +438,8 @@ We officially thank the following contributors for their help in making this rep
|
||||
- [@ericgandrade](https://github.com/ericgandrade)
|
||||
- [@sohamganatra](https://github.com/sohamganatra)
|
||||
- [@Nguyen-Van-Chan](https://github.com/Nguyen-Van-Chan)
|
||||
- [@8hrsk](https://github.com/8hrsk)
|
||||
- [@Wittlesus](https://github.com/Wittlesus)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
@@ -148,6 +148,7 @@
|
||||
"gemini-api-dev",
|
||||
"go-concurrency-patterns",
|
||||
"go-playwright",
|
||||
"go-rod-master",
|
||||
"golang-pro",
|
||||
"graphql",
|
||||
"hubspot-integration",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"generatedAt": "2026-02-08T00:00:00.000Z",
|
||||
"total": 856,
|
||||
"total": 857,
|
||||
"skills": [
|
||||
{
|
||||
"id": "3d-web-experience",
|
||||
@@ -11041,6 +11041,32 @@
|
||||
],
|
||||
"path": "skills/go-playwright/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "go-rod-master",
|
||||
"name": "go-rod-master",
|
||||
"description": "Comprehensive guide for browser automation and web scraping with go-rod (Chrome DevTools Protocol) including stealth anti-bot-detection patterns.",
|
||||
"category": "development",
|
||||
"tags": [
|
||||
"go",
|
||||
"rod",
|
||||
"master"
|
||||
],
|
||||
"triggers": [
|
||||
"go",
|
||||
"rod",
|
||||
"master",
|
||||
"browser",
|
||||
"automation",
|
||||
"web",
|
||||
"scraping",
|
||||
"chrome",
|
||||
"devtools",
|
||||
"protocol",
|
||||
"including",
|
||||
"stealth"
|
||||
],
|
||||
"path": "skills/go-rod-master/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "godot-gdscript-patterns",
|
||||
"name": "godot-gdscript-patterns",
|
||||
@@ -19332,7 +19358,7 @@
|
||||
{
|
||||
"id": "threejs-skills",
|
||||
"name": "threejs-skills",
|
||||
"description": "Three.js skills for creating 3D elements and interactive experiences",
|
||||
"description": "Create 3D scenes, interactive experiences, and visual effects using Three.js. Use when user requests 3D graphics, WebGL experiences, 3D visualizations, animations, or interactive 3D elements.",
|
||||
"category": "general",
|
||||
"tags": [
|
||||
"threejs",
|
||||
@@ -19341,13 +19367,16 @@
|
||||
"triggers": [
|
||||
"threejs",
|
||||
"skills",
|
||||
"3d",
|
||||
"scenes",
|
||||
"interactive",
|
||||
"experiences",
|
||||
"visual",
|
||||
"effects",
|
||||
"three",
|
||||
"js",
|
||||
"creating",
|
||||
"3d",
|
||||
"elements",
|
||||
"interactive",
|
||||
"experiences"
|
||||
"user",
|
||||
"requests"
|
||||
],
|
||||
"path": "skills/threejs-skills/SKILL.md"
|
||||
},
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "antigravity-awesome-skills",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "antigravity-awesome-skills",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"antigravity-awesome-skills": "bin/install.js"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "antigravity-awesome-skills",
|
||||
"version": "5.2.0",
|
||||
"version": "5.4.0",
|
||||
"description": "845+ agentic skills for Claude Code, Gemini CLI, Cursor, Antigravity & more. Installer CLI.",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,36 +1,32 @@
|
||||
## [5.0.0] - 2026-02-10 - "Antigravity Workflows Foundation"
|
||||
# v5.4.0 - CursorRules Pro & Go-Rod
|
||||
|
||||
> First-class Workflows are now available to orchestrate multiple skills through guided execution playbooks.
|
||||
> **Community contributions: CursorRules Pro in credits and go-rod-master skill for browser automation with Go.**
|
||||
|
||||
### 🚀 New Skills
|
||||
This release adds CursorRules Pro to Community Contributors and a new skill for browser automation and web scraping with go-rod (Chrome DevTools Protocol) in Golang, including stealth and anti-bot-detection patterns.
|
||||
|
||||
### 🧭 [antigravity-workflows](skills/antigravity-workflows/)
|
||||
## New Skills
|
||||
|
||||
**Orchestrates multi-step outcomes using curated workflow playbooks.**
|
||||
This new skill routes users from high-level goals to concrete execution steps across related skills and bundles.
|
||||
### go-rod-master
|
||||
|
||||
- **Key Feature 1**: Workflow routing for SaaS MVP, Security Audit, AI Agent Systems, and Browser QA.
|
||||
- **Key Feature 2**: Explicit step-by-step outputs with prerequisites, recommended skills, and validation checkpoints.
|
||||
**Browser automation and web scraping with Go and Chrome DevTools Protocol.**
|
||||
|
||||
> **Try it:** `Use @antigravity-workflows to run ship-saas-mvp for my project.`
|
||||
Comprehensive guide for the go-rod library: launch and page lifecycle, Must vs error patterns, context and timeouts, element selectors, auto-wait, and integration with go-rod/stealth for anti-bot detection.
|
||||
|
||||
- **Key features**: CDP-native driver, thread-safe operations, stealth plugin, request hijacking, concurrent page pools.
|
||||
- **When to use**: Scraping or automating sites with Go, headless browser for SPAs, stealth/anti-bot needs, migrating from chromedp or Playwright Go.
|
||||
|
||||
**Try it:** "Automate logging into example.com with Go using go-rod and stealth."
|
||||
|
||||
## Registry
|
||||
|
||||
- **Total Skills**: 857 (from 856).
|
||||
- **Generated files**: README, skills_index.json, catalog, and bundles synced.
|
||||
|
||||
## Credits
|
||||
|
||||
- **@Wittlesus** - CursorRules Pro in Community Contributors (PR #81).
|
||||
- **@8hrsk** - go-rod-master skill (PR #83).
|
||||
|
||||
---
|
||||
|
||||
## 📦 Improvements
|
||||
|
||||
- **Workflow Registry**: Added `data/workflows.json` for machine-readable workflow metadata.
|
||||
- **Workflow Docs**: Added `docs/WORKFLOWS.md` to distinguish Bundles vs Workflows and provide practical execution playbooks.
|
||||
- **Trinity Sync**: Updated `README.md`, `docs/GETTING_STARTED.md`, and `docs/FAQ.md` for workflow onboarding.
|
||||
- **Go QA Path**: Added optional `@go-playwright` wiring in QA/E2E workflow steps.
|
||||
- **Registry Update**: Catalog regenerated; repository now tracks 714 skills.
|
||||
|
||||
## 👥 Credits
|
||||
|
||||
A huge shoutout to our community and maintainers:
|
||||
|
||||
- **@Walapalam** for the Workflows concept request ([Issue #72](https://github.com/sickn33/antigravity-awesome-skills/issues/72))
|
||||
- **@sickn33** for workflow integration, release preparation, and maintenance updates
|
||||
|
||||
---
|
||||
|
||||
_Upgrade now: `git pull origin main` to fetch the latest skills._
|
||||
Upgrade now: `git pull origin main` to fetch the latest skills.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
const assert = require('assert');
|
||||
const { hasUseSection } = require('../validate-skills');
|
||||
const assert = require("assert");
|
||||
const { hasUseSection } = require("../validate-skills");
|
||||
|
||||
const samples = [
|
||||
['## When to Use', true],
|
||||
['## Use this skill when', true],
|
||||
['## When to Use This Skill', true],
|
||||
['## Overview', false],
|
||||
["## When to Use", true],
|
||||
["## Use this skill when", true],
|
||||
["## When to Use This Skill", true],
|
||||
["## Overview", false],
|
||||
];
|
||||
|
||||
for (const [heading, expected] of samples) {
|
||||
@@ -13,4 +13,31 @@ for (const [heading, expected] of samples) {
|
||||
assert.strictEqual(hasUseSection(content), expected, heading);
|
||||
}
|
||||
|
||||
console.log('ok');
|
||||
// Regression test for YAML validity in frontmatter (Issue #79)
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { listSkillIds, parseFrontmatter } = require("../../lib/skill-utils");
|
||||
|
||||
const SKILLS_DIR = path.join(__dirname, "../../skills");
|
||||
const skillIds = listSkillIds(SKILLS_DIR);
|
||||
|
||||
console.log(`Checking YAML validity for ${skillIds.length} skills...`);
|
||||
|
||||
for (const skillId of skillIds) {
|
||||
const skillPath = path.join(SKILLS_DIR, skillId, "SKILL.md");
|
||||
const content = fs.readFileSync(skillPath, "utf8");
|
||||
const { errors, hasFrontmatter } = parseFrontmatter(content);
|
||||
|
||||
if (!hasFrontmatter) {
|
||||
console.warn(`[WARN] No frontmatter in ${skillId}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
assert.strictEqual(
|
||||
errors.length,
|
||||
0,
|
||||
`YAML parse errors in ${skillId}: ${errors.join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log("ok");
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
* Legacy / alternative validator. For CI and PR checks, use scripts/validate_skills.py.
|
||||
* Run: npm run validate (or npm run validate:strict)
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { listSkillIds, parseFrontmatter } = require('../lib/skill-utils');
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { listSkillIds, parseFrontmatter } = require("../lib/skill-utils");
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const SKILLS_DIR = path.join(ROOT, 'skills');
|
||||
const BASELINE_PATH = path.join(ROOT, 'validation-baseline.json');
|
||||
const ROOT = path.resolve(__dirname, "..");
|
||||
const SKILLS_DIR = path.join(ROOT, "skills");
|
||||
const BASELINE_PATH = path.join(ROOT, "validation-baseline.json");
|
||||
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
@@ -17,12 +17,14 @@ const missingDoNotUseSection = [];
|
||||
const missingInstructionsSection = [];
|
||||
const longFiles = [];
|
||||
const unknownFieldSkills = [];
|
||||
const isStrict = process.argv.includes('--strict')
|
||||
|| process.env.STRICT === '1'
|
||||
|| process.env.STRICT === 'true';
|
||||
const writeBaseline = process.argv.includes('--write-baseline')
|
||||
|| process.env.WRITE_BASELINE === '1'
|
||||
|| process.env.WRITE_BASELINE === 'true';
|
||||
const isStrict =
|
||||
process.argv.includes("--strict") ||
|
||||
process.env.STRICT === "1" ||
|
||||
process.env.STRICT === "true";
|
||||
const writeBaseline =
|
||||
process.argv.includes("--write-baseline") ||
|
||||
process.env.WRITE_BASELINE === "1" ||
|
||||
process.env.WRITE_BASELINE === "true";
|
||||
|
||||
const NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
||||
const MAX_NAME_LENGTH = 64;
|
||||
@@ -30,14 +32,15 @@ const MAX_DESCRIPTION_LENGTH = 1024;
|
||||
const MAX_COMPATIBILITY_LENGTH = 500;
|
||||
const MAX_SKILL_LINES = 500;
|
||||
const ALLOWED_FIELDS = new Set([
|
||||
'name',
|
||||
'description',
|
||||
'risk',
|
||||
'source',
|
||||
'license',
|
||||
'compatibility',
|
||||
'metadata',
|
||||
'allowed-tools',
|
||||
"name",
|
||||
"description",
|
||||
"risk",
|
||||
"source",
|
||||
"license",
|
||||
"compatibility",
|
||||
"metadata",
|
||||
"allowed-tools",
|
||||
"package",
|
||||
]);
|
||||
|
||||
const USE_SECTION_PATTERNS = [
|
||||
@@ -47,15 +50,19 @@ const USE_SECTION_PATTERNS = [
|
||||
];
|
||||
|
||||
function hasUseSection(content) {
|
||||
return USE_SECTION_PATTERNS.some(pattern => pattern.test(content));
|
||||
return USE_SECTION_PATTERNS.some((pattern) => pattern.test(content));
|
||||
}
|
||||
|
||||
function isPlainObject(value) {
|
||||
return value && typeof value === 'object' && !Array.isArray(value);
|
||||
return value && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function validateStringField(fieldName, value, { min = 1, max = Infinity } = {}) {
|
||||
if (typeof value !== 'string') {
|
||||
function validateStringField(
|
||||
fieldName,
|
||||
value,
|
||||
{ min = 1, max = Infinity } = {},
|
||||
) {
|
||||
if (typeof value !== "string") {
|
||||
return `${fieldName} must be a string.`;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
@@ -90,24 +97,37 @@ function loadBaseline() {
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(fs.readFileSync(BASELINE_PATH, 'utf8'));
|
||||
const parsed = JSON.parse(fs.readFileSync(BASELINE_PATH, "utf8"));
|
||||
return {
|
||||
useSection: Array.isArray(parsed.useSection) ? parsed.useSection : [],
|
||||
doNotUseSection: Array.isArray(parsed.doNotUseSection) ? parsed.doNotUseSection : [],
|
||||
instructionsSection: Array.isArray(parsed.instructionsSection) ? parsed.instructionsSection : [],
|
||||
doNotUseSection: Array.isArray(parsed.doNotUseSection)
|
||||
? parsed.doNotUseSection
|
||||
: [],
|
||||
instructionsSection: Array.isArray(parsed.instructionsSection)
|
||||
? parsed.instructionsSection
|
||||
: [],
|
||||
longFile: Array.isArray(parsed.longFile) ? parsed.longFile : [],
|
||||
};
|
||||
} catch (err) {
|
||||
addWarning('Failed to parse validation-baseline.json; strict mode may fail.');
|
||||
return { useSection: [], doNotUseSection: [], instructionsSection: [], longFile: [] };
|
||||
addWarning(
|
||||
"Failed to parse validation-baseline.json; strict mode may fail.",
|
||||
);
|
||||
return {
|
||||
useSection: [],
|
||||
doNotUseSection: [],
|
||||
instructionsSection: [],
|
||||
longFile: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function addStrictSectionErrors(label, missing, baselineSet) {
|
||||
if (!isStrict) return;
|
||||
const strictMissing = missing.filter(skillId => !baselineSet.has(skillId));
|
||||
const strictMissing = missing.filter((skillId) => !baselineSet.has(skillId));
|
||||
if (strictMissing.length) {
|
||||
addError(`Missing "${label}" section (strict): ${strictMissing.length} skills (examples: ${strictMissing.slice(0, 5).join(', ')})`);
|
||||
addError(
|
||||
`Missing "${label}" section (strict): ${strictMissing.length} skills (examples: ${strictMissing.slice(0, 5).join(", ")})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,15 +140,19 @@ function run() {
|
||||
const baselineLongFile = new Set(baseline.longFile || []);
|
||||
|
||||
for (const skillId of skillIds) {
|
||||
const skillPath = path.join(SKILLS_DIR, skillId, 'SKILL.md');
|
||||
const skillPath = path.join(SKILLS_DIR, skillId, "SKILL.md");
|
||||
|
||||
if (!fs.existsSync(skillPath)) {
|
||||
addError(`Missing SKILL.md: ${skillId}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(skillPath, 'utf8');
|
||||
const { data, errors: fmErrors, hasFrontmatter } = parseFrontmatter(content);
|
||||
const content = fs.readFileSync(skillPath, "utf8");
|
||||
const {
|
||||
data,
|
||||
errors: fmErrors,
|
||||
hasFrontmatter,
|
||||
} = parseFrontmatter(content);
|
||||
const lineCount = content.split(/\r?\n/).length;
|
||||
|
||||
if (!hasFrontmatter) {
|
||||
@@ -136,7 +160,9 @@ function run() {
|
||||
}
|
||||
|
||||
if (fmErrors && fmErrors.length) {
|
||||
fmErrors.forEach(error => addError(`Frontmatter parse error (${skillId}): ${error}`));
|
||||
fmErrors.forEach((error) =>
|
||||
addError(`Frontmatter parse error (${skillId}): ${error}`),
|
||||
);
|
||||
}
|
||||
|
||||
if (!NAME_PATTERN.test(skillId)) {
|
||||
@@ -144,7 +170,10 @@ function run() {
|
||||
}
|
||||
|
||||
if (data.name !== undefined) {
|
||||
const nameError = validateStringField('name', data.name, { min: 1, max: MAX_NAME_LENGTH });
|
||||
const nameError = validateStringField("name", data.name, {
|
||||
min: 1,
|
||||
max: MAX_NAME_LENGTH,
|
||||
});
|
||||
if (nameError) {
|
||||
addError(`${nameError} (${skillId})`);
|
||||
} else {
|
||||
@@ -158,15 +187,22 @@ function run() {
|
||||
}
|
||||
}
|
||||
|
||||
const descError = data.description === undefined
|
||||
? 'description is required.'
|
||||
: validateStringField('description', data.description, { min: 1, max: MAX_DESCRIPTION_LENGTH });
|
||||
const descError =
|
||||
data.description === undefined
|
||||
? "description is required."
|
||||
: validateStringField("description", data.description, {
|
||||
min: 1,
|
||||
max: MAX_DESCRIPTION_LENGTH,
|
||||
});
|
||||
if (descError) {
|
||||
addError(`${descError} (${skillId})`);
|
||||
}
|
||||
|
||||
if (data.license !== undefined) {
|
||||
const licenseError = validateStringField('license', data.license, { min: 1, max: 128 });
|
||||
const licenseError = validateStringField("license", data.license, {
|
||||
min: 1,
|
||||
max: 128,
|
||||
});
|
||||
if (licenseError) {
|
||||
addError(`${licenseError} (${skillId})`);
|
||||
}
|
||||
@@ -174,7 +210,7 @@ function run() {
|
||||
|
||||
if (data.compatibility !== undefined) {
|
||||
const compatibilityError = validateStringField(
|
||||
'compatibility',
|
||||
"compatibility",
|
||||
data.compatibility,
|
||||
{ min: 1, max: MAX_COMPATIBILITY_LENGTH },
|
||||
);
|
||||
@@ -183,10 +219,12 @@ function run() {
|
||||
}
|
||||
}
|
||||
|
||||
if (data['allowed-tools'] !== undefined) {
|
||||
if (typeof data['allowed-tools'] !== 'string') {
|
||||
addError(`allowed-tools must be a space-delimited string. (${skillId})`);
|
||||
} else if (!data['allowed-tools'].trim()) {
|
||||
if (data["allowed-tools"] !== undefined) {
|
||||
if (typeof data["allowed-tools"] !== "string") {
|
||||
addError(
|
||||
`allowed-tools must be a space-delimited string. (${skillId})`,
|
||||
);
|
||||
} else if (!data["allowed-tools"].trim()) {
|
||||
addError(`allowed-tools cannot be empty. (${skillId})`);
|
||||
}
|
||||
}
|
||||
@@ -196,7 +234,7 @@ function run() {
|
||||
addError(`metadata must be a string map/object. (${skillId})`);
|
||||
} else {
|
||||
for (const [key, value] of Object.entries(data.metadata)) {
|
||||
if (typeof value !== 'string') {
|
||||
if (typeof value !== "string") {
|
||||
addError(`metadata.${key} must be a string. (${skillId})`);
|
||||
}
|
||||
}
|
||||
@@ -204,10 +242,14 @@ function run() {
|
||||
}
|
||||
|
||||
if (data && Object.keys(data).length) {
|
||||
const unknownFields = Object.keys(data).filter(key => !ALLOWED_FIELDS.has(key));
|
||||
const unknownFields = Object.keys(data).filter(
|
||||
(key) => !ALLOWED_FIELDS.has(key),
|
||||
);
|
||||
if (unknownFields.length) {
|
||||
unknownFieldSkills.push(skillId);
|
||||
addError(`Unknown frontmatter fields (${skillId}): ${unknownFields.join(', ')}`);
|
||||
addError(
|
||||
`Unknown frontmatter fields (${skillId}): ${unknownFields.join(", ")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,39 +261,61 @@ function run() {
|
||||
missingUseSection.push(skillId);
|
||||
}
|
||||
|
||||
if (!content.includes('## Do not use')) {
|
||||
if (!content.includes("## Do not use")) {
|
||||
missingDoNotUseSection.push(skillId);
|
||||
}
|
||||
|
||||
if (!content.includes('## Instructions')) {
|
||||
if (!content.includes("## Instructions")) {
|
||||
missingInstructionsSection.push(skillId);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingUseSection.length) {
|
||||
addWarning(`Missing "Use this skill when" section: ${missingUseSection.length} skills (examples: ${missingUseSection.slice(0, 5).join(', ')})`);
|
||||
addWarning(
|
||||
`Missing "Use this skill when" section: ${missingUseSection.length} skills (examples: ${missingUseSection.slice(0, 5).join(", ")})`,
|
||||
);
|
||||
}
|
||||
|
||||
if (missingDoNotUseSection.length) {
|
||||
addWarning(`Missing "Do not use" section: ${missingDoNotUseSection.length} skills (examples: ${missingDoNotUseSection.slice(0, 5).join(', ')})`);
|
||||
addWarning(
|
||||
`Missing "Do not use" section: ${missingDoNotUseSection.length} skills (examples: ${missingDoNotUseSection.slice(0, 5).join(", ")})`,
|
||||
);
|
||||
}
|
||||
|
||||
if (missingInstructionsSection.length) {
|
||||
addWarning(`Missing "Instructions" section: ${missingInstructionsSection.length} skills (examples: ${missingInstructionsSection.slice(0, 5).join(', ')})`);
|
||||
addWarning(
|
||||
`Missing "Instructions" section: ${missingInstructionsSection.length} skills (examples: ${missingInstructionsSection.slice(0, 5).join(", ")})`,
|
||||
);
|
||||
}
|
||||
|
||||
if (longFiles.length) {
|
||||
addWarning(`SKILL.md over ${MAX_SKILL_LINES} lines: ${longFiles.length} skills (examples: ${longFiles.slice(0, 5).join(', ')})`);
|
||||
addWarning(
|
||||
`SKILL.md over ${MAX_SKILL_LINES} lines: ${longFiles.length} skills (examples: ${longFiles.slice(0, 5).join(", ")})`,
|
||||
);
|
||||
}
|
||||
|
||||
if (unknownFieldSkills.length) {
|
||||
addWarning(`Unknown frontmatter fields detected: ${unknownFieldSkills.length} skills (examples: ${unknownFieldSkills.slice(0, 5).join(', ')})`);
|
||||
addWarning(
|
||||
`Unknown frontmatter fields detected: ${unknownFieldSkills.length} skills (examples: ${unknownFieldSkills.slice(0, 5).join(", ")})`,
|
||||
);
|
||||
}
|
||||
|
||||
addStrictSectionErrors('Use this skill when', missingUseSection, baselineUse);
|
||||
addStrictSectionErrors('Do not use', missingDoNotUseSection, baselineDoNotUse);
|
||||
addStrictSectionErrors('Instructions', missingInstructionsSection, baselineInstructions);
|
||||
addStrictSectionErrors(`SKILL.md line count <= ${MAX_SKILL_LINES}`, longFiles, baselineLongFile);
|
||||
addStrictSectionErrors("Use this skill when", missingUseSection, baselineUse);
|
||||
addStrictSectionErrors(
|
||||
"Do not use",
|
||||
missingDoNotUseSection,
|
||||
baselineDoNotUse,
|
||||
);
|
||||
addStrictSectionErrors(
|
||||
"Instructions",
|
||||
missingInstructionsSection,
|
||||
baselineInstructions,
|
||||
);
|
||||
addStrictSectionErrors(
|
||||
`SKILL.md line count <= ${MAX_SKILL_LINES}`,
|
||||
longFiles,
|
||||
baselineLongFile,
|
||||
);
|
||||
|
||||
if (writeBaseline) {
|
||||
const baselineData = {
|
||||
@@ -266,14 +330,14 @@ function run() {
|
||||
}
|
||||
|
||||
if (warnings.length) {
|
||||
console.warn('Warnings:');
|
||||
console.warn("Warnings:");
|
||||
for (const warning of warnings) {
|
||||
console.warn(`- ${warning}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
console.error('\nErrors:');
|
||||
console.error("\nErrors:");
|
||||
for (const error of errors) {
|
||||
console.error(`- ${error}`);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: azure-ai-contentsafety-ts
|
||||
description: Analyze text and images for harmful content using Azure AI Content Safety (@azure-rest/ai-content-safety). Use when moderating user-generated content, detecting hate speech, violence, sexual content, or self-harm, or managing custom blocklists.
|
||||
package: @azure-rest/ai-content-safety
|
||||
package: "@azure-rest/ai-content-safety"
|
||||
---
|
||||
|
||||
# Azure AI Content Safety REST SDK for TypeScript
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: azure-ai-document-intelligence-ts
|
||||
description: Extract text, tables, and structured data from documents using Azure Document Intelligence (@azure-rest/ai-document-intelligence). Use when processing invoices, receipts, IDs, forms, or building custom document models.
|
||||
package: @azure-rest/ai-document-intelligence
|
||||
package: "@azure-rest/ai-document-intelligence"
|
||||
---
|
||||
|
||||
# Azure Document Intelligence REST SDK for TypeScript
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: azure-ai-projects-ts
|
||||
description: Build AI applications using Azure AI Projects SDK for JavaScript (@azure/ai-projects). Use when working with Foundry project clients, agents, connections, deployments, datasets, indexes, evaluations, or getting OpenAI clients.
|
||||
package: @azure/ai-projects
|
||||
package: "@azure/ai-projects"
|
||||
---
|
||||
|
||||
# Azure AI Projects SDK for TypeScript
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: azure-ai-translation-ts
|
||||
description: Build translation applications using Azure Translation SDKs for JavaScript (@azure-rest/ai-translation-text, @azure-rest/ai-translation-document). Use when implementing text translation, transliteration, language detection, or batch document translation.
|
||||
package: @azure-rest/ai-translation-text, @azure-rest/ai-translation-document
|
||||
package: "@azure-rest/ai-translation-text, @azure-rest/ai-translation-document"
|
||||
---
|
||||
|
||||
# Azure Translation SDKs for TypeScript
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: azure-ai-voicelive-ts
|
||||
description: |
|
||||
Azure AI Voice Live SDK for JavaScript/TypeScript. Build real-time voice AI applications with bidirectional WebSocket communication. Use for voice assistants, conversational AI, real-time speech-to-speech, and voice-enabled chatbots in Node.js or browser environments. Triggers: "voice live", "real-time voice", "VoiceLiveClient", "VoiceLiveSession", "voice assistant TypeScript", "bidirectional audio", "speech-to-speech JavaScript".
|
||||
package: @azure/ai-voicelive
|
||||
package: "@azure/ai-voicelive"
|
||||
---
|
||||
|
||||
# @azure/ai-voicelive (JavaScript/TypeScript)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: azure-appconfiguration-ts
|
||||
description: Build applications using Azure App Configuration SDK for JavaScript (@azure/app-configuration). Use when working with configuration settings, feature flags, Key Vault references, dynamic refresh, or centralized configuration management.
|
||||
package: @azure/app-configuration
|
||||
package: "@azure/app-configuration"
|
||||
---
|
||||
|
||||
# Azure App Configuration SDK for TypeScript
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: azure-cosmos-ts
|
||||
description: |
|
||||
Azure Cosmos DB JavaScript/TypeScript SDK (@azure/cosmos) for data plane operations. Use for CRUD operations on documents, queries, bulk operations, and container management. Triggers: "Cosmos DB", "@azure/cosmos", "CosmosClient", "document CRUD", "NoSQL queries", "bulk operations", "partition key", "container.items".
|
||||
package: @azure/cosmos
|
||||
package: "@azure/cosmos"
|
||||
---
|
||||
|
||||
# @azure/cosmos (TypeScript/JavaScript)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: azure-eventhub-ts
|
||||
description: Build event streaming applications using Azure Event Hubs SDK for JavaScript (@azure/event-hubs). Use when implementing high-throughput event ingestion, real-time analytics, IoT telemetry, or event-driven architectures with partitioned consumers.
|
||||
package: @azure/event-hubs
|
||||
package: "@azure/event-hubs"
|
||||
---
|
||||
|
||||
# Azure Event Hubs SDK for TypeScript
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: azure-identity-ts
|
||||
description: Authenticate to Azure services using Azure Identity SDK for JavaScript (@azure/identity). Use when configuring authentication with DefaultAzureCredential, managed identity, service principals, or interactive browser login.
|
||||
package: @azure/identity
|
||||
package: "@azure/identity"
|
||||
---
|
||||
|
||||
# Azure Identity SDK for TypeScript
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: azure-keyvault-keys-ts
|
||||
description: Manage cryptographic keys using Azure Key Vault Keys SDK for JavaScript (@azure/keyvault-keys). Use when creating, encrypting/decrypting, signing, or rotating keys.
|
||||
package: @azure/keyvault-keys
|
||||
package: "@azure/keyvault-keys"
|
||||
---
|
||||
|
||||
# Azure Key Vault Keys SDK for TypeScript
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: azure-keyvault-secrets-ts
|
||||
description: Manage secrets using Azure Key Vault Secrets SDK for JavaScript (@azure/keyvault-secrets). Use when storing and retrieving application secrets or configuration values.
|
||||
package: @azure/keyvault-secrets
|
||||
package: "@azure/keyvault-secrets"
|
||||
---
|
||||
|
||||
# Azure Key Vault Secrets SDK for TypeScript
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: azure-monitor-opentelemetry-ts
|
||||
description: Instrument applications with Azure Monitor and OpenTelemetry for JavaScript (@azure/monitor-opentelemetry). Use when adding distributed tracing, metrics, and logs to Node.js applications with Application Insights.
|
||||
package: @azure/monitor-opentelemetry
|
||||
package: "@azure/monitor-opentelemetry"
|
||||
---
|
||||
|
||||
# Azure Monitor OpenTelemetry SDK for TypeScript
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: azure-search-documents-ts
|
||||
description: Build search applications using Azure AI Search SDK for JavaScript (@azure/search-documents). Use when creating/managing indexes, implementing vector/hybrid search, semantic ranking, or building agentic retrieval with knowledge bases.
|
||||
package: @azure/search-documents
|
||||
package: "@azure/search-documents"
|
||||
---
|
||||
|
||||
# Azure AI Search SDK for TypeScript
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: azure-servicebus-ts
|
||||
description: Build messaging applications using Azure Service Bus SDK for JavaScript (@azure/service-bus). Use when implementing queues, topics/subscriptions, message sessions, dead-letter handling, or enterprise messaging patterns.
|
||||
package: @azure/service-bus
|
||||
package: "@azure/service-bus"
|
||||
---
|
||||
|
||||
# Azure Service Bus SDK for TypeScript
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: azure-storage-blob-ts
|
||||
description: |
|
||||
Azure Blob Storage JavaScript/TypeScript SDK (@azure/storage-blob) for blob operations. Use for uploading, downloading, listing, and managing blobs and containers. Supports block blobs, append blobs, page blobs, SAS tokens, and streaming. Triggers: "blob storage", "@azure/storage-blob", "BlobServiceClient", "ContainerClient", "upload blob", "download blob", "SAS token", "block blob".
|
||||
package: @azure/storage-blob
|
||||
package: "@azure/storage-blob"
|
||||
---
|
||||
|
||||
# @azure/storage-blob (TypeScript/JavaScript)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: azure-storage-file-share-ts
|
||||
description: |
|
||||
Azure File Share JavaScript/TypeScript SDK (@azure/storage-file-share) for SMB file share operations. Use for creating shares, managing directories, uploading/downloading files, and handling file metadata. Supports Azure Files SMB protocol scenarios. Triggers: "file share", "@azure/storage-file-share", "ShareServiceClient", "ShareClient", "SMB", "Azure Files".
|
||||
package: @azure/storage-file-share
|
||||
package: "@azure/storage-file-share"
|
||||
---
|
||||
|
||||
# @azure/storage-file-share (TypeScript/JavaScript)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: azure-storage-queue-ts
|
||||
description: |
|
||||
Azure Queue Storage JavaScript/TypeScript SDK (@azure/storage-queue) for message queue operations. Use for sending, receiving, peeking, and deleting messages in queues. Supports visibility timeout, message encoding, and batch operations. Triggers: "queue storage", "@azure/storage-queue", "QueueServiceClient", "QueueClient", "send message", "receive message", "dequeue", "visibility timeout".
|
||||
package: @azure/storage-queue
|
||||
package: "@azure/storage-queue"
|
||||
---
|
||||
|
||||
# @azure/storage-queue (TypeScript/JavaScript)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: azure-web-pubsub-ts
|
||||
description: Build real-time messaging applications using Azure Web PubSub SDKs for JavaScript (@azure/web-pubsub, @azure/web-pubsub-client). Use when implementing WebSocket-based real-time features, pub/sub messaging, group chat, or live notifications.
|
||||
package: @azure/web-pubsub, @azure/web-pubsub-client
|
||||
package: "@azure/web-pubsub, @azure/web-pubsub-client"
|
||||
---
|
||||
|
||||
# Azure Web PubSub SDKs for TypeScript
|
||||
|
||||
544
skills/go-rod-master/SKILL.md
Normal file
544
skills/go-rod-master/SKILL.md
Normal file
@@ -0,0 +1,544 @@
|
||||
---
|
||||
name: go-rod-master
|
||||
description: "Comprehensive guide for browser automation and web scraping with go-rod (Chrome DevTools Protocol) including stealth anti-bot-detection patterns."
|
||||
risk: safe
|
||||
source: https://github.com/go-rod/rod
|
||||
---
|
||||
|
||||
# Go-Rod Browser Automation Master
|
||||
|
||||
## Overview
|
||||
|
||||
[Rod](https://github.com/go-rod/rod) is a high-level Go driver built directly on the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) for browser automation and web scraping. Unlike wrappers around other tools, Rod communicates with the browser natively via CDP, providing thread-safe operations, chained context design for timeouts/cancellation, auto-wait for elements, correct iframe/shadow DOM handling, and zero zombie browser processes.
|
||||
|
||||
The companion library [go-rod/stealth](https://github.com/go-rod/stealth) injects anti-bot-detection evasions based on [puppeteer-extra stealth](https://github.com/nichochar/puppeteer-extra/tree/master/packages/extract-stealth-evasions), hiding headless browser fingerprints from detection systems.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Use when the user asks to **scrape**, **automate**, or **test** a website using Go.
|
||||
- Use when the user needs a **headless browser** for dynamic/SPA content (React, Vue, Angular).
|
||||
- Use when the user mentions **stealth**, **anti-bot**, **avoiding detection**, **Cloudflare**, or **bot detection bypass**.
|
||||
- Use when the user wants to work with the **Chrome DevTools Protocol (CDP)** directly from Go.
|
||||
- Use when the user needs to **intercept** or **hijack** network requests in a browser context.
|
||||
- Use when the user asks about **concurrent browser scraping** or **page pooling** in Go.
|
||||
- Use when the user is migrating from **chromedp** or **Playwright Go** and wants a simpler API.
|
||||
|
||||
## Safety & Risk
|
||||
|
||||
**Risk Level: 🔵 Safe**
|
||||
|
||||
- **Read-Only by Default:** Default behavior is navigating and reading page content (scraping/testing).
|
||||
- **Isolated Contexts:** Browser contexts are sandboxed; cookies and storage do not persist unless explicitly saved.
|
||||
- **Resource Cleanup:** Designed around Go's `defer` pattern — browsers and pages close automatically.
|
||||
- **No External Mutations:** Does not modify external state unless the script explicitly submits forms or POSTs data.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Core rod library
|
||||
go get github.com/go-rod/rod@latest
|
||||
|
||||
# Stealth anti-detection plugin (ALWAYS include for production scraping)
|
||||
go get github.com/go-rod/stealth@latest
|
||||
```
|
||||
|
||||
Rod auto-downloads a compatible Chromium binary on first run. To pre-download:
|
||||
|
||||
```bash
|
||||
go run github.com/nichochar/go-rod.github.io/cmd/launcher@latest
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Browser Lifecycle
|
||||
|
||||
Rod manages three layers: **Browser → Page → Element**.
|
||||
|
||||
```go
|
||||
// Launch and connect to a browser
|
||||
browser := rod.New().MustConnect()
|
||||
defer browser.MustClose()
|
||||
|
||||
// Create a page (tab)
|
||||
page := browser.MustPage("https://example.com")
|
||||
|
||||
// Find an element
|
||||
el := page.MustElement("h1")
|
||||
fmt.Println(el.MustText())
|
||||
```
|
||||
|
||||
### Must vs Error Patterns
|
||||
|
||||
Rod provides two API styles for every operation:
|
||||
|
||||
| Style | Method | Use Case |
|
||||
|:------|:-------|:---------|
|
||||
| **Must** | `MustElement()`, `MustClick()`, `MustText()` | Scripting, debugging, prototyping. Panics on error. |
|
||||
| **Error** | `Element()`, `Click()`, `Text()` | Production code. Returns `error` for explicit handling. |
|
||||
|
||||
**Production pattern:**
|
||||
|
||||
```go
|
||||
el, err := page.Element("#login-btn")
|
||||
if err != nil {
|
||||
return fmt.Errorf("login button not found: %w", err)
|
||||
}
|
||||
if err := el.Click(proto.InputMouseButtonLeft, 1); err != nil {
|
||||
return fmt.Errorf("click failed: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
**Scripting pattern with Try:**
|
||||
|
||||
```go
|
||||
err := rod.Try(func() {
|
||||
page.MustElement("#login-btn").MustClick()
|
||||
})
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
log.Println("timeout finding login button")
|
||||
}
|
||||
```
|
||||
|
||||
### Context & Timeout
|
||||
|
||||
Rod uses Go's `context.Context` for cancellation and timeouts. Context propagates recursively to all child operations.
|
||||
|
||||
```go
|
||||
// Set a 5-second timeout for the entire operation chain
|
||||
page.Timeout(5 * time.Second).
|
||||
MustWaitLoad().
|
||||
MustElement("title").
|
||||
CancelTimeout(). // subsequent calls are not bound by the 5s timeout
|
||||
Timeout(30 * time.Second).
|
||||
MustText()
|
||||
```
|
||||
|
||||
### Element Selectors
|
||||
|
||||
Rod supports multiple selector strategies:
|
||||
|
||||
```go
|
||||
// CSS selector (most common)
|
||||
page.MustElement("div.content > p.intro")
|
||||
|
||||
// CSS selector with text regex matching
|
||||
page.MustElementR("button", "Submit|Send")
|
||||
|
||||
// XPath
|
||||
page.MustElementX("//div[@class='content']//p")
|
||||
|
||||
// Search across iframes and shadow DOM (like DevTools Ctrl+F)
|
||||
page.MustSearch(".deeply-nested-element")
|
||||
```
|
||||
|
||||
### Auto-Wait
|
||||
|
||||
Rod automatically retries element queries until the element appears or the context times out. You do not need manual sleeps:
|
||||
|
||||
```go
|
||||
// This will automatically wait until the element exists
|
||||
el := page.MustElement("#dynamic-content")
|
||||
|
||||
// Wait until the element is stable (position/size not changing)
|
||||
el.MustWaitStable().MustClick()
|
||||
|
||||
// Wait until page has no pending network requests
|
||||
wait := page.MustWaitRequestIdle()
|
||||
page.MustElement("#search").MustInput("query")
|
||||
wait()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stealth & Anti-Bot Detection (go-rod/stealth)
|
||||
|
||||
> **IMPORTANT:** For any production scraping or automation against real websites, ALWAYS use `stealth.MustPage()` instead of `browser.MustPage()`. This is the single most important step for avoiding bot detection.
|
||||
|
||||
### How Stealth Works
|
||||
|
||||
The `go-rod/stealth` package injects JavaScript evasions into every new page that:
|
||||
|
||||
- **Remove `navigator.webdriver`** — the primary headless detection signal.
|
||||
- **Spoof WebGL vendor/renderer** — presents real GPU info (e.g., "Intel Inc." / "Intel Iris OpenGL Engine") instead of headless markers like "Google SwiftShader".
|
||||
- **Fix Chrome plugin array** — reports proper `PluginArray` type with realistic plugin count.
|
||||
- **Patch permissions API** — returns `"prompt"` instead of bot-revealing values.
|
||||
- **Set realistic languages** — reports `en-US,en` instead of empty arrays.
|
||||
- **Fix broken image dimensions** — headless browsers report 0x0; stealth fixes this to 16x16.
|
||||
|
||||
### Usage
|
||||
|
||||
**Creating a stealth page (recommended for all production use):**
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/go-rod/rod"
|
||||
"github.com/go-rod/stealth"
|
||||
)
|
||||
|
||||
browser := rod.New().MustConnect()
|
||||
defer browser.MustClose()
|
||||
|
||||
// Use stealth.MustPage instead of browser.MustPage
|
||||
page := stealth.MustPage(browser)
|
||||
page.MustNavigate("https://bot.sannysoft.com")
|
||||
```
|
||||
|
||||
**With error handling:**
|
||||
|
||||
```go
|
||||
page, err := stealth.Page(browser)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create stealth page: %w", err)
|
||||
}
|
||||
page.MustNavigate("https://example.com")
|
||||
```
|
||||
|
||||
**Using stealth.JS directly (advanced — for custom page creation):**
|
||||
|
||||
```go
|
||||
// If you need to create the page yourself (e.g., with specific options),
|
||||
// inject stealth.JS manually via EvalOnNewDocument
|
||||
page := browser.MustPage()
|
||||
page.MustEvalOnNewDocument(stealth.JS)
|
||||
page.MustNavigate("https://example.com")
|
||||
```
|
||||
|
||||
### Verifying Stealth
|
||||
|
||||
Navigate to a bot detection test page to verify evasions:
|
||||
|
||||
```go
|
||||
page := stealth.MustPage(browser)
|
||||
page.MustNavigate("https://bot.sannysoft.com")
|
||||
page.MustScreenshot("stealth_test.png")
|
||||
```
|
||||
|
||||
Expected results for a properly stealth-configured browser:
|
||||
- **WebDriver**: `missing (passed)`
|
||||
- **Chrome**: `present (passed)`
|
||||
- **Plugins Length**: `3` (not `0`)
|
||||
- **Languages**: `en-US,en`
|
||||
|
||||
---
|
||||
|
||||
## Implementation Guidelines
|
||||
|
||||
### 1. Launcher Configuration
|
||||
|
||||
Use the `launcher` package to customize browser launch flags:
|
||||
|
||||
```go
|
||||
import "github.com/go-rod/rod/lib/launcher"
|
||||
|
||||
url := launcher.New().
|
||||
Headless(true). // false for debugging
|
||||
Proxy("127.0.0.1:8080"). // upstream proxy
|
||||
Set("disable-gpu", ""). // custom Chrome flag
|
||||
Delete("use-mock-keychain"). // remove a default flag
|
||||
MustLaunch()
|
||||
|
||||
browser := rod.New().ControlURL(url).MustConnect()
|
||||
defer browser.MustClose()
|
||||
```
|
||||
|
||||
**Debugging mode (visible browser + slow motion):**
|
||||
|
||||
```go
|
||||
l := launcher.New().
|
||||
Headless(false).
|
||||
Devtools(true)
|
||||
defer l.Cleanup()
|
||||
|
||||
browser := rod.New().
|
||||
ControlURL(l.MustLaunch()).
|
||||
Trace(true).
|
||||
SlowMotion(2 * time.Second).
|
||||
MustConnect()
|
||||
```
|
||||
|
||||
### 2. Proxy Support
|
||||
|
||||
```go
|
||||
// Set proxy at launch
|
||||
url := launcher.New().
|
||||
Proxy("socks5://127.0.0.1:1080").
|
||||
MustLaunch()
|
||||
|
||||
browser := rod.New().ControlURL(url).MustConnect()
|
||||
|
||||
// Handle proxy authentication
|
||||
go browser.MustHandleAuth("username", "password")()
|
||||
|
||||
// Ignore SSL certificate errors (for MITM proxies)
|
||||
browser.MustIgnoreCertErrors(true)
|
||||
```
|
||||
|
||||
### 3. Input Simulation
|
||||
|
||||
```go
|
||||
import "github.com/go-rod/rod/lib/input"
|
||||
|
||||
// Type into an input field (replaces existing value)
|
||||
page.MustElement("#email").MustInput("user@example.com")
|
||||
|
||||
// Simulate keyboard keys
|
||||
page.Keyboard.MustType(input.Enter)
|
||||
|
||||
// Press key combinations
|
||||
page.Keyboard.MustPress(input.ControlLeft)
|
||||
page.Keyboard.MustType(input.KeyA)
|
||||
page.Keyboard.MustRelease(input.ControlLeft)
|
||||
|
||||
// Mouse click at coordinates
|
||||
page.Mouse.MustClick(input.MouseLeft)
|
||||
page.Mouse.MustMoveTo(100, 200)
|
||||
```
|
||||
|
||||
### 4. Network Request Interception (Hijacking)
|
||||
|
||||
```go
|
||||
router := browser.HijackRequests()
|
||||
defer router.MustStop()
|
||||
|
||||
// Block all image requests
|
||||
router.MustAdd("*.png", func(ctx *rod.Hijack) {
|
||||
ctx.Response.Fail(proto.NetworkErrorReasonBlockedByClient)
|
||||
})
|
||||
|
||||
// Modify request headers
|
||||
router.MustAdd("*api.example.com*", func(ctx *rod.Hijack) {
|
||||
ctx.Request.Req().Header.Set("Authorization", "Bearer token123")
|
||||
ctx.MustLoadResponse()
|
||||
})
|
||||
|
||||
// Modify response body
|
||||
router.MustAdd("*.js", func(ctx *rod.Hijack) {
|
||||
ctx.MustLoadResponse()
|
||||
ctx.Response.SetBody(ctx.Response.Body() + "\n// injected")
|
||||
})
|
||||
|
||||
go router.Run()
|
||||
```
|
||||
|
||||
### 5. Waiting Strategies
|
||||
|
||||
```go
|
||||
// Wait for page load event
|
||||
page.MustWaitLoad()
|
||||
|
||||
// Wait for no pending network requests (AJAX idle)
|
||||
wait := page.MustWaitRequestIdle()
|
||||
page.MustElement("#search").MustInput("query")
|
||||
wait()
|
||||
|
||||
// Wait for element to be stable (not animating)
|
||||
page.MustElement(".modal").MustWaitStable().MustClick()
|
||||
|
||||
// Wait for element to become invisible
|
||||
page.MustElement(".loading").MustWaitInvisible()
|
||||
|
||||
// Wait for JavaScript condition
|
||||
page.MustWait(`() => document.title === 'Ready'`)
|
||||
|
||||
// Wait for specific navigation/event
|
||||
wait := page.WaitEvent(&proto.PageLoadEventFired{})
|
||||
page.MustNavigate("https://example.com")
|
||||
wait()
|
||||
```
|
||||
|
||||
### 6. Race Selectors (Multiple Outcomes)
|
||||
|
||||
Handle pages where the result can be one of several outcomes (e.g., login success vs error):
|
||||
|
||||
```go
|
||||
page.MustElement("#username").MustInput("user")
|
||||
page.MustElement("#password").MustInput("pass").MustType(input.Enter)
|
||||
|
||||
// Race between success and error selectors
|
||||
elm := page.Race().
|
||||
Element(".dashboard").MustHandle(func(e *rod.Element) {
|
||||
fmt.Println("Login successful:", e.MustText())
|
||||
}).
|
||||
Element(".error-message").MustDo()
|
||||
|
||||
if elm.MustMatches(".error-message") {
|
||||
log.Fatal("Login failed:", elm.MustText())
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Screenshots & PDF
|
||||
|
||||
```go
|
||||
// Full-page screenshot
|
||||
page.MustScreenshot("page.png")
|
||||
|
||||
// Custom screenshot (JPEG, specific region)
|
||||
img, _ := page.Screenshot(true, &proto.PageCaptureScreenshot{
|
||||
Format: proto.PageCaptureScreenshotFormatJpeg,
|
||||
Quality: gson.Int(90),
|
||||
Clip: &proto.PageViewport{
|
||||
X: 0, Y: 0, Width: 1280, Height: 800, Scale: 1,
|
||||
},
|
||||
})
|
||||
utils.OutputFile("screenshot.jpg", img)
|
||||
|
||||
// Scroll screenshot (captures full scrollable page)
|
||||
img, _ := page.MustWaitStable().ScrollScreenshot(nil)
|
||||
utils.OutputFile("full_page.jpg", img)
|
||||
|
||||
// PDF export
|
||||
page.MustPDF("output.pdf")
|
||||
```
|
||||
|
||||
### 8. Concurrent Page Pool
|
||||
|
||||
```go
|
||||
pool := rod.NewPagePool(5) // max 5 concurrent pages
|
||||
|
||||
create := func() *rod.Page {
|
||||
return browser.MustIncognito().MustPage()
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, url := range urls {
|
||||
wg.Add(1)
|
||||
go func(u string) {
|
||||
defer wg.Done()
|
||||
|
||||
page := pool.MustGet(create)
|
||||
defer pool.Put(page)
|
||||
|
||||
page.MustNavigate(u).MustWaitLoad()
|
||||
fmt.Println(page.MustInfo().Title)
|
||||
}(url)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
pool.Cleanup(func(p *rod.Page) { p.MustClose() })
|
||||
```
|
||||
|
||||
### 9. Event Handling
|
||||
|
||||
```go
|
||||
// Listen for console.log output
|
||||
go page.EachEvent(func(e *proto.RuntimeConsoleAPICalled) {
|
||||
if e.Type == proto.RuntimeConsoleAPICalledTypeLog {
|
||||
fmt.Println(page.MustObjectsToJSON(e.Args))
|
||||
}
|
||||
})()
|
||||
|
||||
// Wait for a specific event before proceeding
|
||||
wait := page.WaitEvent(&proto.PageLoadEventFired{})
|
||||
page.MustNavigate("https://example.com")
|
||||
wait()
|
||||
```
|
||||
|
||||
### 10. File Download
|
||||
|
||||
```go
|
||||
wait := browser.MustWaitDownload()
|
||||
|
||||
page.MustElementR("a", "Download PDF").MustClick()
|
||||
|
||||
data := wait()
|
||||
utils.OutputFile("downloaded.pdf", data)
|
||||
```
|
||||
|
||||
### 11. JavaScript Evaluation
|
||||
|
||||
```go
|
||||
// Execute JS on the page
|
||||
page.MustEval(`() => console.log("hello")`)
|
||||
|
||||
// Pass parameters and get return value
|
||||
result := page.MustEval(`(a, b) => a + b`, 1, 2)
|
||||
fmt.Println(result.Int()) // 3
|
||||
|
||||
// Eval on a specific element ("this" = the DOM element)
|
||||
title := page.MustElement("title").MustEval(`() => this.innerText`).String()
|
||||
|
||||
// Direct CDP calls for features Rod doesn't wrap
|
||||
proto.PageSetAdBlockingEnabled{Enabled: true}.Call(page)
|
||||
```
|
||||
|
||||
### 12. Loading Chrome Extensions
|
||||
|
||||
```go
|
||||
extPath, _ := filepath.Abs("./my-extension")
|
||||
|
||||
u := launcher.New().
|
||||
Set("load-extension", extPath).
|
||||
Headless(false). // extensions require headed mode
|
||||
MustLaunch()
|
||||
|
||||
browser := rod.New().ControlURL(u).MustConnect()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
See the `examples/` directory for complete, runnable Go files:
|
||||
- `examples/basic_scrape.go` — Minimal scraping example
|
||||
- `examples/stealth_page.go` — Anti-detection with go-rod/stealth
|
||||
- `examples/request_hijacking.go` — Intercepting and modifying network requests
|
||||
- `examples/concurrent_pages.go` — Page pool for concurrent scraping
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
- ✅ **ALWAYS use `stealth.MustPage(browser)`** instead of `browser.MustPage()` for real-world sites.
|
||||
- ✅ **ALWAYS `defer browser.MustClose()`** immediately after connecting.
|
||||
- ✅ Use the error-returning API (not `Must*`) in production code.
|
||||
- ✅ Set explicit timeouts with `.Timeout()` — never rely on defaults for production.
|
||||
- ✅ Use `browser.MustIncognito().MustPage()` for isolated sessions.
|
||||
- ✅ Use `PagePool` for concurrent scraping instead of spawning unlimited pages.
|
||||
- ✅ Use `MustWaitStable()` before clicking elements that might be animating.
|
||||
- ✅ Use `MustWaitRequestIdle()` after actions that trigger AJAX calls.
|
||||
- ✅ Use `launcher.New().Headless(false).Devtools(true)` for debugging.
|
||||
- ❌ **NEVER** use `time.Sleep()` for waiting — use Rod's built-in wait methods.
|
||||
- ❌ **NEVER** create a new `Browser` per task — create one Browser, use multiple `Page` instances.
|
||||
- ❌ **NEVER** use `browser.MustPage()` for production scraping — use `stealth.MustPage()`.
|
||||
- ❌ **NEVER** ignore errors in production — always handle them explicitly.
|
||||
- ❌ **NEVER** forget to defer-close browsers, pages, and hijack routers.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- **Problem:** Element not found even though it exists on the page.
|
||||
**Solution:** The element may be inside an iframe or shadow DOM. Use `page.MustSearch()` instead of `page.MustElement()` — it searches across all iframes and shadow DOMs.
|
||||
|
||||
- **Problem:** Click doesn't work because the element is animating.
|
||||
**Solution:** Call `el.MustWaitStable()` before `el.MustClick()`.
|
||||
|
||||
- **Problem:** Bot detection despite using stealth.
|
||||
**Solution:** Combine `stealth.MustPage()` with: randomized viewport sizes, realistic User-Agent strings, human-like input delays between keystrokes, and random idle behaviors (scroll, hover).
|
||||
|
||||
- **Problem:** Browser process leaks (zombie processes).
|
||||
**Solution:** Always `defer browser.MustClose()`. Rod uses [leakless](https://github.com/ysmood/leakless) to kill zombies after main process crash, but explicit cleanup is preferred.
|
||||
|
||||
- **Problem:** Timeout errors on slow pages.
|
||||
**Solution:** Use chained context: `page.Timeout(30 * time.Second).MustWaitLoad()`. For AJAX-heavy pages, use `MustWaitRequestIdle()` instead of `MustWaitLoad()`.
|
||||
|
||||
- **Problem:** HijackRequests router not intercepting requests.
|
||||
**Solution:** You must call `go router.Run()` after setting up routes, and `defer router.MustStop()` for cleanup.
|
||||
|
||||
## Limitations
|
||||
|
||||
- **CAPTCHAs:** Rod does not include CAPTCHA solving. External services (2captcha, etc.) must be integrated separately.
|
||||
- **Extreme Anti-Bot:** While `go-rod/stealth` handles common detection (WebDriver, plugin fingerprints, WebGL), extremely strict systems (some Cloudflare configurations, Akamai Bot Manager) may still detect automation. Additional measures (residential proxies, human-like behavioral patterns) may be needed.
|
||||
- **DRM Content:** Cannot interact with DRM-protected media (e.g., Widevine).
|
||||
- **Resource Usage:** Each browser instance consumes significant RAM (~100-300MB+). Use `PagePool` and limit concurrency on memory-constrained systems.
|
||||
- **Extensions in Headless:** Chrome extensions do not work in headless mode. Use `Headless(false)` with XVFB for server environments.
|
||||
- **Platform:** Requires a Chromium-compatible browser. Does not support Firefox or Safari.
|
||||
|
||||
## Documentation References
|
||||
|
||||
- [Official Documentation](https://go-rod.github.io/) — Guides, tutorials, FAQ
|
||||
- [Go API Reference](https://pkg.go.dev/github.com/go-rod/rod) — Complete type and method documentation
|
||||
- [go-rod/stealth](https://github.com/go-rod/stealth) — Anti-bot detection plugin
|
||||
- [Examples (source)](https://github.com/go-rod/rod/blob/main/examples_test.go) — Official example tests
|
||||
- [Rod vs Chromedp Comparison](https://github.com/nichochar/go-rod.github.io/blob/main/lib/examples/compare-chromedp) — Migration reference
|
||||
- [Chrome DevTools Protocol Docs](https://chromedevtools.github.io/devtools-protocol/) — Underlying protocol reference
|
||||
- [Chrome CLI Flags Reference](https://peter.sh/experiments/chromium-command-line-switches) — Launcher flag documentation
|
||||
- `references/api-reference.md` — Quick-reference cheat sheet
|
||||
41
skills/go-rod-master/examples/basic_scrape.go
Normal file
41
skills/go-rod-master/examples/basic_scrape.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-rod/rod"
|
||||
"github.com/go-rod/rod/lib/input"
|
||||
)
|
||||
|
||||
// basic_scrape demonstrates a minimal go-rod scraping workflow:
|
||||
// Launch browser → navigate → extract text → close.
|
||||
func main() {
|
||||
// Launch and connect to a new browser instance.
|
||||
// Rod auto-downloads Chromium if not present.
|
||||
browser := rod.New().
|
||||
Timeout(time.Minute). // global timeout for the browser
|
||||
MustConnect()
|
||||
defer browser.MustClose()
|
||||
|
||||
// Navigate to the target page and wait for it to stabilize
|
||||
page := browser.MustPage("https://github.com").MustWaitStable()
|
||||
|
||||
// Extract the page title via JavaScript evaluation
|
||||
title := page.MustElement("title").MustEval(`() => this.innerText`).String()
|
||||
fmt.Println("Page title:", title)
|
||||
|
||||
// Use CSS selector to find elements
|
||||
links := page.MustElements("a[href]")
|
||||
fmt.Printf("Found %d links on the page\n", len(links))
|
||||
|
||||
// Use keyboard shortcut to trigger search
|
||||
page.Keyboard.MustType(input.Slash)
|
||||
|
||||
// Type into the search input and press Enter
|
||||
page.MustElement("#query-builder-test").MustInput("go-rod").MustType(input.Enter)
|
||||
|
||||
// Wait for results — MustElementR matches by CSS selector + text regex
|
||||
result := page.MustElementR("span", "DevTools Protocol").MustText()
|
||||
fmt.Println("Found result:", result)
|
||||
}
|
||||
81
skills/go-rod-master/examples/concurrent_pages.go
Normal file
81
skills/go-rod-master/examples/concurrent_pages.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-rod/rod"
|
||||
"github.com/go-rod/stealth"
|
||||
)
|
||||
|
||||
// concurrent_pages demonstrates using rod.PagePool for concurrent scraping
|
||||
// with stealth-enabled pages.
|
||||
func main() {
|
||||
browser := rod.New().
|
||||
Timeout(2 * time.Minute).
|
||||
MustConnect()
|
||||
defer browser.MustClose()
|
||||
|
||||
// URLs to scrape concurrently
|
||||
urls := []string{
|
||||
"https://example.com",
|
||||
"https://example.org",
|
||||
"https://www.iana.org/domains/reserved",
|
||||
"https://www.iana.org/about",
|
||||
}
|
||||
|
||||
// Create a page pool with max 3 concurrent pages
|
||||
pool := rod.NewPagePool(3)
|
||||
|
||||
// Factory function: creates stealth-enabled pages in isolated incognito contexts
|
||||
create := func() *rod.Page {
|
||||
// MustIncognito creates an isolated browser context (separate cookies, storage)
|
||||
page := stealth.MustPage(browser.MustIncognito())
|
||||
return page
|
||||
}
|
||||
|
||||
// Collect results safely using a mutex
|
||||
var mu sync.Mutex
|
||||
results := make(map[string]string)
|
||||
|
||||
// Scrape all URLs concurrently
|
||||
var wg sync.WaitGroup
|
||||
for _, url := range urls {
|
||||
wg.Add(1)
|
||||
go func(u string) {
|
||||
defer wg.Done()
|
||||
|
||||
// Get a page from the pool (blocks if pool is full)
|
||||
page := pool.MustGet(create)
|
||||
defer pool.Put(page) // return page to pool when done
|
||||
|
||||
// Navigate and wait for the page to stabilize
|
||||
page.MustNavigate(u).MustWaitStable()
|
||||
|
||||
// Extract the page title
|
||||
title := page.MustInfo().Title
|
||||
|
||||
// Store result
|
||||
mu.Lock()
|
||||
results[u] = title
|
||||
mu.Unlock()
|
||||
|
||||
fmt.Printf("[done] %s → %s\n", u, title)
|
||||
}(url)
|
||||
}
|
||||
|
||||
// Wait for all goroutines to complete
|
||||
wg.Wait()
|
||||
|
||||
// Clean up the pool
|
||||
pool.Cleanup(func(p *rod.Page) {
|
||||
p.MustClose()
|
||||
})
|
||||
|
||||
// Print summary
|
||||
fmt.Printf("\n--- Results (%d pages scraped) ---\n", len(results))
|
||||
for url, title := range results {
|
||||
fmt.Printf(" %s: %s\n", url, title)
|
||||
}
|
||||
}
|
||||
85
skills/go-rod-master/examples/request_hijacking.go
Normal file
85
skills/go-rod-master/examples/request_hijacking.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-rod/rod"
|
||||
"github.com/go-rod/rod/lib/proto"
|
||||
"github.com/go-rod/stealth"
|
||||
)
|
||||
|
||||
// request_hijacking demonstrates intercepting and modifying network requests
|
||||
// using Rod's HijackRequests API.
|
||||
func main() {
|
||||
browser := rod.New().
|
||||
Timeout(time.Minute).
|
||||
MustConnect()
|
||||
defer browser.MustClose()
|
||||
|
||||
// --- Example 1: Block image requests to save bandwidth ---
|
||||
router := browser.HijackRequests()
|
||||
defer router.MustStop()
|
||||
|
||||
// Block all PNG and JPEG image requests
|
||||
router.MustAdd("*.png", func(ctx *rod.Hijack) {
|
||||
ctx.Response.Fail(proto.NetworkErrorReasonBlockedByClient)
|
||||
})
|
||||
router.MustAdd("*.jpg", func(ctx *rod.Hijack) {
|
||||
ctx.Response.Fail(proto.NetworkErrorReasonBlockedByClient)
|
||||
})
|
||||
|
||||
// Modify request headers for API calls
|
||||
router.MustAdd("*api.*", func(ctx *rod.Hijack) {
|
||||
ctx.Request.Req().Header.Set("X-Custom-Header", "go-rod")
|
||||
ctx.Request.Req().Header.Set("Authorization", "Bearer my-token")
|
||||
|
||||
// Load the actual response from the server
|
||||
if err := ctx.LoadResponse(http.DefaultClient, true); err != nil {
|
||||
fmt.Printf("Failed to load response: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("API response status: %d\n", ctx.Response.Payload().ResponseCode)
|
||||
})
|
||||
|
||||
// Inject JavaScript into every JS file loaded
|
||||
router.MustAdd("*.js", func(ctx *rod.Hijack) {
|
||||
if err := ctx.LoadResponse(http.DefaultClient, true); err != nil {
|
||||
return
|
||||
}
|
||||
// Append tracking code to all JavaScript files
|
||||
body := ctx.Response.Body()
|
||||
ctx.Response.SetBody(body + "\n// Monitored by go-rod")
|
||||
})
|
||||
|
||||
// IMPORTANT: Start the router in a goroutine
|
||||
go router.Run()
|
||||
|
||||
// Use stealth page for anti-detection
|
||||
page := stealth.MustPage(browser)
|
||||
page.MustNavigate("https://example.com").MustWaitLoad()
|
||||
|
||||
fmt.Println("Page loaded with request hijacking active")
|
||||
fmt.Println("Title:", page.MustElement("title").MustText())
|
||||
|
||||
// --- Example 2: Capture and log all network requests ---
|
||||
// (Using a separate page to show different patterns)
|
||||
page2 := stealth.MustPage(browser)
|
||||
|
||||
// Enable network domain for request logging
|
||||
proto.NetworkEnable{}.Call(page2)
|
||||
|
||||
// Listen for network responses
|
||||
go page2.EachEvent(func(e *proto.NetworkResponseReceived) {
|
||||
fmt.Printf(" [%d] %s %s\n",
|
||||
e.Response.Status,
|
||||
e.Type.String(),
|
||||
e.Response.URL,
|
||||
)
|
||||
})()
|
||||
|
||||
page2.MustNavigate("https://example.com").MustWaitLoad()
|
||||
fmt.Println("\nNetwork log above shows all requests captured")
|
||||
}
|
||||
91
skills/go-rod-master/examples/stealth_page.go
Normal file
91
skills/go-rod-master/examples/stealth_page.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-rod/rod"
|
||||
"github.com/go-rod/rod/lib/launcher"
|
||||
"github.com/go-rod/rod/lib/utils"
|
||||
"github.com/go-rod/stealth"
|
||||
)
|
||||
|
||||
// stealth_page demonstrates using go-rod/stealth to bypass bot detection.
|
||||
// It creates a stealth-enabled page and verifies evasions against a detection site.
|
||||
func main() {
|
||||
// Ensure the browser binary is downloaded
|
||||
launcher.NewBrowser().MustGet()
|
||||
|
||||
// Launch browser with custom launcher settings
|
||||
url := launcher.New().
|
||||
Headless(true).
|
||||
MustLaunch()
|
||||
|
||||
browser := rod.New().
|
||||
ControlURL(url).
|
||||
Timeout(time.Minute).
|
||||
MustConnect()
|
||||
defer browser.MustClose()
|
||||
|
||||
// CRITICAL: Use stealth.MustPage instead of browser.MustPage
|
||||
// This injects anti-detection JavaScript into every new document
|
||||
page := stealth.MustPage(browser)
|
||||
|
||||
// Navigate to a bot detection test page
|
||||
page.MustNavigate("https://bot.sannysoft.com")
|
||||
|
||||
// Wait for the detection tests to complete
|
||||
page.MustElement("#broken-image-dimensions.passed")
|
||||
|
||||
// Take a screenshot to verify results
|
||||
page.MustScreenshot("stealth_result.png")
|
||||
fmt.Println("Screenshot saved to stealth_result.png")
|
||||
|
||||
// Print detection results
|
||||
printBotDetectionReport(page)
|
||||
|
||||
// ---- Advanced: Using stealth.JS directly ----
|
||||
// If you need to create the page manually (e.g., with specific context),
|
||||
// you can inject stealth.JS via EvalOnNewDocument:
|
||||
advancedPage := browser.MustPage()
|
||||
advancedPage.MustEvalOnNewDocument(stealth.JS)
|
||||
advancedPage.MustNavigate("https://bot.sannysoft.com")
|
||||
advancedPage.MustElement("#broken-image-dimensions.passed")
|
||||
fmt.Println("\nAdvanced stealth page also passed detection tests")
|
||||
|
||||
// ---- Production: Error handling pattern ----
|
||||
prodPage, err := stealth.Page(browser)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create stealth page: %v\n", err)
|
||||
return
|
||||
}
|
||||
prodPage.MustNavigate("https://example.com")
|
||||
title, err := prodPage.MustElement("title").Text()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to get title: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("\nProduction page title: %s\n", title)
|
||||
}
|
||||
|
||||
// printBotDetectionReport extracts and prints the detection test results.
|
||||
func printBotDetectionReport(page *rod.Page) {
|
||||
el := page.MustElement("#broken-image-dimensions.passed")
|
||||
for _, row := range el.MustParents("table").First().MustElements("tr:nth-child(n+2)") {
|
||||
cells := row.MustElements("td")
|
||||
key := cells[0].MustProperty("textContent")
|
||||
|
||||
if strings.HasPrefix(key.String(), "User Agent") {
|
||||
ua := cells[1].MustProperty("textContent").String()
|
||||
passed := !strings.Contains(ua, "HeadlessChrome/")
|
||||
fmt.Printf(" %s: %t\n", key, passed)
|
||||
} else if strings.HasPrefix(key.String(), "Hairline Feature") {
|
||||
continue // machine-dependent, skip
|
||||
} else {
|
||||
fmt.Printf(" %s: %s\n", key, cells[1].MustProperty("textContent"))
|
||||
}
|
||||
}
|
||||
|
||||
_ = utils.OutputFile("stealth_result.png", []byte{})
|
||||
}
|
||||
148
skills/go-rod-master/references/api-reference.md
Normal file
148
skills/go-rod-master/references/api-reference.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# Go-Rod API Quick Reference
|
||||
|
||||
Cheat sheet for the most-used `go-rod/rod` and `go-rod/stealth` APIs.
|
||||
Every `Must*` method has a corresponding error-returning version (without the `Must` prefix).
|
||||
|
||||
---
|
||||
|
||||
## Browser (`rod.Browser`)
|
||||
|
||||
| Method | Description |
|
||||
|:-------|:------------|
|
||||
| `rod.New().MustConnect()` | Launch new browser and connect |
|
||||
| `rod.New().ControlURL(url).MustConnect()` | Connect to existing browser via WebSocket URL |
|
||||
| `browser.MustClose()` | Close browser and all pages |
|
||||
| `browser.MustPage(url)` | Create new page (tab) and navigate |
|
||||
| `browser.MustPage()` | Create blank page |
|
||||
| `browser.MustIncognito()` | Create isolated incognito context |
|
||||
| `browser.MustIgnoreCertErrors(true)` | Ignore SSL certificate errors |
|
||||
| `browser.MustHandleAuth(user, pass)` | Handle HTTP basic/proxy auth |
|
||||
| `browser.HijackRequests()` | Create request interceptor router |
|
||||
| `browser.MustWaitDownload()` | Wait for a file download to complete |
|
||||
| `browser.ServeMonitor("")` | Start visual monitoring server |
|
||||
| `browser.Trace(true)` | Enable verbose tracing |
|
||||
| `browser.SlowMotion(duration)` | Add delay between actions |
|
||||
| `rod.NewPagePool(n)` | Create pool of max `n` reusable pages |
|
||||
| `rod.NewBrowserPool(n)` | Create pool of max `n` reusable browsers |
|
||||
|
||||
## Page (`rod.Page`)
|
||||
|
||||
| Method | Description |
|
||||
|:-------|:------------|
|
||||
| `page.MustNavigate(url)` | Navigate to URL |
|
||||
| `page.MustWaitLoad()` | Wait for `load` event |
|
||||
| `page.MustWaitStable()` | Wait until page DOM is stable |
|
||||
| `page.MustWaitRequestIdle()` | Wait until no pending network requests |
|
||||
| `page.MustWaitIdle()` | Wait for both load and request idle |
|
||||
| `page.MustWait(js)` | Wait for JS expression to return truthy |
|
||||
| `page.MustElement(selector)` | Find element by CSS selector (auto-wait) |
|
||||
| `page.MustElementR(selector, regex)` | Find element by CSS + text regex |
|
||||
| `page.MustElementX(xpath)` | Find element by XPath |
|
||||
| `page.MustElements(selector)` | Find all matching elements |
|
||||
| `page.MustSearch(query)` | Search across iframes + shadow DOM |
|
||||
| `page.MustEval(js, args...)` | Execute JavaScript on page |
|
||||
| `page.MustEvalOnNewDocument(js)` | Inject JS before any page script runs |
|
||||
| `page.MustScreenshot(path)` | Take PNG screenshot |
|
||||
| `page.MustPDF(path)` | Export page as PDF |
|
||||
| `page.ScrollScreenshot(opts)` | Full-page scroll screenshot |
|
||||
| `page.MustInfo()` | Get page info (title, URL) |
|
||||
| `page.Timeout(duration)` | Set timeout for chained operations |
|
||||
| `page.CancelTimeout()` | Remove timeout for subsequent operations |
|
||||
| `page.Race()` | Start race selector (multiple outcomes) |
|
||||
| `page.Keyboard` | Access keyboard controller |
|
||||
| `page.Mouse` | Access mouse controller |
|
||||
| `page.WaitEvent(proto)` | Wait for specific CDP event |
|
||||
| `page.EachEvent(handler)` | Subscribe to events continuously |
|
||||
| `page.Event()` | Channel-based event stream |
|
||||
|
||||
## Element (`rod.Element`)
|
||||
|
||||
| Method | Description |
|
||||
|:-------|:------------|
|
||||
| `el.MustClick()` | Click the element |
|
||||
| `el.MustInput(text)` | Clear and type text into input |
|
||||
| `el.MustType(keys...)` | Simulate key presses |
|
||||
| `el.MustText()` | Get text content |
|
||||
| `el.MustHTML()` | Get outer HTML |
|
||||
| `el.MustProperty(name)` | Get JS property value |
|
||||
| `el.MustAttribute(name)` | Get HTML attribute value |
|
||||
| `el.MustWaitStable()` | Wait until position/size stable |
|
||||
| `el.MustWaitVisible()` | Wait until element is visible |
|
||||
| `el.MustWaitInvisible()` | Wait until element is hidden |
|
||||
| `el.MustParents(selector)` | Find parent elements matching selector |
|
||||
| `el.MustElements(selector)` | Find child elements |
|
||||
| `el.MustMatches(selector)` | Check if element matches selector |
|
||||
| `el.MustEval(js)` | Eval JS with `this` = element |
|
||||
| `el.MustScreenshot(path)` | Screenshot just this element |
|
||||
|
||||
## Input (`rod/lib/input`)
|
||||
|
||||
| Constant | Description |
|
||||
|:---------|:------------|
|
||||
| `input.Enter` | Enter key |
|
||||
| `input.Escape` | Escape key |
|
||||
| `input.Tab` | Tab key |
|
||||
| `input.Slash` | `/` key |
|
||||
| `input.ControlLeft` | Left Ctrl |
|
||||
| `input.ShiftLeft` | Left Shift |
|
||||
| `input.KeyA` — `input.KeyZ` | Letter keys |
|
||||
| `input.MouseLeft` | Left mouse button |
|
||||
|
||||
## Launcher (`rod/lib/launcher`)
|
||||
|
||||
| Method | Description |
|
||||
|:-------|:------------|
|
||||
| `launcher.New()` | Create new launcher |
|
||||
| `l.Headless(bool)` | Enable/disable headless mode |
|
||||
| `l.Devtools(bool)` | Auto-open DevTools |
|
||||
| `l.Proxy(addr)` | Set proxy server |
|
||||
| `l.Set(flag, value)` | Set Chrome CLI flag |
|
||||
| `l.Delete(flag)` | Remove Chrome CLI flag |
|
||||
| `l.MustLaunch()` | Launch browser, return control URL |
|
||||
| `l.Cleanup()` | Kill browser process |
|
||||
| `launcher.NewBrowser().MustGet()` | Download browser binary |
|
||||
| `launcher.Open(url)` | Open URL in system browser |
|
||||
|
||||
## Stealth (`go-rod/stealth`)
|
||||
|
||||
| API | Description |
|
||||
|:----|:------------|
|
||||
| `stealth.MustPage(browser)` | Create stealth page (panics on error) |
|
||||
| `stealth.Page(browser)` | Create stealth page (returns error) |
|
||||
| `stealth.JS` | Raw JS string with all stealth evasions |
|
||||
|
||||
**What stealth.JS injects:**
|
||||
- Removes `navigator.webdriver` detection
|
||||
- Spoofs WebGL vendor/renderer to real GPU values
|
||||
- Fixes Chrome plugin array (`PluginArray` type, count=3)
|
||||
- Patches permissions API (returns `"prompt"`)
|
||||
- Sets realistic languages (`en-US,en`)
|
||||
- Fixes broken image dimensions (16x16 instead of 0x0)
|
||||
|
||||
## Network Hijacking (`rod.Hijack`)
|
||||
|
||||
| Method | Description |
|
||||
|:-------|:------------|
|
||||
| `router.MustAdd(pattern, handler)` | Add URL pattern handler |
|
||||
| `router.Run()` | Start intercepting (call with `go`) |
|
||||
| `router.MustStop()` | Stop intercepting |
|
||||
| `ctx.Request.Req()` | Access `*http.Request` |
|
||||
| `ctx.Request.URL()` | Get request URL |
|
||||
| `ctx.LoadResponse(client, true)` | Load response from server |
|
||||
| `ctx.MustLoadResponse()` | Load response (panics on error) |
|
||||
| `ctx.Response.Body()` | Get response body |
|
||||
| `ctx.Response.SetBody(s)` | Modify response body |
|
||||
| `ctx.Response.Fail(reason)` | Block the request |
|
||||
| `ctx.Response.Payload()` | Get response metadata |
|
||||
|
||||
## Direct CDP (`rod/lib/proto`)
|
||||
|
||||
```go
|
||||
// Call any CDP method directly
|
||||
proto.PageSetAdBlockingEnabled{Enabled: true}.Call(page)
|
||||
|
||||
// Or via generic JSON API
|
||||
page.Call(ctx, "", "Page.setAdBlockingEnabled", map[string]bool{"enabled": true})
|
||||
```
|
||||
|
||||
Full CDP protocol reference: https://chromedevtools.github.io/devtools-protocol/
|
||||
@@ -2,7 +2,7 @@
|
||||
name: m365-agents-ts
|
||||
description: |
|
||||
Microsoft 365 Agents SDK for TypeScript/Node.js. Build multichannel agents for Teams/M365/Copilot Studio with AgentApplication routing, Express hosting, streaming responses, and Copilot Studio client integration. Triggers: "Microsoft 365 Agents SDK", "@microsoft/agents-hosting", "AgentApplication", "startServer", "streamingResponse", "Copilot Studio client", "@microsoft/agents-copilotstudio-client".
|
||||
package: @microsoft/agents-hosting, @microsoft/agents-hosting-express, @microsoft/agents-activity, @microsoft/agents-copilotstudio-client
|
||||
package: "@microsoft/agents-hosting, @microsoft/agents-hosting-express, @microsoft/agents-activity, @microsoft/agents-copilotstudio-client"
|
||||
---
|
||||
|
||||
# Microsoft 365 Agents SDK (TypeScript)
|
||||
|
||||
@@ -1,22 +1,654 @@
|
||||
---
|
||||
name: threejs-skills
|
||||
description: "Three.js skills for creating 3D elements and interactive experiences"
|
||||
source: "https://github.com/CloudAI-X/threejs-skills"
|
||||
description: Create 3D scenes, interactive experiences, and visual effects using Three.js. Use when user requests 3D graphics, WebGL experiences, 3D visualizations, animations, or interactive 3D elements.
|
||||
source: https://github.com/CloudAI-X/threejs-skills
|
||||
risk: safe
|
||||
---
|
||||
|
||||
# Threejs Skills
|
||||
# Three.js Skills
|
||||
|
||||
## Overview
|
||||
Systematically create high-quality 3D scenes and interactive experiences using Three.js best practices.
|
||||
|
||||
Three.js skills for creating 3D elements and interactive experiences
|
||||
## When to Use
|
||||
|
||||
## When to Use This Skill
|
||||
- Requests 3D visualizations or graphics ("create a 3D model", "show in 3D")
|
||||
- Wants interactive 3D experiences ("rotating cube", "explorable scene")
|
||||
- Needs WebGL or canvas-based rendering
|
||||
- Asks for animations, particles, or visual effects
|
||||
- Mentions Three.js, WebGL, or 3D rendering
|
||||
- Wants to visualize data in 3D space
|
||||
|
||||
Use this skill when you need to work with three.js skills for creating 3d elements and interactive experiences.
|
||||
## Core Setup Pattern
|
||||
|
||||
## Instructions
|
||||
### 1. Essential Three.js Imports
|
||||
|
||||
This skill provides guidance and patterns for three.js skills for creating 3d elements and interactive experiences.
|
||||
Always use the correct CDN version (r128):
|
||||
|
||||
For more information, see the [source repository](https://github.com/CloudAI-X/threejs-skills).
|
||||
```javascript
|
||||
import * as THREE from "https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js";
|
||||
```
|
||||
|
||||
**CRITICAL**: Do NOT use example imports like `THREE.OrbitControls` - they won't work on the CDN.
|
||||
|
||||
### 2. Scene Initialization
|
||||
|
||||
Every Three.js artifact needs these core components:
|
||||
|
||||
```javascript
|
||||
// Scene - contains all 3D objects
|
||||
const scene = new THREE.Scene();
|
||||
|
||||
// Camera - defines viewing perspective
|
||||
const camera = new THREE.PerspectiveCamera(
|
||||
75, // Field of view
|
||||
window.innerWidth / window.innerHeight, // Aspect ratio
|
||||
0.1, // Near clipping plane
|
||||
1000, // Far clipping plane
|
||||
);
|
||||
camera.position.z = 5;
|
||||
|
||||
// Renderer - draws the scene
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
document.body.appendChild(renderer.domElement);
|
||||
```
|
||||
|
||||
### 3. Animation Loop
|
||||
|
||||
Use requestAnimationFrame for smooth rendering:
|
||||
|
||||
```javascript
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
// Update object transformations here
|
||||
mesh.rotation.x += 0.01;
|
||||
mesh.rotation.y += 0.01;
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
animate();
|
||||
```
|
||||
|
||||
## Systematic Development Process
|
||||
|
||||
### 1. Define the Scene
|
||||
|
||||
Start by identifying:
|
||||
|
||||
- **What objects** need to be rendered
|
||||
- **Camera position** and field of view
|
||||
- **Lighting setup** required
|
||||
- **Interaction model** (static, rotating, user-controlled)
|
||||
|
||||
### 2. Build Geometry
|
||||
|
||||
Choose appropriate geometry types:
|
||||
|
||||
**Basic Shapes:**
|
||||
|
||||
- `BoxGeometry` - cubes, rectangular prisms
|
||||
- `SphereGeometry` - spheres, planets
|
||||
- `CylinderGeometry` - cylinders, tubes
|
||||
- `PlaneGeometry` - flat surfaces, ground planes
|
||||
- `TorusGeometry` - donuts, rings
|
||||
|
||||
**IMPORTANT**: Do NOT use `CapsuleGeometry` (introduced in r142, not available in r128)
|
||||
|
||||
**Alternatives for capsules:**
|
||||
|
||||
- Combine `CylinderGeometry` + 2 `SphereGeometry`
|
||||
- Use `SphereGeometry` with adjusted parameters
|
||||
- Create custom geometry with vertices
|
||||
|
||||
### 3. Apply Materials
|
||||
|
||||
Choose materials based on visual needs:
|
||||
|
||||
**Common Materials:**
|
||||
|
||||
- `MeshBasicMaterial` - unlit, flat colors (no lighting needed)
|
||||
- `MeshStandardMaterial` - physically-based, realistic (needs lighting)
|
||||
- `MeshPhongMaterial` - shiny surfaces with specular highlights
|
||||
- `MeshLambertMaterial` - matte surfaces, diffuse reflection
|
||||
|
||||
```javascript
|
||||
const material = new THREE.MeshStandardMaterial({
|
||||
color: 0x00ff00,
|
||||
metalness: 0.5,
|
||||
roughness: 0.5,
|
||||
});
|
||||
```
|
||||
|
||||
### 4. Add Lighting
|
||||
|
||||
**If using lit materials** (Standard, Phong, Lambert), add lights:
|
||||
|
||||
```javascript
|
||||
// Ambient light - general illumination
|
||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
|
||||
scene.add(ambientLight);
|
||||
|
||||
// Directional light - like sunlight
|
||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
||||
directionalLight.position.set(5, 5, 5);
|
||||
scene.add(directionalLight);
|
||||
```
|
||||
|
||||
**Skip lighting** if using `MeshBasicMaterial` - it's unlit by design.
|
||||
|
||||
### 5. Handle Responsiveness
|
||||
|
||||
Always add window resize handling:
|
||||
|
||||
```javascript
|
||||
window.addEventListener("resize", () => {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
});
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Rotating Object
|
||||
|
||||
```javascript
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
mesh.rotation.x += 0.01;
|
||||
mesh.rotation.y += 0.01;
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Camera Controls (OrbitControls Alternative)
|
||||
|
||||
Since `THREE.OrbitControls` isn't available on CDN, implement custom controls:
|
||||
|
||||
```javascript
|
||||
let isDragging = false;
|
||||
let previousMousePosition = { x: 0, y: 0 };
|
||||
|
||||
renderer.domElement.addEventListener("mousedown", () => {
|
||||
isDragging = true;
|
||||
});
|
||||
|
||||
renderer.domElement.addEventListener("mouseup", () => {
|
||||
isDragging = false;
|
||||
});
|
||||
|
||||
renderer.domElement.addEventListener("mousemove", (event) => {
|
||||
if (isDragging) {
|
||||
const deltaX = event.clientX - previousMousePosition.x;
|
||||
const deltaY = event.clientY - previousMousePosition.y;
|
||||
|
||||
// Rotate camera around scene
|
||||
const rotationSpeed = 0.005;
|
||||
camera.position.x += deltaX * rotationSpeed;
|
||||
camera.position.y -= deltaY * rotationSpeed;
|
||||
camera.lookAt(scene.position);
|
||||
}
|
||||
|
||||
previousMousePosition = { x: event.clientX, y: event.clientY };
|
||||
});
|
||||
|
||||
// Zoom with mouse wheel
|
||||
renderer.domElement.addEventListener("wheel", (event) => {
|
||||
event.preventDefault();
|
||||
camera.position.z += event.deltaY * 0.01;
|
||||
camera.position.z = Math.max(2, Math.min(20, camera.position.z)); // Clamp
|
||||
});
|
||||
```
|
||||
|
||||
### Raycasting for Object Selection
|
||||
|
||||
Detect mouse clicks and hovers on 3D objects:
|
||||
|
||||
```javascript
|
||||
const raycaster = new THREE.Raycaster();
|
||||
const mouse = new THREE.Vector2();
|
||||
const clickableObjects = []; // Array of meshes that can be clicked
|
||||
|
||||
// Update mouse position
|
||||
window.addEventListener("mousemove", (event) => {
|
||||
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
||||
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
||||
});
|
||||
|
||||
// Detect clicks
|
||||
window.addEventListener("click", () => {
|
||||
raycaster.setFromCamera(mouse, camera);
|
||||
const intersects = raycaster.intersectObjects(clickableObjects);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
const clickedObject = intersects[0].object;
|
||||
// Handle click - change color, scale, etc.
|
||||
clickedObject.material.color.set(0xff0000);
|
||||
}
|
||||
});
|
||||
|
||||
// Hover effect in animation loop
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
raycaster.setFromCamera(mouse, camera);
|
||||
const intersects = raycaster.intersectObjects(clickableObjects);
|
||||
|
||||
// Reset all objects
|
||||
clickableObjects.forEach((obj) => {
|
||||
obj.scale.set(1, 1, 1);
|
||||
});
|
||||
|
||||
// Highlight hovered object
|
||||
if (intersects.length > 0) {
|
||||
intersects[0].object.scale.set(1.2, 1.2, 1.2);
|
||||
document.body.style.cursor = "pointer";
|
||||
} else {
|
||||
document.body.style.cursor = "default";
|
||||
}
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
```
|
||||
|
||||
### Particle System
|
||||
|
||||
```javascript
|
||||
const particlesGeometry = new THREE.BufferGeometry();
|
||||
const particlesCount = 1000;
|
||||
const posArray = new Float32Array(particlesCount * 3);
|
||||
|
||||
for (let i = 0; i < particlesCount * 3; i++) {
|
||||
posArray[i] = (Math.random() - 0.5) * 10;
|
||||
}
|
||||
|
||||
particlesGeometry.setAttribute(
|
||||
"position",
|
||||
new THREE.BufferAttribute(posArray, 3),
|
||||
);
|
||||
|
||||
const particlesMaterial = new THREE.PointsMaterial({
|
||||
size: 0.02,
|
||||
color: 0xffffff,
|
||||
});
|
||||
|
||||
const particlesMesh = new THREE.Points(particlesGeometry, particlesMaterial);
|
||||
scene.add(particlesMesh);
|
||||
```
|
||||
|
||||
### User Interaction (Mouse Movement)
|
||||
|
||||
```javascript
|
||||
let mouseX = 0;
|
||||
let mouseY = 0;
|
||||
|
||||
document.addEventListener("mousemove", (event) => {
|
||||
mouseX = (event.clientX / window.innerWidth) * 2 - 1;
|
||||
mouseY = -(event.clientY / window.innerHeight) * 2 + 1;
|
||||
});
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
camera.position.x = mouseX * 2;
|
||||
camera.position.y = mouseY * 2;
|
||||
camera.lookAt(scene.position);
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
```
|
||||
|
||||
### Loading Textures
|
||||
|
||||
```javascript
|
||||
const textureLoader = new THREE.TextureLoader();
|
||||
const texture = textureLoader.load("texture-url.jpg");
|
||||
|
||||
const material = new THREE.MeshStandardMaterial({
|
||||
map: texture,
|
||||
});
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Performance
|
||||
|
||||
- **Reuse geometries and materials** when creating multiple similar objects
|
||||
- **Use `BufferGeometry`** for custom shapes (more efficient)
|
||||
- **Limit particle counts** to maintain 60fps (start with 1000-5000)
|
||||
- **Dispose of resources** when removing objects:
|
||||
```javascript
|
||||
geometry.dispose();
|
||||
material.dispose();
|
||||
texture.dispose();
|
||||
```
|
||||
|
||||
### Visual Quality
|
||||
|
||||
- Always set `antialias: true` on renderer for smooth edges
|
||||
- Use appropriate camera FOV (45-75 degrees typical)
|
||||
- Position lights thoughtfully - avoid overlapping multiple bright lights
|
||||
- Add ambient + directional lighting for realistic scenes
|
||||
|
||||
### Code Organization
|
||||
|
||||
- Initialize scene, camera, renderer at the top
|
||||
- Group related objects (e.g., all particles in one group)
|
||||
- Keep animation logic in the animate function
|
||||
- Separate object creation into functions for complex scenes
|
||||
|
||||
### Common Pitfalls to Avoid
|
||||
|
||||
- ❌ Using `THREE.OrbitControls` - not available on CDN
|
||||
- ❌ Using `THREE.CapsuleGeometry` - requires r142+
|
||||
- ❌ Forgetting to add objects to scene with `scene.add()`
|
||||
- ❌ Using lit materials without adding lights
|
||||
- ❌ Not handling window resize
|
||||
- ❌ Forgetting to call `renderer.render()` in animation loop
|
||||
|
||||
## Example Workflow
|
||||
|
||||
User: "Create an interactive 3D sphere that responds to mouse movement"
|
||||
|
||||
1. **Setup**: Import Three.js (r128), create scene/camera/renderer
|
||||
2. **Geometry**: Create `SphereGeometry(1, 32, 32)` for smooth sphere
|
||||
3. **Material**: Use `MeshStandardMaterial` for realistic look
|
||||
4. **Lighting**: Add ambient + directional lights
|
||||
5. **Interaction**: Track mouse position, update camera
|
||||
6. **Animation**: Rotate sphere, render continuously
|
||||
7. **Responsive**: Add window resize handler
|
||||
8. **Result**: Smooth, interactive 3D sphere ✓
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Black screen / Nothing renders:**
|
||||
|
||||
- Check if objects added to scene
|
||||
- Verify camera position isn't inside objects
|
||||
- Ensure renderer.render() is called
|
||||
- Add lights if using lit materials
|
||||
|
||||
**Poor performance:**
|
||||
|
||||
- Reduce particle count
|
||||
- Lower geometry detail (segments)
|
||||
- Reuse materials/geometries
|
||||
- Check browser console for errors
|
||||
|
||||
**Objects not visible:**
|
||||
|
||||
- Check object position vs camera position
|
||||
- Verify material has visible color/properties
|
||||
- Ensure camera far plane includes objects
|
||||
- Add lighting if needed
|
||||
|
||||
## Advanced Techniques
|
||||
|
||||
### Visual Polish for Portfolio-Grade Rendering
|
||||
|
||||
**Shadows:**
|
||||
|
||||
```javascript
|
||||
// Enable shadows on renderer
|
||||
renderer.shadowMap.enabled = true;
|
||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Soft shadows
|
||||
|
||||
// Light that casts shadows
|
||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
|
||||
directionalLight.position.set(5, 10, 5);
|
||||
directionalLight.castShadow = true;
|
||||
|
||||
// Configure shadow quality
|
||||
directionalLight.shadow.mapSize.width = 2048;
|
||||
directionalLight.shadow.mapSize.height = 2048;
|
||||
directionalLight.shadow.camera.near = 0.5;
|
||||
directionalLight.shadow.camera.far = 50;
|
||||
|
||||
scene.add(directionalLight);
|
||||
|
||||
// Objects cast and receive shadows
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
|
||||
// Ground plane receives shadows
|
||||
const groundGeometry = new THREE.PlaneGeometry(20, 20);
|
||||
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x808080 });
|
||||
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
|
||||
ground.rotation.x = -Math.PI / 2;
|
||||
ground.receiveShadow = true;
|
||||
scene.add(ground);
|
||||
```
|
||||
|
||||
**Environment Maps & Reflections:**
|
||||
|
||||
```javascript
|
||||
// Create environment map from cubemap
|
||||
const loader = new THREE.CubeTextureLoader();
|
||||
const envMap = loader.load([
|
||||
"px.jpg",
|
||||
"nx.jpg", // positive x, negative x
|
||||
"py.jpg",
|
||||
"ny.jpg", // positive y, negative y
|
||||
"pz.jpg",
|
||||
"nz.jpg", // positive z, negative z
|
||||
]);
|
||||
|
||||
scene.environment = envMap; // Affects all PBR materials
|
||||
scene.background = envMap; // Optional: use as skybox
|
||||
|
||||
// Or apply to specific materials
|
||||
const material = new THREE.MeshStandardMaterial({
|
||||
metalness: 1.0,
|
||||
roughness: 0.1,
|
||||
envMap: envMap,
|
||||
});
|
||||
```
|
||||
|
||||
**Tone Mapping & Output Encoding:**
|
||||
|
||||
```javascript
|
||||
// Improve color accuracy and HDR rendering
|
||||
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||||
renderer.toneMappingExposure = 1.0;
|
||||
renderer.outputEncoding = THREE.sRGBEncoding;
|
||||
|
||||
// Makes colors more vibrant and realistic
|
||||
```
|
||||
|
||||
**Fog for Depth:**
|
||||
|
||||
```javascript
|
||||
// Linear fog
|
||||
scene.fog = new THREE.Fog(0xcccccc, 10, 50); // color, near, far
|
||||
|
||||
// Or exponential fog (more realistic)
|
||||
scene.fog = new THREE.FogExp2(0xcccccc, 0.02); // color, density
|
||||
```
|
||||
|
||||
### Custom Geometry from Vertices
|
||||
|
||||
```javascript
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
const vertices = new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0]);
|
||||
geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
|
||||
```
|
||||
|
||||
### Post-Processing Effects
|
||||
|
||||
While advanced post-processing may not be available in r128 CDN, basic effects can be achieved with shaders and render targets.
|
||||
|
||||
### Group Objects
|
||||
|
||||
```javascript
|
||||
const group = new THREE.Group();
|
||||
group.add(mesh1);
|
||||
group.add(mesh2);
|
||||
group.rotation.y = Math.PI / 4;
|
||||
scene.add(group);
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
Three.js artifacts require systematic setup:
|
||||
|
||||
1. Import correct CDN version (r128)
|
||||
2. Initialize scene, camera, renderer
|
||||
3. Create geometry + material = mesh
|
||||
4. Add lighting if using lit materials
|
||||
5. Implement animation loop
|
||||
6. Handle window resize
|
||||
7. Avoid r128 incompatible features
|
||||
|
||||
Follow these patterns for reliable, performant 3D experiences.
|
||||
|
||||
## Modern Three.js & Production Practices
|
||||
|
||||
While this skill focuses on CDN-based Three.js (r128) for artifact compatibility, here's what you'd do in production environments:
|
||||
|
||||
### Modular Imports with Build Tools
|
||||
|
||||
```javascript
|
||||
// In production with npm/vite/webpack:
|
||||
import * as THREE from "three";
|
||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer";
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- Tree-shaking (smaller bundle sizes)
|
||||
- Access to full example library (OrbitControls, loaders, etc.)
|
||||
- Latest Three.js features (r150+)
|
||||
- TypeScript support
|
||||
|
||||
### Animation Libraries (GSAP Integration)
|
||||
|
||||
```javascript
|
||||
// Smooth timeline-based animations
|
||||
import gsap from "gsap";
|
||||
|
||||
// Instead of manual animation loops:
|
||||
gsap.to(mesh.position, {
|
||||
x: 5,
|
||||
duration: 2,
|
||||
ease: "power2.inOut",
|
||||
});
|
||||
|
||||
// Complex sequences:
|
||||
const timeline = gsap.timeline();
|
||||
timeline
|
||||
.to(mesh.rotation, { y: Math.PI * 2, duration: 2 })
|
||||
.to(mesh.scale, { x: 2, y: 2, z: 2, duration: 1 }, "-=1");
|
||||
```
|
||||
|
||||
**Why GSAP:**
|
||||
|
||||
- Professional easing functions
|
||||
- Timeline control (pause, reverse, scrub)
|
||||
- Better than manual lerping for complex animations
|
||||
|
||||
### Scroll-Based Interactions
|
||||
|
||||
```javascript
|
||||
// Sync 3D animations with page scroll
|
||||
let scrollY = window.scrollY;
|
||||
|
||||
window.addEventListener("scroll", () => {
|
||||
scrollY = window.scrollY;
|
||||
});
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
// Rotate based on scroll position
|
||||
mesh.rotation.y = scrollY * 0.001;
|
||||
|
||||
// Move camera through scene
|
||||
camera.position.y = -(scrollY / window.innerHeight) * 10;
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
```
|
||||
|
||||
**Advanced scroll libraries:**
|
||||
|
||||
- ScrollTrigger (GSAP plugin)
|
||||
- Locomotive Scroll
|
||||
- Lenis smooth scroll
|
||||
|
||||
### Performance Optimization in Production
|
||||
|
||||
```javascript
|
||||
// Level of Detail (LOD)
|
||||
const lod = new THREE.LOD();
|
||||
lod.addLevel(highDetailMesh, 0); // Close up
|
||||
lod.addLevel(mediumDetailMesh, 10); // Medium distance
|
||||
lod.addLevel(lowDetailMesh, 50); // Far away
|
||||
scene.add(lod);
|
||||
|
||||
// Instanced meshes for many identical objects
|
||||
const geometry = new THREE.BoxGeometry();
|
||||
const material = new THREE.MeshStandardMaterial();
|
||||
const instancedMesh = new THREE.InstancedMesh(geometry, material, 1000);
|
||||
|
||||
// Set transforms for each instance
|
||||
const matrix = new THREE.Matrix4();
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
matrix.setPosition(
|
||||
Math.random() * 100,
|
||||
Math.random() * 100,
|
||||
Math.random() * 100,
|
||||
);
|
||||
instancedMesh.setMatrixAt(i, matrix);
|
||||
}
|
||||
```
|
||||
|
||||
### Modern Loading Patterns
|
||||
|
||||
```javascript
|
||||
// In production, load 3D models:
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
|
||||
const loader = new GLTFLoader();
|
||||
loader.load("model.gltf", (gltf) => {
|
||||
scene.add(gltf.scene);
|
||||
|
||||
// Traverse and setup materials
|
||||
gltf.scene.traverse((child) => {
|
||||
if (child.isMesh) {
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### When to Use What
|
||||
|
||||
**CDN Approach (Current Skill):**
|
||||
|
||||
- Quick prototypes and demos
|
||||
- Educational content
|
||||
- Artifacts and embedded experiences
|
||||
- No build step required
|
||||
|
||||
**Production Build Approach:**
|
||||
|
||||
- Client projects and portfolios
|
||||
- Complex applications
|
||||
- Need latest features (r150+)
|
||||
- Performance-critical applications
|
||||
- Team collaboration with version control
|
||||
|
||||
### Recommended Production Stack
|
||||
|
||||
```
|
||||
Three.js (latest) + Vite/Webpack
|
||||
├── GSAP (animations)
|
||||
├── React Three Fiber (optional - React integration)
|
||||
├── Drei (helper components)
|
||||
├── Leva (debug GUI)
|
||||
└── Post-processing effects
|
||||
```
|
||||
|
||||
This skill provides CDN-compatible foundations. In production, you'd layer on these modern tools for professional results.
|
||||
|
||||
@@ -3923,6 +3923,15 @@
|
||||
"risk": "safe",
|
||||
"source": "https://github.com/playwright-community/playwright-go"
|
||||
},
|
||||
{
|
||||
"id": "go-rod-master",
|
||||
"path": "skills/go-rod-master",
|
||||
"category": "uncategorized",
|
||||
"name": "go-rod-master",
|
||||
"description": "Comprehensive guide for browser automation and web scraping with go-rod (Chrome DevTools Protocol) including stealth anti-bot-detection patterns.",
|
||||
"risk": "safe",
|
||||
"source": "https://github.com/go-rod/rod"
|
||||
},
|
||||
{
|
||||
"id": "godot-gdscript-patterns",
|
||||
"path": "skills/godot-gdscript-patterns",
|
||||
@@ -7006,7 +7015,7 @@
|
||||
"path": "skills/threejs-skills",
|
||||
"category": "uncategorized",
|
||||
"name": "threejs-skills",
|
||||
"description": "Three.js skills for creating 3D elements and interactive experiences",
|
||||
"description": "Create 3D scenes, interactive experiences, and visual effects using Three.js. Use when user requests 3D graphics, WebGL experiences, 3D visualizations, animations, or interactive 3D elements.",
|
||||
"risk": "safe",
|
||||
"source": "https://github.com/CloudAI-X/threejs-skills"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user