Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Integration with hosted documentation platforms GitBook and Notion. Manage spaces, synchronize content with Git, export/import between formats, configure webhooks, and retrieve analytics.
.claude/skills/a5c-ai-gitbook-notion/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | 214% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 86% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 122% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 124% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 254% | 0% |
Integration with hosted documentation platforms.
Invoke this skill when you need to:
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | platform | string | Yes | gitbook, notion | | action | string | Yes | sync, export, import, analytics | | spaceId | string | No | GitBook space or Notion database ID | | sourcePath | string | No | Path to source content | | outputPath | string | No | Path for exported content |
json{ "platform": "gitbook", "action": "sync", "spaceId": "abc123", "sourcePath": "./docs" }
yaml# .gitbook.yaml root: ./docs structure: readme: README.md summary: SUMMARY.md redirects: /old-page: /new-page /api/v1: /api/v2
markdown# Summary ## Getting Started * [Introduction](README.md) * [Installation](getting-started/installation.md) * [Quick Start](getting-started/quickstart.md) ## User Guide * [Configuration](user-guide/configuration.md) * [Features](user-guide/features.md) * [Authentication](user-guide/features/auth.md) * [Data Management](user-guide/features/data.md) ## API Reference * [Overview](api/README.md) * [Authentication](api/authentication.md) * [Endpoints](api/endpoints/README.md) * [Users](api/endpoints/users.md) * [Projects](api/endpoints/projects.md) ## Resources * [FAQ](resources/faq.md) * [Changelog](CHANGELOG.md)
javascriptconst GitBook = require('gitbook-api'); class GitBookManager { constructor(token) { this.client = new GitBook({ token }); } // List spaces async listSpaces(organizationId) { return await this.client.spaces.list({ organizationId }); } // Get space content async getContent(spaceId) { const pages = await this.client.spaces.listPages(spaceId); return pages; } // Update page async updatePage(spaceId, pageId, content) { return await this.client.pages.update(spaceId, pageId, { document: { markdown: content } }); } // Create page async createPage(spaceId, title, content, parentId = null) { return await this.client.pages.create(spaceId, { title, parent: parentId, document: { markdown: content } }); } // Sync from Git async syncFromGit(spaceId, repoUrl, branch = 'main') { return await this.client.spaces.sync(spaceId, { source: 'github', url: repoUrl, branch }); } // Get analytics async getAnalytics(spaceId, period = '30d') { return await this.client.spaces.getAnalytics(spaceId, { period }); } }
yaml# .github/workflows/gitbook-sync.yml name: Sync to GitBook on: push: branches: [main] paths: - 'docs/**' jobs: sync: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Sync to GitBook uses: gitbook/github-action-sync@v1 with: token: ${{ secrets.GITBOOK_TOKEN }} space: ${{ secrets.GITBOOK_SPACE_ID }}
javascriptconst notionSchema = { database_id: 'abc123', properties: { 'Title': { type: 'title', title: {} }, 'Slug': { type: 'rich_text', rich_text: {} }, 'Category': { type: 'select', select: { options: [ { name: 'Guide', color: 'blue' }, { name: 'Reference', color: 'green' }, { name: 'Tutorial', color: 'purple' } ] } }, 'Status': { type: 'status', status: { options: [ { name: 'Draft', color: 'gray' }, { name: 'Review', color: 'yellow' }, { name: 'Published', color: 'green' } ] } }, 'Last Updated': { type: 'last_edited_time', last_edited_time: {} }, 'Author': { type: 'people', people: {} }, 'Tags': { type: 'multi_select', multi_select: { options: [] } } } };
javascriptconst { Client } = require('@notionhq/client'); class NotionDocsManager { constructor(token) { this.notion = new Client({ auth: token }); } // Query documentation pages async queryDocs(databaseId, filter = {}) { const response = await this.notion.databases.query({ database_id: databaseId, filter: { and: [ { property: 'Status', status: { equals: 'Published' } }, ...Object.entries(filter).map(([prop, value]) => ({ property: prop, [typeof value === 'string' ? 'rich_text' : 'select']: { equals: value } })) ] }, sorts: [ { property: 'Last Updated', direction: 'descending' } ] }); return response.results; } // Get page content async getPageContent(pageId) { const blocks = await this.notion.blocks.children.list({ block_id: pageId }); return this.blocksToMarkdown(blocks.results); } // Create documentation page async createDocPage(databaseId, title, content, properties = {}) { const blocks = this.markdownToBlocks(content); return await this.notion.pages.create({ parent: { database_id: databaseId }, properties: { 'Title': { title: [{ text: { content: title } }] }, 'Slug': { rich_text: [{ text: { content: this.slugify(title) } }] }, 'Status': { status: { name: 'Draft' } }, ...properties }, children: blocks }); } // Update page async updatePage(pageId, content) { // Clear existing blocks const existing = await this.notion.blocks.children.list({ block_id: pageId }); for (const block of existing.results) { await this.notion.blocks.delete({ block_id: block.id }); } // Add new blocks const blocks = this.markdownToBlocks(content); await this.notion.blocks.children.append({ block_id: pageId, children: blocks }); } // Convert Notion blocks to Markdown blocksToMarkdown(blocks) { let markdown = ''; for (const block of blocks) { switch (block.type) { case 'paragraph': markdown += this.richTextToMarkdown(block.paragraph.rich_text) + '\n\n'; break; case 'heading_1': markdown += '# ' + this.richTextToMarkdown(block.heading_1.rich_text) + '\n\n'; break; case 'heading_2': markdown += '## ' + this.richTextToMarkdown(block.heading_2.rich_text) + '\n\n'; break; case 'heading_3': markdown += '### ' + this.richTextToMarkdown(block.heading_3.rich_text) + '\n\n'; break; case 'bulleted_list_item': markdown += '- ' + this.richTextToMarkdown(block.bulleted_list_item.rich_text) + '\n'; break; case 'numbered_list_item': markdown += '1. ' + this.richTextToMarkdown(block.numbered_list_item.rich_text) + '\n'; break; case 'code': markdown += '```' + block.code.language + '\n'; markdown += this.richTextToMarkdown(block.code.rich_text); markdown += '\n```\n\n'; break; case 'quote': markdown += '> ' + this.richTextToMarkdown(block.quote.rich_text) + '\n\n'; break; case 'callout': markdown += '> **' + block.callout.icon?.emoji + '** '; markdown += this.richTextToMarkdown(block.callout.rich_text) + '\n\n'; break; } } return markdown; } // Convert Markdown to Notion blocks markdownToBlocks(markdown) { const blocks = []; const lines = markdown.split('\n'); let i = 0; while (i < lines.length) { const line = lines[i]; if (line.startsWith('# ')) { blocks.push({ type: 'heading_1', heading_1: { rich_text: [{ text: { content: line.slice(2) } }] } }); } else if (line.startsWith('## ')) { blocks.push({ type: 'heading_2', heading_2: { rich_text: [{ text: { content: line.slice(3) } }] } }); } else if (line.startsWith('### ')) { blocks.push({ type: 'heading_3', heading_3: { rich_text: [{ text: { content: line.slice(4) } }] } }); } else if (line.startsWith('```')) { const lang = line.slice(3); let code = ''; i++; while (i < lines.length && !lines[i].startsWith('```')) { code += lines[i] + '\n'; i++; } blocks.push({ type: 'code', code: { language: lang || 'plain text', rich_text: [{ text: { content: code.trim() } }] } }); } else if (line.startsWith('- ')) { blocks.push({ type: 'bulleted_list_item', bulleted_list_item: { rich_text: [{ text: { content: line.slice(2) } }] } }); } else if (/^\d+\. /.test(line)) { blocks.push({ type: 'numbered_list_item', numbered_list_item: { rich_text: [{ text: { content: line.replace(/^\d+\. /, '') } }] } }); } else if (line.trim()) { blocks.push({ type: 'paragraph', paragraph: { rich_text: [{ text: { content: line } }] } }); } i++; } return blocks; } }
javascriptasync function exportNotionToMarkdown(databaseId, outputDir) { const manager = new NotionDocsManager(process.env.NOTION_TOKEN); const pages = await manager.queryDocs(databaseId); for (const page of pages) { const title = page.properties.Title.title[0].plain_text; const slug = page.properties.Slug.rich_text[0]?.plain_text || slugify(title); const category = page.properties.Category.select?.name || 'uncategorized'; const content = await manager.getPageContent(page.id); const frontMatter = `--- title: ${title} notion_id: ${page.id} last_updated: ${page.last_edited_time} --- `; const filePath = path.join(outputDir, category, `${slug}.md`); await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, frontMatter + content); } }
javascriptasync function getGitBookAnalytics(spaceId) { const analytics = await gitbook.getAnalytics(spaceId, '30d'); return { pageViews: analytics.pageViews, uniqueVisitors: analytics.uniqueVisitors, topPages: analytics.topPages.map(p => ({ path: p.path, views: p.views })), searchQueries: analytics.searches.map(s => ({ query: s.query, count: s.count, noResults: s.noResults })) }; }
json{ "devDependencies": { "@notionhq/client": "^2.2.0", "gitbook-api": "^0.8.0", "gray-matter": "^4.0.0" } }
bash# Export from Notion node scripts/notion-export.js --database abc123 --output ./docs # Sync to GitBook gitbook sync ./docs --space abc123 # Import to Notion node scripts/notion-import.js --input ./docs --database abc123
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-11 | fail→pass | 12,819 | 2,699 | -79% | 1 | 1 | 0% | 1,345 | 4,219 | +214% | 0 | 0 | — |
case-12 | pass→pass | 17,222 | 7,941 | -54% | 1 | 1 | 0% | 2,190 | 4,135 | +89% | 0 | 0 | — |
case-01 | pass→pass | 14,713 | 8,233 | -44% | 1 | 1 | 0% | 1,541 | 4,313 | +180% | 0 | 0 | — |
case-02 | pass→pass | 10,416 | 9,357 | -10% | 1 | 1 | 0% | 1,016 | 4,268 | +320% | 0 | 0 | — |
case-03 | pass→pass | 17,234 | 10,578 | -39% | 1 | 1 | 0% | 2,261 | 4,523 | +100% | 0 | 0 | — |
case-04 | fail→pass | 17,451 | 11,491 | -34% | 1 | 1 | 0% | 2,502 | 4,665 | +86% | 0 | 0 | — |
case-05 | pass→pass | 17,711 | 8,686 | -51% | 1 | 1 | 0% | 2,105 | 5,027 | +139% | 0 | 0 | — |
case-06 | pass→pass | 8,089 | 4,937 | -39% | 1 | 1 | 0% | 1,602 | 4,641 | +190% | 0 | 0 | — |
case-07 | fail→pass | 12,130 | 11,190 | -8% | 1 | 1 | 0% | 2,134 | 4,728 | +122% | 0 | 0 | — |
case-08 | fail→pass | 18,957 | 15,244 | -20% | 1 | 1 | 0% | 2,604 | 5,828 | +124% | 0 | 0 | — |
case-09 | pass→pass | 17,551 | 15,428 | -12% | 1 | 1 | 0% | 2,140 | 5,325 | +149% | 0 | 0 | — |
case-10 | pass→pass | 15,394 | 20,767 | +35% | 1 | 1 | 0% | 2,641 | 6,021 | +128% | 0 | 0 | — |
case-13 | fail→pass | 7,019 | 7,791 | +11% | 1 | 1 | 0% | 1,194 | 4,225 | +254% | 0 | 0 | — |
case-14 | pass→pass | 10,120 | 8,700 | -14% | 1 | 1 | 0% | 822 | 4,389 | +434% | 0 | 0 | — |
case-15 | pass→pass | 13,396 | 3,328 | -75% | 1 | 1 | 0% | 1,400 | 4,300 | +207% | 0 | 0 | — |
case-16 | pass→pass | 22,641 | 14,134 | -38% | 1 | 1 | 0% | 2,461 | 5,927 | +141% | 0 | 0 | — |
case-17 | fail→pass | 18,206 | 9,711 | -47% | 1 | 1 | 0% | 551 | 4,618 | +738% | 0 | 0 | — |
case-18 | fail→pass | 17,259 | 13,760 | -20% | 1 | 1 | 0% | 375 | 5,810 | +1449% | 0 | 0 | — |
case-19 | pass→pass | 9,519 | 6,129 | -36% | 1 | 1 | 0% | 730 | 4,606 | +531% | 0 | 0 | — |
case-20 | pass→pass | 20,134 | 21,537 | +7% | 1 | 1 | 0% | 2,885 | 6,261 | +117% | 0 | 0 | — |
case-21 | pass→pass | 17,621 | 15,172 | -14% | 1 | 1 | 0% | 2,399 | 6,725 | +180% | 0 | 0 | — |
case-22 | pass→pass | 18,637 | 14,751 | -21% | 1 | 1 | 0% | 2,135 | 5,552 | +160% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 20 counted toward the lift figure. The other 2 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +32 percentage points is the difference between those two pass rates over the 20 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.