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
72 changes: 72 additions & 0 deletions .github/workflows/sync-skills.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Sync agent skills

# Keeps skill/ in sync with the agent skills published in muxinc/skills.
# Triggered by a repository_dispatch from that repo on skill changes, with a
# weekly cron as a backstop and workflow_dispatch for manual runs. Opens a PR
# only when the synced content actually changed.
#
# EMBEDDED_SKILLS is an allowlist: muxinc/skills also publishes skills with
# vendored reference docs (skills/mux), which the CLI intentionally does not
# embed — the CLI ships routing skills only, never docs content.

on:
repository_dispatch:
types: [skills-updated]
workflow_dispatch:
schedule:
- cron: '17 6 * * 1'

permissions:
contents: write
pull-requests: write

env:
EMBEDDED_SKILLS: mux-docs

jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Check out muxinc/skills
uses: actions/checkout@v4
with:
repository: muxinc/skills
path: .skills-src

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shallow clone empties skills SHA

High Severity

actions/checkout defaults to fetch-depth: 1, but the sync step runs git log with a pathspec to find the last commit that touched the allowlisted skills. In a shallow clone that returns empty whenever HEAD did not touch those paths, so skill/manifest.json gets "commit": "" and the embedded-skills test fails. Weekly cron and manual dispatch are especially exposed.

Suggested change
path: .skills-src
- name: Check out muxinc/skills
uses: actions/checkout@v4
with:
repository: muxinc/skills
path: .skills-src
fetch-depth: 0
Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 355edb4. Configure here.


- uses: oven-sh/setup-bun@v2

- name: Copy skills into skill/
run: |
set -euo pipefail
rm -rf skill
mkdir -p skill
for name in $EMBEDDED_SKILLS; do
test -f ".skills-src/skills/$name/SKILL.md"
cp -R ".skills-src/skills/$name" "skill/$name"
done
SKILLS_SHA=$(git -C .skills-src log -1 --format=%H -- $(printf "skills/%s " $EMBEDDED_SKILLS))
printf '{\n "source": "muxinc/skills",\n "commit": "%s"\n}\n' "$SKILLS_SHA" > skill/manifest.json
rm -rf .skills-src

- name: Regenerate embedded skills module
run: bun scripts/generate-embedded-skills.ts

- name: Verify embedded module matches skill/
run: bun test src/lib/embedded-skills.test.ts

- name: Open pull request
uses: peter-evans/create-pull-request@v6
with:
commit-message: 'chore: sync agent skills from muxinc/skills'
title: 'Sync agent skills from muxinc/skills'
body: |
Automated sync of the agent skills embedded in the CLI from
[muxinc/skills](https://github.com/muxinc/skills). The source
commit is recorded in `skill/manifest.json`.

The next tagged release will embed these files in the compiled
binaries via `src/lib/embedded-skills.gen.ts`.
branch: chore/sync-agent-skills
delete-branch: true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sync PRs skip CI

Medium Severity

peter-evans/create-pull-request runs with the default GITHUB_TOKEN, so the opened sync PR does not trigger other workflows. Required CI checks on pull_request stay pending and block merge, leaving the automated skills sync unable to land without a manual workaround.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a2819b9. Configure here.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
"check:write": "pnpm exec biome check --write",
"typecheck": "bun --bun tsc --noEmit",
"test": "bun test",
"test:watch": "bun test --watch"
"test:watch": "bun test --watch",
"generate:skills": "bun scripts/generate-embedded-skills.ts"
},
"dependencies": {
"@cliffy/ansi": "jsr:1.0.0-rc.8",
Expand Down
53 changes: 53 additions & 0 deletions scripts/generate-embedded-skills.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env bun
/**
* Generates src/lib/embedded-skills.gen.ts from the contents of skill/.
*
* The skill/ directory is synced from muxinc/skills by the sync-skills
* workflow. Embedding the files as string constants means every build target
* (bun run, dist bundle, compiled binary) ships the skills with no runtime
* filesystem or network dependency.
*
* Run with: pnpm run generate:skills
*/
import { readdir } from 'node:fs/promises';
import { join } from 'node:path';

const repoRoot = join(import.meta.dir, '..');
const skillDir = join(repoRoot, 'skill');
const outPath = join(repoRoot, 'src', 'lib', 'embedded-skills.gen.ts');

const entries = await readdir(skillDir, {
recursive: true,
withFileTypes: true,
});

const files: Record<string, string> = {};
for (const entry of entries) {
if (!entry.isFile()) continue;
const absolute = join(entry.parentPath, entry.name);
const relative = absolute.slice(skillDir.length + 1);
files[relative] = await Bun.file(absolute).text();
}

const sortedPaths = Object.keys(files).sort();
if (sortedPaths.length === 0) {
console.error(
'No files found in skill/ — refusing to generate an empty module.',
);
process.exit(1);
}

const lines = [
'// Generated by scripts/generate-embedded-skills.ts from skill/ — do not edit.',
'// Regenerate with: pnpm run generate:skills',
'',
'export const EMBEDDED_SKILLS: Record<string, string> = {',
...sortedPaths.map(
(path) => ` ${JSON.stringify(path)}: ${JSON.stringify(files[path])},`,
),
'};',
'',
];

await Bun.write(outPath, lines.join('\n'));
console.log(`Embedded ${sortedPaths.length} files into ${outPath}`);
4 changes: 4 additions & 0 deletions skill/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"source": "muxinc/skills",
"commit": "99ff3093cf76a57a6745d04f2e45622175034411"
}
76 changes: 76 additions & 0 deletions skill/mux-docs/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
name: mux-docs
description: Use for any question about Mux APIs, SDKs, the mux CLI, webhooks, assets, uploads, playback, live streaming, Mux Data, or Mux Robots. Finds the current Mux documentation — Mux publishes every docs page as LLM-ready markdown indexed at mux.com/llms.txt — so answers come from today's published docs instead of web search or model memory.
---

# Mux docs discovery

Mux publishes its entire documentation as agent-ready markdown, auto-generated from the same source as the real docs site. For any Mux question, fetch the relevant page and answer from it — never answer Mux API questions from memory, because API shapes and guidance change.

## Workflow

1. **Route — try these in order and stop at the first hit.** The order is cost-ordered: each step reads fewer tokens than the next.
1. If the `mux` CLI is installed, run `mux docs find "<topic>" --agent` — it searches the docs index CLI-side and returns only the matching page URLs, so you never read an index into context. (Check `mux docs --help`; older versions lack it.)
2. Construct the URL from a known pattern: the tables below (collections, Robots `robots-<task>.md`, API references) cover most topics.
3. Fetch the smallest collection index for the topic area (a few dozen lines).
4. Fetch https://www.mux.com/llms.txt — the master index of every docs page — only when the steps above fail.
2. **Fetch ONE page** — the page's markdown version: append `.md` to its URL (e.g. `https://www.mux.com/docs/guides/start-live-streaming.md`). Only add an API reference bundle when you need exact request/response shapes; do not fetch extra pages "for context."
3. **Answer from the fetched content** and cite the page URL.

**Never fetch https://www.mux.com/llms-full.txt.** It is the entire documentation in a single file and will flood the context window; every page in it is reachable individually through the steps above.

## Collection indexes

| Collection | URL |
| --- | --- |
| Core concepts | https://www.mux.com/docs/core.txt |
| Video: upload, encode, manage assets | https://www.mux.com/docs/guides/video.txt |
| Uploader: handle user uploads | https://www.mux.com/docs/guides/uploader.txt |
| Player: embed video playback | https://www.mux.com/docs/guides/player.txt |
| Data: playback quality and analytics | https://www.mux.com/docs/guides/data.txt |
| Data integrations: third-party player monitoring | https://www.mux.com/docs/guides/data-integrations.txt |
| Framework integrations and SDKs | https://www.mux.com/docs/integrations.txt |
| Example implementations | https://www.mux.com/docs/examples.txt |
| Pricing and billing | https://www.mux.com/docs/pricing.txt |


## Mux Robots (AI video workflows)

Robots — Mux's AI-powered video workflows — don't have their own collection index; their guides are part of the video collection and the master llms.txt index. Start here:

| Resource | URL |
| --- | --- |
| Robots overview | https://www.mux.com/docs/guides/robots.md |
| Robots directives reference | https://www.mux.com/docs/guides/robots-directives.md |

Task-specific guides follow the pattern `https://www.mux.com/docs/guides/robots-<task>.md`, where `<task>` is one of: `summarize`, `moderate`, `generate-chapters`, `ask-questions`, `find-key-moments`, `find-scenes`, `find-best-thumbnails`, `translate-captions`, `translate-audio`, `generate-premium-captions`, `edit-captions`, `generate-engagement-insights`.

## API references and specs

For exact endpoint, parameter, and webhook shapes, prefer these over prose guides:

| Resource | URL |
| --- | --- |
| Video API reference | https://www.mux.com/docs/api-reference/video.txt |
| Data API reference | https://www.mux.com/docs/api-reference/data.txt |
| System API reference | https://www.mux.com/docs/api-reference/system.txt |
| Full API spec (OpenAPI JSON) | https://www.mux.com/api-spec.json |
| Webhook spec (JSON) | https://www.mux.com/webhook-spec.json |
| Mux Player web component API | https://raw.githubusercontent.com/muxinc/elements/refs/heads/main/packages/mux-player/REFERENCE.md |
| Mux Player React component API | https://raw.githubusercontent.com/muxinc/elements/refs/heads/main/packages/mux-player-react/REFERENCE.md |

All of these resources are also discoverable from the docs site itself: every docs page links to "Docs for LLMs" (https://www.mux.com/docs/core/llms-txt), the canonical page describing Mux's llms.txt files and machine-readable bundles. If this skill's URL lists ever drift from reality, that page is the source of truth.

## Self-healing

If a docs URL 404s, the docs have likely been reorganized. Do not give up after one 404: run `mux docs find "<topic>" --agent` (or fetch https://www.mux.com/llms.txt and search it) to get the page's current URL.

## Staying in sync

- This skill is distributed from the `muxinc/skills` repository and ships embedded in the `mux` CLI. Updating the CLI (`brew upgrade mux` / `npm update -g @mux/cli`) updates the embedded skills, and `mux skills update` refreshes all local copies (including `~/.claude/skills`) to match the installed version. For skills decoupled from CLI releases, install the `mux@mux` Claude Code plugin or pull directly from https://github.com/muxinc/skills.
- The CLI embeds only skill instruction files, never docs content — always fetch docs live using the workflow above. Check `mux skills --help` for what the installed version offers.

## Guardrails

- Cite the docs page URL you answered from.
- If the network is unavailable, say you could not verify against current docs — never guess API request/response shapes from memory.
50 changes: 50 additions & 0 deletions src/commands/docs/find.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Command } from '@cliffy/command';
import { handleCommandError } from '@/lib/errors.ts';
import {
fetchDocsIndex,
parseDocsIndex,
searchDocsIndex,
} from '../../lib/docs-index.ts';

interface DocsFindOptions {
json?: boolean;
limit: number;
}

export const docsFindCommand = new Command()
.description(
'Search the live Mux docs index (mux.com/llms.txt) and print matching page URLs.\n\nSearches CLI-side so agents get page URLs without reading the index into context. Fetch the returned URL for the page content; no docs are stored locally.',
)
.arguments('<query...:string>')
.option('--limit <n:number>', 'Maximum number of results', { default: 5 })
.option('--json', 'Output JSON instead of pretty format')
.action(async (options: DocsFindOptions, ...query: string[]) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variadic query args mishandled

High Severity

Cliffy passes a named variadic &lt;query...&gt; as one string[] argument. Capturing it with a rest parameter nests that array, so query.join(' ') turns mux docs find live streaming into the single term live,streaming and search returns no matches. The same pattern in assets create uses .flat() for this reason.

Suggested change
.action(async (options: DocsFindOptions, ...query: string[]) => {
.action(async (options: DocsFindOptions, query: string[]) => {
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 355edb4. Configure here.

try {
const text = await fetchDocsIndex();
const entries = parseDocsIndex(text);
const results = searchDocsIndex(entries, query.join(' '), options.limit);

if (options.json) {
console.log(
JSON.stringify({ query: query.join(' '), results }, null, 2),
);
return;
}

if (results.length === 0) {
console.log(
'No matching docs pages. Try different terms, or browse https://www.mux.com/llms.txt',
);
return;
}

for (const result of results) {
console.log(result.url);
if (result.description) {
console.log(` ${result.description}`);
}
}
} catch (error) {
await handleCommandError(error, 'docs', 'find', options);
}
});
12 changes: 12 additions & 0 deletions src/commands/docs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Command } from '@cliffy/command';
import { docsFindCommand } from './find.ts';

// biome-ignore lint/suspicious/noExplicitAny: Cliffy's chained types are too complex for TS to infer
export const docsCommand: Command<any> = new Command()
.description(
'Search the live Mux documentation index.\n\nNo docs are stored locally — `find` searches mux.com/llms.txt and prints current page URLs.',
)
.action(function () {
this.showHelp();
})
.command('find', docsFindCommand);
16 changes: 16 additions & 0 deletions src/commands/skills/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Command } from '@cliffy/command';
import { skillsInstallCommand } from './install.ts';
import { skillsPathCommand } from './path.ts';
import { skillsUpdateCommand } from './update.ts';

// biome-ignore lint/suspicious/noExplicitAny: Cliffy's chained types are too complex for TS to infer
export const skillsCommand: Command<any> = new Command()
.description(
'Locate or install the agent skills embedded in this CLI build.\n\nFor agents and other tooling, start with `mux skills path --json`.',
)
.action(function () {
this.showHelp();
})
.command('path', skillsPathCommand)
.command('install', skillsInstallCommand)
.command('update', skillsUpdateCommand);
59 changes: 59 additions & 0 deletions src/commands/skills/install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { join } from 'node:path';
import { Command } from '@cliffy/command';
import { handleCommandError } from '@/lib/errors.ts';
import {
getDefaultAgentSkillsDir,
getSkillsManifest,
installSkills,
listSkills,
} from '../../lib/embedded-skills.ts';

interface DocsInstallOptions {
dir: string;
json?: boolean;
}

export const skillsInstallCommand = new Command()
.description(
'Install the embedded agent skills into an agent skills directory.\n\nDefaults to ~/.claude/skills, which Claude Code loads automatically — no CLAUDE.md or AGENTS.md changes needed. Run `mux skills update` after upgrading the CLI to refresh the installed copy.',
)
.option('--dir <path:string>', 'Target skills directory', {
default: getDefaultAgentSkillsDir(),
})
.option('--json', 'Output JSON instead of pretty format')
.action(async (options: DocsInstallOptions) => {
try {
const { dir } = await installSkills(options.dir);
const manifest = getSkillsManifest();
const skills = listSkills().map((skill) => ({
name: skill.name,
path: join(dir, skill.path),
}));

if (options.json) {
console.log(
JSON.stringify(
{ skills_dir: dir, source: manifest, skills },
null,
2,
),
);
return;
}

console.log(`Installed ${skills.length} agent skills to ${dir}`);
console.log(
`Synced from: ${manifest.source}@${manifest.commit.slice(0, 7)}`,
);
console.log('');
for (const skill of skills) {
console.log(` ${skill.name}`);
}
console.log('');
console.log(
'Claude Code loads these automatically in new sessions. Run `mux skills update` after upgrading the CLI to refresh this copy.',
);
} catch (error) {
await handleCommandError(error, 'skills', 'install', options);
}
});
Loading
Loading