From simplero-skills
Import an online course from any platform (Kajabi, Skool, Teachable, WordPress/LearnDash, etc.) into Simplero. Handles video, audio, text, attachments, and resources.
npx claudepluginhub simplero/skills --plugin simplero-skillsThis skill uses the workspace's default tool permissions.
You import online courses from external platforms into Simplero. You handle the entire pipeline: scraping the source, downloading media, uploading assets, and creating the lesson structure.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Guides agent creation for Claude Code plugins with file templates, frontmatter specs (name, description, model), triggering examples, system prompts, and best practices.
You import online courses from external platforms into Simplero. You handle the entire pipeline: scraping the source, downloading media, uploading assets, and creating the lesson structure.
Ask the user for anything you don't already have:
X-API-Key (not X-Simplero-API-Key)GET /courses.json to list courses, or GET /courses/{id}.json to show modules in a specific course. Help them pick or create the right target.Simplero's course hierarchy:
Site
└── Course
└── Module (a section/grouping)
└── Lesson
├── title (string)
├── body (HTML — the lesson's text/copy content)
├── asset_id (a single video or audio file)
├── attachments[] (PDFs, downloads, links)
├── publish_status ("published" or "draft")
├── default_playback_speed (float, e.g. 1.0 or 1.5)
├── allow_multiple_completions (boolean)
└── remember_playback_position (boolean)
Mapping rules:
POST /courses/{course_id}/course_modules.json with {"title": "...", "publish_status": "published"})asset_idbodyattachments (download the actual file when possible, don't just link)Use Playwright (headless Chromium) to log in and scrape. Key patterns per platform:
headless=True, custom user-agent, remove webdriver flag?page=N pagination linksSkool:
/GROUP/classroomwindow.__NEXT_DATA__ for buildId and allCourses (in pageProps)metadata.title, not namechildren — each child has course.metadata with title, videoStream (BunnyCDN HLS), resources (JSON string of links)unitType: "set" are sub-modules — recurse into their childrenunitType: "module" are individual lessonsKajabi:
/login — fields are #member_email and #member_password. Submit button may be input[type="submit"] OR button#form-button — check the page./categories/{id}/posts/{id}wistia_async_{ID} in div classes/products/{slug}) may show only the first 10 lessons with a "Show More" link at the bottom. Click it.?page=2, ?page=3, etc.) that appear at the bottom. These are NOT visible until after Show More is clicked.?page=N on the course URL itself — some courses paginate at the top level (e.g., /products/{slug}?page=2)./categories/{id}), also check for ?page=N pagination..post-body, .kjb-post__body, .post__body first.panel__block inside .section__body — strip the h1.panel__title and h5.panel__sub-title, keep the remaining h2, p, and other content elementsp.syllabus__text descriptions (these may be truncated — always prefer the full content from the lesson page)WordPress/LearnDash:
/wp-login.php.entry-content or .learndash-contentTeachable:
/sign_in/courses/{slug}/curriculumLook for these in order on each lesson page:
wistia_async_{ID} classes on divs → Wistia<iframe src="...player.vimeo.com/video/{ID}..."> → Vimeo<iframe src="...youtube.com/embed/{ID}..."> → YouTubevideoStream in JSON data → BunnyCDN HLS<video> or <source> elements → direct MP4/HLS<iframe src="...soundcloud.com/..."> or soundcloud.com links → SoundCloud audio<audio> elements → direct audio filesEvery lesson's text/copy content MUST be scraped and imported. This is the lesson description, instructions, or written content that accompanies the video/audio.
.post-body, .kjb-post__body, .entry-content, .lesson-content, .panel__block (Kajabi — strip title elements)data-* attributes from extracted HTML for cleanlinessNot every lesson's primary content is a video. The main asset could be an image (infographic, map, diagram), a PDF (workbook, guide, checklist), an audio file (podcast, meditation, voice note), or something else entirely. Look for whatever the lesson is built around:
.player__video img, .post-hero img, or the main content area for large images. Use the CDN URL (e.g., kajabi-storefronts-production.kajabi-cdn.com/...) rather than /courses/downloads/ URLs which may redirect to HTML.asset_id.<audio> elements, SoundCloud embeds, or direct .mp3/.m4a links. These go in asset_id just like video.asset_id to null and put everything in the body.Upload whatever the primary content is as the lesson's asset_id — Simplero supports video, audio, images, and PDFs as lesson assets. If a lesson has both a video AND supplementary files (PDFs, images), the video goes in asset_id and the rest go as attachments.
Look for ALL downloadable files on each lesson page — PDFs, images, spreadsheets, docs, everything:
/courses/downloads/ or file extensions)https://drive.google.com/uc?export=download&id={FILE_ID}?dl=1 for direct downloadresources field)<a> tags pointing to downloadable filesFor Kajabi download URLs: The /courses/downloads/{id}/filename pattern may return an HTML page instead of the file. If the downloaded file is suspiciously small (<10 KB) or is HTML, fall back to the direct CDN URL from the img tag or use Playwright to follow the download redirect and capture the actual file URL.
Use yt-dlp for all video platforms — it handles auth, format selection, and audio merging correctly.
Wistia:
yt-dlp -f "hd_mp4-720p/best[height<=720]" -o output.mp4 "https://fast.wistia.net/embed/iframe/{WISTIA_ID}"
Wistia files are pre-muxed (video+audio in one file), so no merge needed.
Vimeo:
yt-dlp -f "bestvideo[ext=mp4][height<=720]+bestaudio" --merge-output-format mp4 -o output.mp4 "https://player.vimeo.com/video/{ID}?h={HASH}"
Include the ?h= hash parameter if present in the iframe src — it's required for private videos.
YouTube:
yt-dlp -f "bestvideo[ext=mp4][height<=720]+bestaudio" --merge-output-format mp4 -o output.mp4 "https://www.youtube.com/watch?v={ID}"
BunnyCDN HLS (Skool):
ffmpeg -y -headers "Referer: https://www.skool.com/\r\n" -i "{HLS_URL}" -c copy output.mp4
Use 1200s timeout. These streams are already muxed.
SoundCloud:
yt-dlp -f "bestaudio" -o output.mp3 "{SOUNDCLOUD_URL}"
Direct MP4/HLS:
yt-dlp -f "best[height<=720]" -o output.mp4 "{URL}"
# Or for raw HLS:
ffmpeg -y -i "{URL}" -c copy output.mp4
ffprobe -v quiet -show_streams -print_format json output.mp4
Check that there's a stream with codec_type: "audio". If missing, re-download with a different format selector that includes audio.
curl -sL "https://drive.google.com/uc?export=download&id={FILE_ID}" -o file.pdfcurl -sL "{URL}" -o file.extBase URL: https://simplero.com/api/v1
Auth header: X-API-Key: {key}
Upload an asset (video, audio, PDF):
curl -s -X POST -H "X-API-Key: {key}" \
-F "file=@video.mp4;type=video/mp4" \
https://simplero.com/api/v1/assets.json
Returns {"id": 12345, ...}. Use the ID as asset_id on a lesson (for video/audio) or to create an attachment.
Content types: video/mp4, audio/mpeg, application/pdf, application/vnd.openxmlformats-officedocument.*, etc.
Create a module:
POST /courses/{course_id}/course_modules.json
{"title": "Module Name", "publish_status": "published", "position": 1}
Create a lesson:
POST /course_modules/{module_id}/course_lessons.json
{
"title": "Lesson Title",
"body": "<p>HTML content here</p>",
"asset_id": 12345,
"position": 1,
"publish_status": "published",
"default_playback_speed": 1.0,
"allow_multiple_completions": true,
"remember_playback_position": false
}
The last three fields are optional — only set them for meditation/workout/repeat-listen content (see Playback Settings section below).
Update a lesson:
PUT /course_lessons/{lesson_id}.json
{"body": "...", "asset_id": 12345}
Note: PUT endpoint is flat (/course_lessons/{id}.json), NOT nested under modules.
Add a file attachment to a lesson:
POST /course_lessons/{lesson_id}/attachments.json
{"asset_id": 12345}
The asset must be uploaded first via /assets.json.
Add a link attachment to a lesson:
POST /course_lessons/{lesson_id}/attachments.json
{"title": "Resource Name", "link": "https://example.com/resource"}
Use this as fallback when file download isn't possible.
List courses:
GET /courses.json
Get course with modules and lessons:
GET /courses/{id}.json
Write a Python script that:
import_state.json) mapping "module:lesson" keys to Simplero lesson IDs or "error:..." stringsRun the script and monitor progress. For large imports (100+ lessons), run in background with nohup and check periodically.
ffprobe shows no audio stream, try a different format or download method.When the content is a meditation, guided visualization, workout, breathwork session, hypnosis, affirmation track, or similar content that people listen to repeatedly and should experience at normal speed:
Set these fields when creating the lesson:
{
"default_playback_speed": 1.0,
"allow_multiple_completions": true,
"remember_playback_position": false
}
default_playback_speed: 1.0 — prevents the player from defaulting to a faster speed. Meditations, workouts, and similar content should always start at 1x.allow_multiple_completions: true — lets users mark the lesson complete more than once, since these are designed to be repeated.remember_playback_position: false — the lesson starts from the beginning each time instead of resuming where they left off, since users want the full experience each listen.How to detect this type of content: Look at the lesson title and description for keywords like: meditation, guided, visualization, breathwork, workout, exercise, affirmation, hypnosis, tapping, EFT, yoga, relaxation, sleep, journaling prompt (audio), prayer. Also consider the context — if the entire course is a meditation series or wellness program, apply these settings to all lessons.