Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Atlassian Confluence integration for enterprise documentation. Create and update pages via API, manage spaces and permissions, handle content migration, and sync between Markdown and Confluence.
.claude/skills/a5c-ai-confluence-docs/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 44% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 50% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 98% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 77% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 137% | 0% |
Atlassian Confluence integration for enterprise documentation.
Invoke this skill when you need to:
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | action | string | Yes | create, update, migrate, export | | baseUrl | string | Yes | Confluence instance URL | | spaceKey | string | Yes | Target space key | | sourcePath | string | No | Source Markdown files | | pageId | string | No | Specific page ID for updates | | parentPageId | string | No | Parent page for hierarchy |
json{ "action": "migrate", "baseUrl": "https://company.atlassian.net/wiki", "spaceKey": "DOCS", "sourcePath": "./docs", "parentPageId": "123456" }
json{ "baseUrl": "https://company.atlassian.net/wiki", "auth": { "type": "token", "email": "${CONFLUENCE_EMAIL}", "token": "${CONFLUENCE_TOKEN}" }, "space": { "key": "DOCS", "name": "Documentation" }, "migration": { "preserveStructure": true, "convertTables": true, "uploadImages": true, "macroMapping": { "note": "info", "warning": "warning", "code": "code" } }, "sync": { "dryRun": false, "updateExisting": true, "createMissing": true, "archiveRemoved": false } }
javascriptconst ConfluenceClient = require('confluence-api'); class ConfluenceManager { constructor(config) { this.client = new ConfluenceClient({ username: config.email, password: config.token, baseUrl: config.baseUrl }); } // Create a new page async createPage(spaceKey, title, content, parentId = null) { const page = { type: 'page', title, space: { key: spaceKey }, body: { storage: { value: content, representation: 'storage' } } }; if (parentId) { page.ancestors = [{ id: parentId }]; } return await this.client.postContent(page); } // Update existing page async updatePage(pageId, title, content, version) { const page = { id: pageId, type: 'page', title, version: { number: version + 1 }, body: { storage: { value: content, representation: 'storage' } } }; return await this.client.putContent(page); } // Get page by title async getPageByTitle(spaceKey, title) { const result = await this.client.getContentBySpaceKey(spaceKey, { title, expand: 'version,body.storage' }); return result.results[0] || null; } // Upload attachment async uploadAttachment(pageId, filePath, comment = '') { const form = new FormData(); form.append('file', fs.createReadStream(filePath)); form.append('comment', comment); return await this.client.createAttachment(pageId, form); } // Add labels async addLabels(pageId, labels) { const labelPayload = labels.map(name => ({ prefix: 'global', name })); return await this.client.postLabels(pageId, labelPayload); } }
javascriptconst marked = require('marked'); class MarkdownToConfluence { constructor(options = {}) { this.options = options; this.attachments = []; } convert(markdown, metadata = {}) { // Parse front matter const { content, frontMatter } = this.parseFrontMatter(markdown); // Convert markdown to HTML let html = marked.parse(content); // Convert to Confluence storage format html = this.convertToStorageFormat(html); // Handle macros html = this.convertMacros(html); // Handle code blocks html = this.convertCodeBlocks(html); // Handle images html = this.convertImages(html); // Handle tables html = this.convertTables(html); return { title: frontMatter.title || metadata.title, content: html, labels: frontMatter.tags || [], attachments: this.attachments }; } convertMacros(html) { // Convert admonitions to Confluence macros const macroMap = { 'note': 'info', 'warning': 'warning', 'tip': 'tip', 'danger': 'warning' }; for (const [mdType, confType] of Object.entries(macroMap)) { const regex = new RegExp(`<div class="${mdType}">([\\s\\S]*?)</div>`, 'g'); html = html.replace(regex, (match, content) => { return `<ac:structured-macro ac:name="${confType}"> <ac:rich-text-body>${content}</ac:rich-text-body> </ac:structured-macro>`; }); } return html; } convertCodeBlocks(html) { // Convert code blocks to Confluence code macro return html.replace( /<pre><code class="language-(\w+)">([\s\S]*?)<\/code><\/pre>/g, (match, language, code) => { const decodedCode = this.decodeHtml(code); return `<ac:structured-macro ac:name="code"> <ac:parameter ac:name="language">${language}</ac:parameter> <ac:plain-text-body><![CDATA[${decodedCode}]]></ac:plain-text-body> </ac:structured-macro>`; } ); } convertImages(html) { // Convert images to Confluence attachments return html.replace( /<img src="([^"]+)" alt="([^"]*)"[^>]*>/g, (match, src, alt) => { if (src.startsWith('http')) { // External image return `<ac:image><ri:url ri:value="${src}" /></ac:image>`; } else { // Local attachment const filename = path.basename(src); this.attachments.push({ src, filename }); return `<ac:image><ri:attachment ri:filename="${filename}" /></ac:image>`; } } ); } convertTables(html) { // Confluence uses standard HTML tables but needs specific attributes return html.replace(/<table>/g, '<table class="wrapped">'); } }
javascriptclass ConfluenceToMarkdown { constructor(client) { this.client = client; } async exportSpace(spaceKey, outputDir) { const pages = await this.getAllPages(spaceKey); const structure = this.buildHierarchy(pages); for (const page of pages) { const markdown = await this.exportPage(page); const filePath = this.getFilePath(page, structure, outputDir); await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, markdown); } return { exported: pages.length }; } async exportPage(page) { const content = page.body.storage.value; // Convert Confluence storage format to Markdown let markdown = this.convertToMarkdown(content); // Add front matter const frontMatter = { title: page.title, confluence_id: page.id, last_modified: page.version.when }; return `--- ${yaml.stringify(frontMatter)}--- ${markdown}`; } convertToMarkdown(storage) { let md = storage; // Convert code macro md = md.replace( /<ac:structured-macro ac:name="code"[^>]*>[\s\S]*?<ac:parameter ac:name="language">(\w+)<\/ac:parameter>[\s\S]*?<ac:plain-text-body><!\[CDATA\[([\s\S]*?)\]\]><\/ac:plain-text-body>[\s\S]*?<\/ac:structured-macro>/g, (match, lang, code) => `\`\`\`${lang}\n${code}\n\`\`\`` ); // Convert info macro md = md.replace( /<ac:structured-macro ac:name="(info|warning|tip)"[^>]*>[\s\S]*?<ac:rich-text-body>([\s\S]*?)<\/ac:rich-text-body>[\s\S]*?<\/ac:structured-macro>/g, (match, type, content) => `> **${type.toUpperCase()}:** ${this.stripHtml(content)}` ); // Convert headings, lists, etc. md = this.convertHtmlToMarkdown(md); return md; } }
javascriptasync function syncDocumentation(config) { const confluence = new ConfluenceManager(config); const converter = new MarkdownToConfluence(config.migration); // Get local files const localFiles = await glob('docs/**/*.md'); // Get Confluence pages const pages = await confluence.getSpaceContent(config.space.key); const results = { created: [], updated: [], skipped: [], errors: [] }; for (const file of localFiles) { try { const markdown = await fs.readFile(file, 'utf8'); const converted = converter.convert(markdown, { file }); // Check if page exists const existing = await confluence.getPageByTitle( config.space.key, converted.title ); if (existing) { if (config.sync.updateExisting) { await confluence.updatePage( existing.id, converted.title, converted.content, existing.version.number ); results.updated.push(file); } else { results.skipped.push(file); } } else if (config.sync.createMissing) { await confluence.createPage( config.space.key, converted.title, converted.content, config.parentPageId ); results.created.push(file); } // Upload attachments for (const attachment of converted.attachments) { await confluence.uploadAttachment( existing?.id || results.created[results.created.length - 1].id, attachment.src ); } } catch (error) { results.errors.push({ file, error: error.message }); } } return results; }
javascriptasync function createDocumentationSpace(config) { const client = new ConfluenceManager(config); const space = await client.client.postSpace({ key: config.space.key, name: config.space.name, description: { plain: { value: config.space.description, representation: 'plain' } }, permissions: [ { subjects: { group: { name: 'confluence-users' } }, operation: { key: 'read', target: 'space' } } ] }); // Create home page await client.createPage( config.space.key, 'Home', '<h1>Welcome to Documentation</h1>', null ); return space; }
json{ "devDependencies": { "confluence-api": "^1.4.0", "marked": "^12.0.0", "gray-matter": "^4.0.0", "form-data": "^4.0.0" } }
bash# Sync Markdown to Confluence node scripts/confluence-sync.js --config confluence.config.json # Export Confluence to Markdown node scripts/confluence-export.js --space DOCS --output ./exported # Create new space node scripts/confluence-space.js create --key NEWDOCS --name "New Documentation"
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 33,204 | 27,858 | -16% | 1 | 1 | 0% | 5,828 | 8,420 | +44% | 0 | 0 | — |
case-02 | fail→pass | 26,233 | 18,813 | -28% | 1 | 1 | 0% | 4,306 | 6,450 | +50% | 0 | 0 | — |
case-03 | fail→pass | 25,593 | 20,557 | -20% | 1 | 1 | 0% | 3,524 | 6,978 | +98% | 0 | 0 | — |
case-04 | fail→fail | 34,801 | 5,022 | -86% | 1 | 1 | 0% | 1,267 | 4,423 | +249% | 0 | 0 | — |
case-05 | pass→pass | 16,993 | 10,909 | -36% | 1 | 1 | 0% | 2,267 | 4,647 | +105% | 0 | 0 | — |
case-06 | pass→pass | 27,254 | 17,292 | -37% | 1 | 1 | 0% | 4,145 | 5,847 | +41% | 0 | 0 | — |
case-07 | pass→pass | 29,854 | 17,681 | -41% | 1 | 1 | 0% | 3,806 | 5,860 | +54% | 0 | 0 | — |
case-08 | pass→pass | 17,631 | 17,967 | +2% | 1 | 1 | 0% | 3,441 | 5,815 | +69% | 0 | 0 | — |
case-09 | fail→pass | 24,748 | 34,835 | +41% | 1 | 1 | 0% | 3,736 | 6,625 | +77% | 0 | 0 | — |
case-10 | pass→pass | 13,255 | 5,745 | -57% | 1 | 1 | 0% | 1,560 | 4,317 | +177% | 0 | 0 | — |
case-11 | pass→pass | 16,430 | 7,688 | -53% | 1 | 1 | 0% | 1,772 | 4,357 | +146% | 0 | 0 | — |
case-12 | pass→pass | 4,396 | 7,825 | +78% | 1 | 1 | 0% | 799 | 3,945 | +394% | 0 | 0 | — |
case-13 | fail→pass | 15,126 | 10,093 | -33% | 1 | 1 | 0% | 1,885 | 4,464 | +137% | 0 | 0 | — |
case-14 | pass→pass | 15,967 | 3,965 | -75% | 1 | 1 | 0% | 2,929 | 4,261 | +45% | 0 | 0 | — |
case-15 | pass→pass | 14,675 | 10,660 | -27% | 1 | 1 | 0% | 2,060 | 5,041 | +145% | 0 | 0 | — |
case-16 | fail→pass | 6,901 | 1,800 | -74% | 1 | 1 | 0% | 1,047 | 3,680 | +251% | 0 | 0 | — |
case-17 | fail→pass | 9,336 | 7,238 | -22% | 1 | 1 | 0% | 1,581 | 3,683 | +133% | 0 | 0 | — |
case-18 | fail→fail | 19,874 | 11,174 | -44% | 1 | 1 | 0% | 3,103 | 5,861 | +89% | 0 | 0 | — |
case-19 | pass→pass | 15,206 | 3,512 | -77% | 1 | 1 | 0% | 1,875 | 4,091 | +118% | 0 | 0 | — |
case-20 | pass→pass | 14,691 | 13,506 | -8% | 1 | 1 | 0% | 2,013 | 5,229 | +160% | 0 | 0 | — |
case-21 | pass→pass | 16,896 | 13,168 | -22% | 1 | 1 | 0% | 2,559 | 5,128 | +100% | 0 | 0 | — |
case-22 | pass→pass | 15,930 | 23,566 | +48% | 1 | 1 | 0% | 2,169 | 7,372 | +240% | 0 | 0 | — |
case-23 | pass→pass | 17,200 | 18,643 | +8% | 1 | 1 | 0% | 2,670 | 6,357 | +138% | 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. 23 cases were attempted, and 22 counted toward the lift figure. The other 1 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 +30 percentage points is the difference between those two pass rates over the 22 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.