Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 25 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,38 +26,42 @@ npx skills add runwayml/skills

## Available Skills

<!-- GENERATED:SKILLS:START -->

### Getting Started

| Skill | Description |
| --------------------- | ---------------------------------------------------------------------------------------------- |
| `recipe-full-setup` | End-to-end setup: compatibility check API key → SDK install → integration code → test |
| `check-compatibility` | Analyze your project to verify it can safely call the Runway API server-side |
| `setup-api-key` | Guide through account creation, SDK installation, and environment variable configuration |
| `check-org-details` | Query your organization's rate limits, credit balance, usage tier, and daily generation counts |
| Skill | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `recipe-full-setup` | Complete Runway API setup: check compatibility, configure API key, and integrate generation endpoints |
| `check-compatibility` | Analyze a user's codebase to verify it can use Runway's public API (server-side requirement) |
| `setup-api-key` | Guide users through obtaining and configuring a Runway API key |
| `check-org-details` | Query the Runway API for organization details: rate limits, credit balance, usage tier, and daily generation counts |

### Generation

| Skill | Description |
| ----------------- | ----------------------------------------------------------------------------------- |
| `integrate-video` | Text-to-video, image-to-video, video-to-video, and character performance generation |
| `integrate-image` | Text-to-image generation with optional reference images via `@Tag` syntax |
| `integrate-audio` | Text-to-speech, sound effects, voice isolation, dubbing, and speech-to-speech |
| Skill | Description |
| ----------------- | ------------------------------------------------------------------------------------------------- |
| `integrate-video` | Help users integrate Runway video generation APIs (text-to-video, image-to-video, video-to-video) |
| `integrate-image` | Help users integrate Runway image generation APIs (text-to-image with reference images) |
| `integrate-audio` | Help users integrate Runway audio APIs (TTS, sound effects, voice isolation, dubbing) |

### Characters (Real-Time Avatars)

| Skill | Description |
| --------------------------- | ------------------------------------------------------------------------------------------ |
| `integrate-characters` | Create GWM-1 avatars and set up server-side session management for real-time conversations |
| `integrate-character-embed` | Embed avatar call UI in React apps using `@runwayml/avatars-react` |
| `integrate-documents` | Add knowledge base documents to avatars for domain-specific conversations |
| Skill | Description |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `integrate-characters` | Help users create Runway Characters (GWM-1 avatars) and integrate real-time conversational sessions into their apps |
| `integrate-character-embed` | Help users embed Runway Character avatar calls in React apps using the @runwayml/avatars-react SDK |
| `integrate-documents` | Help users add knowledge base documents to Runway Characters for domain-specific conversations |

### Utilities

| Skill | Description |
| --------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `integrate-uploads` | Upload local files to get `runway://` URIs for use as generation inputs |
| `api-reference` | Complete API reference — models, endpoints, costs, rate limits, and error codes |
| `fetch-api-reference` | Fetch the latest API docs from [docs.dev.runwayml.com/api](https://docs.dev.runwayml.com/api/) as the source of truth |
| Skill | Description |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `integrate-uploads` | Help users upload local files to Runway for use as inputs to generation models |
| `api-reference` | Complete reference for Runway's public API: models, endpoints, costs, limits, and types |
| `fetch-api-reference` | Retrieve the latest Runway API reference from docs.dev.runwayml.com and use it as the authoritative source before any integration work |

<!-- GENERATED:SKILLS:END -->

## Supported Models

Expand Down
159 changes: 159 additions & 0 deletions scripts/build-readme.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/usr/bin/env node

/**
* Generates the "Available Skills" section in README.md from skill frontmatter.
* Hand-written content outside the markers is preserved.
*
* Usage:
* node scripts/build-readme.mjs # write README.md
* node scripts/build-readme.mjs --check # exit 1 if section is stale
*/

import { readdirSync, readFileSync, writeFileSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";

const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const SKILLS_DIR = resolve(ROOT, "skills");
const README_PATH = resolve(ROOT, "README.md");

const START_MARKER = "<!-- GENERATED:SKILLS:START -->";
const END_MARKER = "<!-- GENERATED:SKILLS:END -->";

const CATEGORIES = [
{
title: "Getting Started",
skills: ["recipe-full-setup", "check-compatibility", "setup-api-key", "check-org-details"],
},
{
title: "Generation",
skills: ["integrate-video", "integrate-image", "integrate-audio"],
},
{
title: "Characters (Real-Time Avatars)",
skills: ["integrate-characters", "integrate-character-embed", "integrate-documents"],
},
{
title: "Utilities",
skills: ["integrate-uploads", "api-reference", "fetch-api-reference"],
},
];

function parseFrontmatter(content) {
const match = content.match(/^---\n([\s\S]*?)\n---/);
if (!match) return null;

const fields = {};
for (const line of match[1].split("\n")) {
const colonIdx = line.indexOf(":");
if (colonIdx === -1) continue;
const key = line.slice(0, colonIdx).trim();
let value = line.slice(colonIdx + 1).trim();
if (value.startsWith('"') && value.endsWith('"')) {
value = value.slice(1, -1);
}
fields[key] = value;
}
return fields;
}

function loadSkills() {
const skills = new Map();
const dirs = readdirSync(SKILLS_DIR, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name);

for (const dir of dirs) {
try {
const content = readFileSync(resolve(SKILLS_DIR, dir, "SKILL.md"), "utf-8");
const fm = parseFrontmatter(content);
if (fm) {
skills.set(dir, fm);
}
} catch {
// skip directories without SKILL.md
}
}
return skills;
}

function padColumn(rows, colIdx) {
return Math.max(...rows.map((row) => row[colIdx].length));
}

function buildTable(headers, rows) {
const widths = headers.map((_, i) => padColumn([headers, ...rows], i));
const formatRow = (row) =>
"| " + row.map((cell, i) => cell.padEnd(widths[i])).join(" | ") + " |";

return [
formatRow(headers),
"| " + widths.map((w) => "-".repeat(w)).join(" | ") + " |",
...rows.map(formatRow),
].join("\n");
}

function generateSection(skills) {
const categorized = new Set();
const sections = [];

for (const category of CATEGORIES) {
const rows = [];
for (const name of category.skills) {
const fm = skills.get(name);
if (!fm) continue;
rows.push([`\`${name}\``, fm.description]);
categorized.add(name);
}
if (rows.length > 0) {
sections.push(`### ${category.title}\n\n${buildTable(["Skill", "Description"], rows)}`);
}
}

const uncategorized = [...skills.entries()]
.filter(([name]) => !categorized.has(name))
.map(([name, fm]) => [`\`${name}\``, fm.description]);

if (uncategorized.length > 0) {
sections.push(`### Other\n\n${buildTable(["Skill", "Description"], uncategorized)}`);
}

return sections.join("\n\n");
}

const skills = loadSkills();
const generated = generateSection(skills);

const readme = readFileSync(README_PATH, "utf-8");

const startIdx = readme.indexOf(START_MARKER);
const endIdx = readme.indexOf(END_MARKER);

let newReadme;

if (startIdx !== -1 && endIdx !== -1) {
newReadme =
readme.slice(0, startIdx + START_MARKER.length) +
"\n\n" +
generated +
"\n\n" +
readme.slice(endIdx);
} else {
console.error(
`Markers not found in README.md. Add these markers around the skills section:\n ${START_MARKER}\n ${END_MARKER}`,
);
process.exit(1);
}

const isCheck = process.argv.includes("--check");

if (isCheck) {
if (readme !== newReadme) {
console.error("STALE: README.md skills section — run `node scripts/build-readme.mjs` to regenerate");
process.exit(1);
}
console.log("README.md skills section is up to date.");
} else {
writeFileSync(README_PATH, newReadme);
console.log("wrote README.md skills section");
}