video-sdk/web
Zoom Video SDK for Web - JavaScript/TypeScript integration for browser-based video sessions, real-time communication, screen sharing, recording, and live transcription
- License 许可
- Apache-2.0
- 最近更新
- 2026-11-11
- Stars
- ★ 3.5w
- Forks
- 3.9k
01 前言 Front Matter
- name
- video-sdk/web
- triggers
- [ "video sdk web", "custom video web", "attachvideo", "peer-video-state-change", "web videosdk" ]
- description
- Zoom Video SDK for Web - JavaScript/TypeScript integration for browser-based video sessions, real-time communication, screen sharing, recording, and live transcription
- user-invocable
- false
02 资源文件 17
- 参考文档concepts/sdk-architecture-pattern.md8.3 KB
- 参考文档concepts/singleton-hierarchy.md18.9 KB
- 参考文档examples/chat.md12.0 KB
- 参考文档examples/command-channel.md7.1 KB
- 参考文档examples/event-handling.md14.1 KB
- 参考文档examples/framework-integrations.md10.8 KB
- 参考文档examples/react-hooks.md9.8 KB
- 参考文档examples/recording.md10.4 KB
- 参考文档examples/screen-share.md12.1 KB
- 参考文档examples/session-join-pattern.md10.2 KB
- 参考文档examples/transcription.md13.9 KB
- 参考文档examples/video-rendering.md10.0 KB
- 参考文档references/events-reference.md12.8 KB
- 参考文档references/web-reference.md15.5 KB
- 参考文档references/web.md26.2 KB
- 参考文档RUNBOOK.md2.5 KB
- 参考文档troubleshooting/common-issues.md11.0 KB
03 SKILL.md 正文
Zoom Video SDK - Web Development
Expert guidance for developing with the Zoom Video SDK on Web. This SDK enables custom video applications in the browser with real-time video/audio, screen sharing, cloud recording, live streaming, chat, and live transcription.
This skill is for custom video sessions, not embedded Zoom meetings. If the user wants a custom UI for a real Zoom meeting, route to ../../meeting-sdk/web/component-view/SKILL.md.
Official Documentation: https://developers.zoom.us/docs/video-sdk/web/ API Reference: https://marketplacefront.zoom.us/sdk/custom/web/modules.html Sample Repository: https://github.com/zoom/videosdk-web-sample
Quick Links
New to Video SDK? Follow this path:
- SDK Architecture Pattern - Universal 3-step pattern for ANY feature
- Session Join Pattern - Complete working code to join a session
- Video Rendering - Display video with attachVideo()
- Event Handling - Required events for video/audio
Reference:
- Singleton Hierarchy - 4-level SDK navigation map
- API Reference - Methods, events, error codes
- SKILL.md - Complete documentation navigation
- ../../probe-sdk/SKILL.md - Optional browser/device/network readiness diagnostics before join
Having issues?
- Video not showing → Video Rendering (use attachVideo, not renderVideo)
- getMediaStream() returns undefined → Call AFTER join() completes
- Quick diagnostics → Common Issues
SDK Overview
The Zoom Video SDK for Web is a JavaScript library that provides:
- Session Management: Join/leave video SDK sessions
- Video/Audio: Start/stop camera and microphone
- Screen Sharing: Share screens or browser tabs
- Cloud Recording: Record sessions to Zoom cloud
- Live Streaming: Stream to RTMP endpoints
- Chat: In-session messaging
- Command Channel: Custom command messaging
- Live Transcription: Real-time speech-to-text
- Subsessions: Breakout room support
- Whiteboard: Collaborative whiteboard features
- Virtual Background: Blur or custom image backgrounds
Prerequisites
System Requirements
- Modern Browser: Chrome 80+, Firefox 75+, Safari 14+, Edge 80+
- Video SDK Credentials: SDK Key and Secret from Marketplace
- JWT Token: Server-side generated signature
Browser Feature Requirements
// Check browser compatibility before init
const compatibility = ZoomVideo.checkSystemRequirements();
console.log('Audio:', compatibility.audio);
console.log('Video:', compatibility.video);
console.log('Screen:', compatibility.screen);
// Check feature support
const features = ZoomVideo.checkFeatureRequirements();
console.log('Supported:', features.supportFeatures);
console.log('Unsupported:', features.unSupportFeatures);
Optional Pre-Join Diagnostics (Recommended for Reliability)
Use Probe SDK as a readiness gate before client.join(...) when you need to reduce failed starts:
- Run diagnostics with ../../probe-sdk/SKILL.md.
- Evaluate policy (
allow,warn,block). - Start Video SDK join only when policy allows.
Cross-skill flow: ../../general/use-cases/probe-sdk-preflight-readiness-gate.md
Installation
NPM (Recommended)
npm install @zoom/videosdk
import ZoomVideo from '@zoom/videosdk';
CDN (Fallback Strategy Recommended)
Note: Some networks/ad blockers can block
source.zoom.us. If you see flaky loads, first try allowlisting the domain in your environment. If needed, consider a fallback (mirror/self-host) only if it's permitted for your use case and you can keep versions in sync.
# Download SDK locally
curl "https://source.zoom.us/videosdk/zoom-video-2.3.12.min.js" -o public/js/zoom-video-sdk.min.js
<!-- Use local copy instead of CDN -->
<script src="js/zoom-video-sdk.min.js"></script>
// CDN exports as WebVideoSDK, NOT ZoomVideo
const ZoomVideo = WebVideoSDK.default;
Quick Start
import ZoomVideo from '@zoom/videosdk';
// 1. Create client (singleton - returns same instance)
const client = ZoomVideo.createClient();
// 2. Initialize SDK
await client.init('en-US', 'Global', { patchJsMedia: true });
// 3. Join session
await client.join(topic, signature, userName, password);
// 4. CRITICAL: Get stream AFTER join
const stream = client.getMediaStream();
// 5. Start media
await stream.startVideo();
await stream.startAudio();
// 6. Attach video to DOM
const videoElement = await stream.attachVideo(userId, VideoQuality.Video_360P);
document.getElementById('video-container').appendChild(videoElement);
SDK Lifecycle (CRITICAL ORDER)
The SDK has a strict lifecycle. Violating it causes silent failures.
1. Create client: client = ZoomVideo.createClient()
2. Initialize: await client.init('en-US', 'Global', options)
3. Join session: await client.join(topic, signature, userName, password)
4. Get stream: stream = client.getMediaStream() ← ONLY AFTER JOIN
5. Start media: await stream.startVideo() / await stream.startAudio()
Common Mistake:
// WRONG: Getting stream before joining
const stream = client.getMediaStream(); // Returns undefined!
await client.join(...);
// CORRECT: Get stream after joining
await client.join(...);
const stream = client.getMediaStream(); // Works!
Critical Gotchas and Best Practices
getMediaStream() ONLY Works After join()
The #1 issue that causes video/audio to fail:
// WRONG
const stream = client.getMediaStream(); // undefined!
await client.join(...);
// CORRECT
await client.join(...);
const stream = client.getMediaStream(); // Works
Use attachVideo() NOT renderVideo()
renderVideo() is deprecated. Use attachVideo() which returns a VideoPlayer element:
import { VideoQuality } from '@zoom/videosdk';
// CORRECT: attachVideo returns element to append
const videoElement = await stream.attachVideo(userId, VideoQuality.Video_360P);
document.getElementById('video-container').appendChild(videoElement);
// WRONG: renderVideo is deprecated
await stream.renderVideo(canvas, userId, ...); // Don't use!
Video Rendering is Event-Driven (CRITICAL)
You MUST listen for events to properly render participant videos:
// When another participant's video state changes
client.on('peer-video-state-change', async (payload) => {
const { action, userId } = payload;
if (action === 'Start') {
// Participant turned on video - attach it
const element = await stream.attachVideo(userId, VideoQuality.Video_360P);
container.appendChild(element);
} else if (action === 'Stop') {
// Participant turned off video - detach it
await stream.detachVideo(userId);
}
});
// When participants join/leave
client.on('user-added', (payload) => {
// New participant joined - check if their video is on
const users = client.getAllUser();
// Render videos for users with bVideoOn === true
});
client.on('user-removed', (payload) => {
// Participant left - clean up their video element
stream.detachVideo(payload[0].userId);
});
Peer Video on Mid-Session Join
Existing participants' videos won't auto-render when you join mid-session.
// After joining, render existing participants' videos
const renderExistingVideos = async () => {
await new Promise(resolve => setTimeout(resolve, 500));
const users = client.getAllUser();
const currentUserId = client.getCurrentUserInfo().userId;
for (const user of users) {
if (user.bVideoOn && user.userId !== currentUserId) {
const element = await stream.attachVideo(user.userId, VideoQuality.Video_360P);
document.getElementById(`video-${user.userId}`).appendChild(element);
}
}
};
CDN Race Condition with ES Modules
When using <script type="module"> with CDN, the SDK may not be loaded yet:
function waitForSDK(timeout = 10000) {
return new Promise((resolve, reject) => {
if (typeof WebVideoSDK !== 'undefined') {
resolve();
return;
}
const start = Date.now();
const check = setInterval(() => {
if (typeof WebVideoSDK !== 'undefined') {
clearInterval(check);
resolve();
} else if (Date.now() - start > timeout) {
clearInterval(check);
reject(new Error('SDK failed to load'));
}
}, 100);
});
}
await waitForSDK();
const ZoomVideo = WebVideoSDK.default;
SharedArrayBuffer for HD Video
For optimal performance and HD video, configure these headers on your server:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Note: As of v1.11.2, SharedArrayBuffer is elective (not strictly required).
Check HD Capability Before Enabling
const stream = client.getMediaStream();
// Check if 720p is supported
const hdSupported = stream.isSupportHDVideo();
// Get maximum video quality
const maxQuality = stream.getVideoMaxQuality();
// 0=90P, 1=180P, 2=360P, 3=720P, 4=1080P
// Start video with HD
if (hdSupported) {
await stream.startVideo({ hd: true });
}
Screen Share Rendering Mode Check
const stream = client.getMediaStream();
// Check which element type to use
if (stream.isStartShareScreenWithVideoElement()) {
// Use video element
const video = document.getElementById('share-video');
await stream.startShareScreen(video as unknown as HTMLCanvasElement);
} else {
// Use canvas element
const canvas = document.getElementById('share-canvas');
await stream.startShareScreen(canvas);
}
Key Features
Video Quality Enum
import { VideoQuality } from '@zoom/videosdk';
VideoQuality.Video_90P // 0
VideoQuality.Video_180P // 1
VideoQuality.Video_360P // 2 (recommended for most cases)
VideoQuality.Video_720P // 3
VideoQuality.Video_1080P // 4
Virtual Backgrounds
const stream = client.getMediaStream();
// Always check support first
if (stream.isSupportVirtualBackground()) {
// Blur background
await stream.updateVirtualBackgroundImage('blur');
// Custom image background
await stream.updateVirtualBackgroundImage('https://example.com/bg.jpg');
// Remove virtual background
await stream.updateVirtualBackgroundImage(undefined);
}
Video Processor (Custom Effects)
The VideoProcessor class allows you to intercept and modify video frames:
// video-processor-worker.js
class MyVideoProcessor extends VideoProcessor {
processFrame(input, output) {
const ctx = output.getContext('2d');
ctx.drawImage(input, 0, 0);
// Add overlay
ctx.fillStyle = 'white';
ctx.font = '24px Arial';
ctx.fillText('Live', 20, 40);
return true;
}
}
WebRTC Mode
Enable WebRTC mode for direct peer-to-peer streaming with HD video support:
await client.init('en-US', 'Global', {
patchJsMedia: true,
webrtc: true // Enable WebRTC mode
});
Feature Clients
Access specialized clients from the VideoClient:
| Client | Access Method | Purpose |
|---|---|---|
| Stream | client.getMediaStream() |
Video, audio, screen share, devices |
| Chat | client.getChatClient() |
Send/receive messages |
| Command | client.getCommandClient() |
Custom commands (reactions, etc.) |
| Recording | client.getRecordingClient() |
Cloud recording control |
| Transcription | client.getLiveTranscriptionClient() |
Live captions |
| LiveStream | client.getLiveStreamClient() |
RTMP streaming |
| Subsession | client.getSubsessionClient() |
Breakout rooms |
| Whiteboard | client.getWhiteboardClient() |
Collaborative whiteboard |
Common Tasks
Start/Stop Video
await stream.startVideo();
await stream.stopVideo();
Start/Stop Audio
await stream.startAudio();
await stream.muteAudio();
await stream.unmuteAudio();
await stream.stopAudio();
Switch Devices
// Get available devices
const cameras = stream.getCameraList();
const mics = stream.getMicList();
const speakers = stream.getSpeakerList();
// Switch devices
await stream.switchCamera(cameraId);
await stream.switchMicrophone(micId);
await stream.switchSpeaker(speakerId);
Screen Sharing
// Start sharing
await stream.startShareScreen(canvas);
// Stop sharing
await stream.stopShareScreen();
// Receive share
client.on('active-share-change', async (payload) => {
if (payload.state === 'Active') {
await stream.startShareView(canvas, payload.userId);
} else {
await stream.stopShareView();
}
});
Chat
const chatClient = client.getChatClient();
// Send to everyone
await chatClient.send('Hello, everyone!');
// Send to specific user
await chatClient.sendToUser(userId, 'Private message');
// Receive messages
client.on('chat-on-message', (payload) => {
console.log(`${payload.sender.name}: ${payload.message}`);
});
Recording (Host Only)
const recordingClient = client.getRecordingClient();
await recordingClient.startCloudRecording();
await recordingClient.stopCloudRecording();
client.on('recording-change', (payload) => {
console.log('Recording status:', payload.state);
});
Leave/End Session
// Leave session (others stay)
await client.leave();
// End session for ALL participants (host only)
await client.leave(true);
Error Handling
Common Join Errors
| Error | Cause | Solution |
|---|---|---|
Invalid signature |
JWT expired or malformed | Generate new signature |
Session does not exist |
Host hasn't started yet | Show "waiting" message, retry |
Permission denied |
User denied camera/mic | Request permission again |
Example Error Handler
try {
await client.join(topic, signature, userName, password);
} catch (error) {
if (error.reason?.includes('signature')) {
// Regenerate signature and retry
} else if (error.reason?.includes('Session')) {
// Show "Waiting for host..." and poll
} else if (error.reason?.includes('Permission')) {
// Guide user to enable permissions
}
console.error('Join failed:', error);
}
Browser Compatibility
| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| Video | 80+ | 75+ | 14+ | 80+ |
| Audio | 80+ | 75+ | 14+ | 80+ |
| Screen Share | 80+ | 75+ | 15+ | 80+ |
| Virtual BG | 80+ | 90+ | - | 80+ |
Safari Notes:
- Virtual background not supported
- Screen sharing requires macOS 15+
CORS Errors (Telemetry)
CORS errors to log-external-gateway.zoom.us are harmless.
These are caused by COOP/COEP headers blocking telemetry requests. They don't affect SDK functionality.
Complete Documentation Library
Core Concepts
- SDK Architecture Pattern - Universal 3-step pattern for ANY feature
- Singleton Hierarchy - 4-level navigation guide
Complete Examples
- Session Join Pattern - JWT auth + session join with full code
- Video Rendering - attachVideo() patterns
- Screen Share - Send and receive screen shares
- Event Handling - Required events
- Chat - In-session messaging
- Recording - Cloud recording control
- Transcription - Live captions
Framework Integrations
- React Hooks - Official @zoom/videosdk-react library
- Framework Integrations - Next.js, Vue/Nuxt patterns
Troubleshooting
- Common Issues - Quick diagnostics & error codes
References
- API Reference - Complete method signatures
- Events Reference - All event types
- SKILL.md - Complete navigation guide
Official Sample Repositories
| Type | Repository |
|---|---|
| Web Sample | videosdk-web-sample |
| React SDK | videosdk-react |
| Next.js | videosdk-nextjs-quickstart |
| Vue/Nuxt | videosdk-vue-nuxt-quickstart |
| Auth Endpoint | videosdk-auth-endpoint-sample |
| UI Toolkit | videosdk-zoom-ui-toolkit-react-sample |
Resources
- Official Docs: https://developers.zoom.us/docs/video-sdk/web/
- API Reference: https://marketplacefront.zoom.us/sdk/custom/web/modules.html
- Dev Forum: https://devforum.zoom.us/
- GitHub Samples: https://github.com/zoom/videosdk-web-sample
Need help? Start with SKILL.md for complete navigation.
Merged from video-sdk/web/SKILL.md
Zoom Video SDK Web - Complete Documentation Index
Quick Start Path
If you're new to the SDK, follow this order:
-
Read the architecture pattern → concepts/sdk-architecture-pattern.md
- Universal formula: Create Client → Init → Join → Get Stream → Use
- Once you understand this, you can implement any feature
-
Implement session join → examples/session-join-pattern.md
- Complete working JWT + session join code
-
Listen to events → examples/event-handling.md
- CRITICAL: The SDK is event-driven, you must listen for events
-
Implement video → examples/video-rendering.md
- Use attachVideo(), NOT renderVideo()
-
Troubleshoot any issues → troubleshooting/common-issues.md
- Quick diagnostic checklist
- Error code tables
Documentation Structure
video-sdk/web/
├── SKILL.md # Main skill overview
├── SKILL.md # This file - navigation guide
│
├── concepts/ # Core architectural patterns
│ ├── sdk-architecture-pattern.md # Universal formula for ANY feature
│ └── singleton-hierarchy.md # 4-level navigation guide
│
├── examples/ # Complete working code
│ ├── session-join-pattern.md # JWT auth + session join
│ ├── video-rendering.md # attachVideo() patterns
│ ├── screen-share.md # Send and receive screen shares
│ ├── event-handling.md # Required events
│ ├── chat.md # Chat implementation
│ ├── command-channel.md # Command channel messaging
│ ├── recording.md # Cloud recording control
│ ├── transcription.md # Live transcription/captions
│ ├── react-hooks.md # Official @zoom/videosdk-react library
│ └── framework-integrations.md # Next.js, Vue/Nuxt, ZFG patterns
│
├── troubleshooting/ # Problem solving guides
│ └── common-issues.md # Quick diagnostic workflow
│
└── references/ # Reference documentation
├── web-reference.md # API hierarchy, methods, error codes
└── events-reference.md # All event types
By Use Case
I want to build a video app
- SDK Architecture Pattern - Understand the pattern
- Session Join Pattern - Join sessions
- Video Rendering - Display video
- Event Handling - Listen for video events
I'm getting runtime errors
- Common Issues - Error code tables
- "getMediaStream() is undefined" → Call AFTER join() completes
I want to receive screen shares
- Screen Share - startShareView() patterns
- Event Handling - active-share-change event
I want to send screen shares
- Screen Share - startShareScreen() patterns
- Check isStartShareScreenWithVideoElement() for element type
I want to use chat
- Chat - Send/receive messages
- getChatClient() for ChatClient access
I want to record sessions
- Recording - Cloud recording (host only)
- getRecordingClient() for RecordingClient access
I want to use live transcription
- Transcription - Enable live captions
- getLiveTranscriptionClient() for LiveTranscriptionClient access
I want to use command channel
- Command Channel - Custom signaling between participants
- Must call getCommandClient() AFTER join()
I want to implement a specific feature
- SDK Architecture Pattern - START HERE!
- Singleton Hierarchy - Navigate to the feature
- API Reference - Method signatures
I'm using React
- React Hooks - Official @zoom/videosdk-react library
- Provides hooks: useSession, useSessionUsers, useVideoState, useAudioState
- Pre-built components: VideoPlayerComponent, ScreenSharePlayerComponent
I'm using Next.js or Vue/Nuxt
- Framework Integrations - SSR considerations
- Server-side JWT generation patterns
- Client-side only SDK usage
Most Critical Documents
1. SDK Architecture Pattern (MASTER DOCUMENT)
concepts/sdk-architecture-pattern.md
The universal 5-step pattern:
- Create client
- Initialize SDK
- Join session
- Get stream
- Use features + listen to events
2. Common Issues (MOST COMMON PROBLEMS)
troubleshooting/common-issues.md
Common issues:
- getMediaStream() returns undefined
- Video not displaying
- renderVideo() deprecated
3. Singleton Hierarchy (NAVIGATION MAP)
concepts/singleton-hierarchy.md
4-level deep navigation showing how to reach every feature.
Key Learnings
Critical Discoveries:
-
getMediaStream() ONLY works after join()
- The stream object is not available until session is joined
- See: SDK Architecture Pattern
-
Use attachVideo() NOT renderVideo()
- renderVideo() is deprecated
- attachVideo() returns a VideoPlayer element to append to DOM
- See: Video Rendering
-
The SDK is Event-Driven
- You MUST listen for events to render participant videos
- key events: peer-video-state-change, user-added, user-removed
- See: Event Handling
-
Peer Videos on Mid-Session Join
- Existing participants' videos won't auto-render
- Must manually iterate getAllUser() and attachVideo()
- See: Video Rendering
-
CDN vs NPM
- CDN exports as
WebVideoSDK.default, notZoomVideo - Some networks/ad blockers may block
source.zoom.us- allowlist or use a permitted fallback strategy - See: Session Join Pattern
- CDN exports as
-
SharedArrayBuffer for HD
- Required for 720p/1080p video
- Need COOP/COEP headers on server
- Check with
stream.isSupportHDVideo()
-
Screen Share Element Type
- Check
isStartShareScreenWithVideoElement()for correct element type - See: Screen Share
- Check
-
Command Channel Setup Order
- Must call getCommandClient() AFTER client.join()
- Register listeners AFTER join, not before
- Web uses getCommandClient() not getCmdChannel()
- See: Command Channel
-
Command Channel is Session-Scoped
- Does NOT span across different sessions
- Both sender and receiver must be in the same session
Quick Reference
"getMediaStream() returns undefined"
→ Call AFTER join() completes
"Video not showing"
→ Video Rendering - Use attachVideo(), check events
"renderVideo() doesn't work"
→ Video Rendering - Use attachVideo() instead
"How do I implement [feature]?"
"How do I navigate to [client]?"
"What error code means what?"
Document Version
Based on Zoom Video SDK for Web v2.3.x
Happy coding!
Remember: The SDK Architecture Pattern is your key to unlocking the entire SDK. Read it first!
Operations
- RUNBOOK.md - 5-minute preflight and debugging checklist.
04 同仓库更多 Skills
ticket-deflector
Reads a forwarded customer email or ticket, pulls order and refund status from a payments connector (PayPal, Square, or Stripe) or Shopify, account history from the CRM, and open tickets from a support desk (Zoho Desk), drafts a tone-matched reply in the owner's writing voice, and can issue a refund through the payments connector with explicit owner approval. With Shopify connected it also runs a proactive order-triage mode that surfaces orders needing attention — unfulfilled past the promised window, payment problems, pending refunds, stuck shipments — and drafts the next action for each before the customer has to ask. Use when the user says "draft a response," "answer this customer," "where's my order," "I want a refund," "check my orders," or "anything about to blow up."
tax-season-organizer
Prepares tax-season materials for the owner's accountant, not tax advice. US federal tax; a non-US business gets its closed-books packet instead. Two modes: (1) quarterly estimated tax from YTD net income in the ledger (MYOB, NetSuite, QuickBooks, Xero, or Zoho Books); (2) year-end 1099 prep, scanning the ledger, PayPal, and Stripe for contractors paid over USD 600 into a 1099-NEC list with missing W-9 flags. Any tax request routes first to /tax-prep, which confirms the books are closed and reconciled before running this skill. Use this skill directly only when the owner says the period's books are already closed: "books are closed, now do the 1099s," "run the quarterly estimate off the closed numbers," or "just the contractor W-9 list."
tax-prep
Prepares tax materials as a two-link chain — month-end-prep confirms the books are closed and reconciled first, then tax-season-organizer calculates the quarterly estimated payment or builds the year-end 1099-NEC list and accountant packet from those closed numbers. Requires a ledger (MYOB, NetSuite, QuickBooks, Xero, or Zoho Books); uses Gusto, PayPal, and Stripe when connected, else CSV upload. US federal tax math; a non-US business gets the closed-books packet. Prep material for a CPA, never tax advice, nothing filed. Trigger on "quarterly taxes," "estimated tax payment," "how much should I set aside," "1099s," "1099-NEC," "W-9s," "year-end tax prep," "get my books ready for my accountant," any phrasing that suggests a tax deadline is coming, or a question about net profit or YTD income that sounds like worry about a tax bill. An owner who says the period's books are already closed routes to tax-season-organizer directly.
speed-to-lead
Makes sure no inbound lead goes unanswered. Watches web forms, the shared inbox, and the CRM for new inquiries, qualifies each one against the owner's criteria, drafts a reply in the owner's voice with real meeting times for the owner to approve and send, routes anything hot to a human right away, and logs the whole thing to the CRM. Never sends on its own. Built to run on a schedule so overnight inquiries are drafted and sorted by morning, and works from forwarded or pasted inquiries when no connector is available. Use this whenever the owner is worried about inbound leads or response time — including phrasings like "leads are going cold," "did anyone answer that inquiry," "I keep missing form submissions," "nothing falls through the cracks," "handle my inbound," "respond to this lead," or "we lose people because nobody gets back fast enough." Reach for it for a single unanswered inquiry as readily as for a standing setup.
social-content-engine
Runs the owner's content operation as a standing thing rather than a one-off: keeps a rolling posting calendar, learns the owner's voice, generates on-brand Canva graphics, writes captions that sound like them, repurposes one asset into every channel format, and stages posts for approval instead of publishing on its own. Pulls product photos and prices from Shopify when connected; with no connectors, drafts the calendar and asset briefs for manual posting. Use this whenever the owner talks about posting, content, or social — including "I need to post more," "we've gone quiet on Instagram," "what should we post this month," "make the content," "give me a month of posts," "write this in my voice," "repurpose this," or "set up our content calendar." Reach for it for a single post as readily as for a full quarter. One handoff: a finished, approved campaign brief goes to canva-creator; this skill owns the standing operation.
smb-router
The front door to the Small Business plugin. Listens to what the owner needs right now — vague or specific — and routes them to the best skill or slash command for the moment. Also serves as a guide: explains what's available, suggests what to try next, and adapts recommendations based on stored business context. Trigger whenever the owner asks "what can you do," "help me with my business," "what should I focus on," "I don't know where to start," or any open-ended business request that doesn't clearly match a single skill.