-
Notifications
You must be signed in to change notification settings - Fork 2
Add docs manifest fetch #70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| --- | ||
| name: mux-cli | ||
| description: Use for all questions about Mux APIs, Mux SDKs, Mux CLI, webhooks, assets, uploads, playback, live streaming, Mux Data, Mux Robots, or Mux docs. The agent must use `mux docs search --json` and `mux docs read` before web search or answering from memory. | ||
| --- | ||
|
|
||
| # Mux CLI | ||
|
|
||
| Use the `mux` command for Mux API operations and documentation lookup. | ||
|
|
||
| ## Mandatory docs workflow | ||
|
|
||
| For any Mux API, SDK, CLI, webhook, playback, upload, asset, live streaming, Data, Robots, or docs question, use the local Mux docs commands before using web search or answering from memory. | ||
|
|
||
| Do not search the web first. | ||
|
|
||
| First run: | ||
|
|
||
| ```bash | ||
| mux docs search "<topic>" --json | ||
| ``` | ||
|
|
||
| Then read the most relevant doc: | ||
|
|
||
| ```bash | ||
| mux docs read "<doc-id>" --format markdown | ||
| ``` | ||
|
|
||
| Only use web search if: | ||
|
|
||
| 1. `mux docs search` fails, | ||
| 2. the docs index has no relevant result, or | ||
| 3. the user explicitly asks for external or web results. | ||
|
|
||
| ## Command resolution | ||
|
|
||
| If `mux` is available, use: | ||
|
|
||
| ```bash | ||
| mux docs search "<topic>" --json | ||
| mux docs read "<doc-id>" --format markdown | ||
| ``` | ||
|
|
||
| If working inside the Mux CLI repo and `mux` is not installed, use: | ||
|
|
||
| ```bash | ||
| pnpm dev docs search "<topic>" --json | ||
| pnpm dev docs read "<doc-id>" --format markdown | ||
| ``` | ||
|
|
||
| If the docs cache has not been populated, run: | ||
|
|
||
| ```bash | ||
| mux docs update | ||
| ``` | ||
|
|
||
| For local pre-deploy testing with generated artifacts, run: | ||
|
|
||
| ```bash | ||
| mux docs update --artifact-path ../mux.com/apps/web/public/.well-known/mux-docs | ||
| ``` | ||
|
|
||
| ## Docs sources | ||
|
|
||
| The CLI resolves docs in this order: | ||
|
|
||
| 1. cached published manifest/index populated by `mux docs update` | ||
| 2. `--source <path>` | ||
| 3. `MUX_DOCS_PATH` | ||
| 4. a sibling `../mux.com` checkout for local Mux development | ||
|
|
||
| The published docs index is downloaded from `docs.mux.com` and contains only `.mdx` files from `apps/web/app/docs/_guides`. | ||
|
|
||
| ## Example | ||
|
|
||
| User: "How do I verify Mux webhook signatures?" | ||
|
|
||
| Run: | ||
|
|
||
| ```bash | ||
| mux docs search "verify webhook signatures" --json | ||
| mux docs read verify-webhook-signatures --format markdown | ||
| ``` | ||
|
|
||
| Then answer using the retrieved docs. Cite the docs page by `doc.id` and `doc.url` when possible. | ||
|
|
||
| ## Agent usage guidelines | ||
|
|
||
| - Use `mux docs search --json` before answering Mux-specific questions. | ||
| - Use `mux docs read <doc-id>` before providing detailed API behavior or code examples. | ||
| - Cite docs by `doc.id` and `doc.url` when possible. | ||
| - Do not mutate Mux resources unless the user explicitly asks you to. | ||
| - For write/delete/update operations, explain the intended action and confirm if there is any ambiguity. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| import { describe, expect, test } from 'bun:test'; | ||
| import { docsCommand } from './index.ts'; | ||
| import { readCommand } from './read.ts'; | ||
| import { searchCommand } from './search.ts'; | ||
| import { sourceCommand } from './source.ts'; | ||
| import { updateCommand } from './update.ts'; | ||
|
|
||
| describe('mux docs command', () => { | ||
| test('has a docs-focused description', () => { | ||
| expect(docsCommand.getDescription()).toMatch(/Mux docs/i); | ||
| }); | ||
|
|
||
| test('registers docs subcommands', () => { | ||
| const commandNames = docsCommand | ||
| .getCommands() | ||
| .map((command) => command.getName()); | ||
|
|
||
| expect(commandNames).toContain('search'); | ||
| expect(commandNames).toContain('read'); | ||
| expect(commandNames).toContain('update'); | ||
| expect(commandNames).toContain('source'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('mux docs search command', () => { | ||
| test('describes docs search', () => { | ||
| expect(searchCommand.getDescription()).toMatch(/search.*docs/i); | ||
| }); | ||
|
|
||
| test('accepts a query argument', () => { | ||
| const args = searchCommand.getArguments(); | ||
|
|
||
| expect(args.length).toBeGreaterThanOrEqual(1); | ||
| expect(args[0]?.name).toMatch(/query/i); | ||
| }); | ||
|
|
||
| test('has agent-friendly output and source options', () => { | ||
| const optionNames = searchCommand.getOptions().map((option) => option.name); | ||
|
|
||
| expect(optionNames).toContain('json'); | ||
| expect(optionNames).toContain('limit'); | ||
| expect(optionNames).toContain('source'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('mux docs read command', () => { | ||
| test('describes reading a docs page', () => { | ||
| expect(readCommand.getDescription()).toMatch(/read.*docs/i); | ||
| }); | ||
|
|
||
| test('accepts a doc id argument', () => { | ||
| const args = readCommand.getArguments(); | ||
|
|
||
| expect(args.length).toBe(1); | ||
| expect(args[0]?.name).toMatch(/doc.*id|id/i); | ||
| }); | ||
|
|
||
| test('has markdown and JSON output options', () => { | ||
| const optionNames = readCommand.getOptions().map((option) => option.name); | ||
|
|
||
| expect(optionNames).toContain('format'); | ||
| expect(optionNames).toContain('json'); | ||
| expect(optionNames).toContain('source'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('mux docs update command', () => { | ||
| test('describes refreshing the docs cache', () => { | ||
| expect(updateCommand.getDescription()).toMatch(/update|refresh/i); | ||
| }); | ||
|
|
||
| test('has source, force, and JSON options', () => { | ||
| const optionNames = updateCommand.getOptions().map((option) => option.name); | ||
|
|
||
| expect(optionNames).toContain('source'); | ||
| expect(optionNames).toContain('force'); | ||
| expect(optionNames).toContain('json'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('mux docs source command', () => { | ||
| test('describes showing the active docs source', () => { | ||
| expect(sourceCommand.getDescription()).toMatch(/source/i); | ||
| }); | ||
|
|
||
| test('has a JSON option', () => { | ||
| const optionNames = sourceCommand.getOptions().map((option) => option.name); | ||
|
|
||
| expect(optionNames).toContain('json'); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { Command } from '@cliffy/command'; | ||
| import { readCommand } from './read.ts'; | ||
| import { searchCommand } from './search.ts'; | ||
| import { sourceCommand } from './source.ts'; | ||
| import { updateCommand } from './update.ts'; | ||
|
|
||
| export const docsCommand = new Command() | ||
| .description('Search, read, and refresh Mux docs') | ||
| .action(function () { | ||
| this.showHelp(); | ||
| }) | ||
| .command('search', searchCommand) | ||
| .command('read', readCommand) | ||
| .command('update', updateCommand) | ||
| .command('source', sourceCommand); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import { Command } from '@cliffy/command'; | ||
| import { | ||
| findDocById, | ||
| formatDocContent, | ||
| loadDocsIndex, | ||
| readDocContent, | ||
| } from '@/lib/docs.ts'; | ||
|
|
||
| interface ReadOptions { | ||
| format?: 'markdown' | 'raw'; | ||
| json?: boolean; | ||
| source?: string; | ||
| } | ||
|
|
||
| export const readCommand = new Command() | ||
| .description('Read a Mux docs page') | ||
| .arguments('<doc-id:string>') | ||
| .option('--format <format:string>', 'Output format: markdown or raw', { | ||
| default: 'markdown', | ||
| value: (value: string): 'markdown' | 'raw' => { | ||
| if (value !== 'markdown' && value !== 'raw') { | ||
| throw new Error('Invalid format. Must be "markdown" or "raw".'); | ||
| } | ||
| return value; | ||
| }, | ||
| }) | ||
| .option('--json', 'Output JSON instead of pretty format') | ||
| .option('--source <path:string>', 'Path to a local mux.com docs repository') | ||
| .action(async (options: ReadOptions, docId: string) => { | ||
| try { | ||
| const { index, source } = await loadDocsIndex({ | ||
| explicitPath: options.source, | ||
| }); | ||
| const doc = findDocById(index, docId); | ||
|
|
||
| if (!doc) { | ||
| throw new Error(`Docs page not found: ${docId}`); | ||
| } | ||
|
|
||
| const content = formatDocContent( | ||
| await readDocContent(doc), | ||
| options.format, | ||
| ); | ||
|
|
||
| if (options.json) { | ||
| console.log( | ||
| JSON.stringify( | ||
| { | ||
| source, | ||
| doc, | ||
| content, | ||
| }, | ||
| null, | ||
| 2, | ||
| ), | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
| console.log(content); | ||
| } catch (error) { | ||
| printDocsError(error, options); | ||
| } | ||
| }); | ||
|
|
||
| function printDocsError(error: unknown, options: ReadOptions): never { | ||
| const message = error instanceof Error ? error.message : String(error); | ||
|
|
||
| if (options.json) { | ||
| console.error(JSON.stringify({ error: message }, null, 2)); | ||
| } else { | ||
| console.error(`Error: ${message}`); | ||
| } | ||
|
|
||
| process.exit(1); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Identical error handler duplicated across command filesLow Severity
Additional Locations (1)Reviewed by Cursor Bugbot for commit bc0d437. Configure here. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| import { Command } from '@cliffy/command'; | ||
| import { loadDocsIndex, searchDocsIndex } from '@/lib/docs.ts'; | ||
|
|
||
| interface SearchOptions { | ||
| json?: boolean; | ||
| limit?: number; | ||
| source?: string; | ||
| } | ||
|
|
||
| export const searchCommand = new Command() | ||
| .description('Search Mux docs') | ||
| .arguments('<query:string>') | ||
| .option('--json', 'Output JSON instead of pretty format') | ||
| .option('--limit <number:number>', 'Number of results to return', { | ||
| default: 10, | ||
| }) | ||
| .option('--source <path:string>', 'Path to a local mux.com docs repository') | ||
| .action(async (options: SearchOptions, query: string) => { | ||
| try { | ||
| const { index, source } = await loadDocsIndex({ | ||
| explicitPath: options.source, | ||
| }); | ||
| const results = searchDocsIndex(index, query, { limit: options.limit }); | ||
|
|
||
| if (options.json) { | ||
| console.log( | ||
| JSON.stringify( | ||
| { | ||
| query, | ||
| source, | ||
| results: results.map((result) => ({ | ||
| score: result.score, | ||
| snippet: result.snippet, | ||
| doc: { | ||
| id: result.entry.id, | ||
| title: result.entry.title, | ||
| description: result.entry.description, | ||
| product: result.entry.product, | ||
| route: result.entry.route, | ||
| url: result.entry.url, | ||
| relativePath: result.entry.relativePath, | ||
| headings: result.entry.headings, | ||
| }, | ||
| })), | ||
| }, | ||
| null, | ||
| 2, | ||
| ), | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
| if (results.length === 0) { | ||
| console.log('No docs found.'); | ||
| return; | ||
| } | ||
|
|
||
| for (const result of results) { | ||
| console.log(`${result.entry.title} (${result.entry.id})`); | ||
| console.log(` ${result.entry.url}`); | ||
| if (result.entry.description) { | ||
| console.log(` ${result.entry.description}`); | ||
| } else { | ||
| console.log(` ${result.snippet}`); | ||
| } | ||
| console.log(''); | ||
| } | ||
| } catch (error) { | ||
| printDocsError(error, options); | ||
| } | ||
| }); | ||
|
|
||
| function printDocsError(error: unknown, options: SearchOptions): never { | ||
| const message = error instanceof Error ? error.message : String(error); | ||
|
|
||
| if (options.json) { | ||
| console.error(JSON.stringify({ error: message }, null, 2)); | ||
| } else { | ||
| console.error(`Error: ${message}`); | ||
| } | ||
|
|
||
| process.exit(1); | ||
| } |


Uh oh!
There was an error while loading. Please reload this page.