Migrate all repos into monorepo context folders

Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
This commit is contained in:
2026-06-30 20:39:52 +02:00
parent 2f2b295531
commit a5f8fb49ab
1717 changed files with 447332 additions and 0 deletions
@@ -0,0 +1,24 @@
<#
.SYNOPSIS
Check which pages exist under the configured root page
.USAGE
powershell -ExecutionPolicy Bypass -File scripts/confluence-check.ps1
#>
. "$PSScriptRoot/../config.ps1"
. "$PSScriptRoot/lib/confluence-api.ps1"
Write-Host "=== Confluence Page Check ===" -ForegroundColor Cyan
Write-Host "Root: $RootPageId | Space: $SpaceKey"
Write-Host ""
$Children = Get-ChildPages -PageId $RootPageId
Write-Host "Found $($Children.Count) child pages:" -ForegroundColor Green
Write-Host ""
Write-Host ("{0,-10} {1,-60} {2}" -f "ID", "Title", "Version")
Write-Host ("-" * 80)
$Children | Sort-Object { $_.title } | ForEach-Object {
$ver = if ($_.version) { $_.version.number } else { "?" }
Write-Host ("{0,-10} {1,-60} v{2}" -f $_.id, $_.title, $ver)
}
@@ -0,0 +1,94 @@
<#
.SYNOPSIS
Export a Confluence space page tree to local files (HTML + Markdown + JSON index)
.USAGE
powershell -ExecutionPolicy Bypass -File scripts/confluence-export.ps1
#>
# Load config and shared functions
. "$PSScriptRoot/../config.ps1"
. "$PSScriptRoot/lib/confluence-api.ps1"
Write-Host "============================================================"
Write-Host " Confluence Export - $SpaceKey"
Write-Host "============================================================"
Write-Host " URL: $ConfluenceUrl"
Write-Host " Root Page: $RootPageId"
Write-Host ""
function Export-PageTree {
param([string]$PageId, [string]$ParentPath, [int]$Depth = 0, [ref]$Counter)
$Indent = " " * $Depth
$Counter.Value++
$Page = Get-PageContent -PageId $PageId
if ($null -eq $Page) { return $null }
$Title = [string]$Page.title
Write-Host "$Indent[$($Counter.Value)] $Title"
$Labels = @()
if ($Page.metadata -and $Page.metadata.labels -and $Page.metadata.labels.results) {
$Labels = @($Page.metadata.labels.results | ForEach-Object { $_.name })
}
$VersionNum = if ($Page.version) { $Page.version.number } else { 0 }
$PageInfo = [PSCustomObject]@{
id = $Page.id; title = $Title; version = $VersionNum
labels = $Labels; depth = $Depth; path = "$ParentPath/$Title"; children = @()
}
# Save content
$HtmlContent = ""
$TextContent = ""
if ($Page.body -and $Page.body.storage) {
$HtmlContent = [string]$Page.body.storage.value
$TextContent = Convert-HtmlToText $HtmlContent
}
$SafeTitle = $Title -replace '[\\/:*?"<>|]', '_'
if ($SafeTitle.Length -gt 80) { $SafeTitle = $SafeTitle.Substring(0, 80) }
$PageDir = Join-Path $OutputDir "pages"
New-Item -ItemType Directory -Path $PageDir -Force | Out-Null
# Markdown
$MdLines = @("# $Title", "", "> Page ID: $($Page.id)", "> Version: $VersionNum", "> Path: $($PageInfo.path)", "> Labels: $(($Labels) -join ', ')", "", "---", "", $TextContent)
$MdPath = Join-Path $PageDir "$($Page.id)_$SafeTitle.md"
$MdLines -join "`n" | Out-File -FilePath $MdPath -Encoding UTF8
# HTML
$HtmlPath = Join-Path $PageDir "$($Page.id)_$SafeTitle.html"
$HtmlContent | Out-File -FilePath $HtmlPath -Encoding UTF8
# Children
$Children = @(Get-ChildPages -PageId $PageId)
$ChildInfos = @()
foreach ($Child in $Children) {
$ChildInfo = Export-PageTree -PageId $Child.id -ParentPath $PageInfo.path -Depth ($Depth + 1) -Counter $Counter
if ($null -ne $ChildInfo) { $ChildInfos += $ChildInfo }
}
$PageInfo.children = $ChildInfos
return $PageInfo
}
# Main
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
Write-Host ">> Testing connection..." -ForegroundColor Yellow
$TestResult = Invoke-ConfluenceApi -Endpoint "/space/$SpaceKey"
if ($null -eq $TestResult) { Write-Host " Connection failed." -ForegroundColor Red; exit 1 }
Write-Host " Space: $($TestResult.name) ($SpaceKey)" -ForegroundColor Green
Write-Host ""
Write-Host ">> Exporting page tree from $RootPageId..." -ForegroundColor Yellow
$Counter = [ref]0
$Tree = Export-PageTree -PageId $RootPageId -ParentPath "" -Depth 0 -Counter $Counter
Write-Host ""
Write-Host ">> $($Counter.Value) pages exported" -ForegroundColor Green
# JSON index
$JsonPath = Join-Path $OutputDir "page-index.json"
$Tree | ConvertTo-Json -Depth 20 | Out-File -FilePath $JsonPath -Encoding UTF8
Write-Host " Index: $JsonPath"
Write-Host "============================================================" -ForegroundColor Green
@@ -0,0 +1,88 @@
<#
.SYNOPSIS
Push local markdown files from /pages/ folder as Confluence pages
.DESCRIPTION
Reads all .md files from the /pages/ directory, converts them to HTML,
and creates/updates them as child pages under the configured root page.
.USAGE
powershell -ExecutionPolicy Bypass -File scripts/confluence-push.ps1
#>
. "$PSScriptRoot/../config.ps1"
. "$PSScriptRoot/lib/confluence-api.ps1"
Write-Host "============================================================"
Write-Host " Confluence Push - $SpaceKey"
Write-Host "============================================================"
Write-Host " Target: $ConfluenceUrl/spaces/$SpaceKey"
Write-Host " Parent: $RootPageId"
Write-Host ""
# Test connection
Write-Host ">> Testing connection..." -ForegroundColor Yellow
$Test = Invoke-ConfluenceApi -Endpoint "/content/$RootPageId"
if (-not $Test) { Write-Host " Connection failed!" -ForegroundColor Red; exit 1 }
Write-Host " Target page: $($Test.title)" -ForegroundColor Green
# Find markdown files to push
$PagesPath = Join-Path $ProjectDir "pages"
if (-not (Test-Path $PagesPath)) {
Write-Host " No /pages/ directory found. Create it and add .md files." -ForegroundColor Yellow
exit 0
}
$MdFiles = Get-ChildItem -Path $PagesPath -Filter "*.md" | Sort-Object Name
if ($MdFiles.Count -eq 0) {
Write-Host " No .md files found in /pages/" -ForegroundColor Yellow
exit 0
}
Write-Host ""
Write-Host ">> Pushing $($MdFiles.Count) pages..." -ForegroundColor Yellow
function Convert-MdToHtml($mdContent) {
# Simple markdown to Confluence storage format conversion
$html = $mdContent
# Headers
$html = $html -replace '^#### (.+)$', '<h4>$1</h4>'
$html = $html -replace '^### (.+)$', '<h3>$1</h3>'
$html = $html -replace '^## (.+)$', '<h2>$1</h2>'
$html = $html -replace '^# (.+)$', '<h1>$1</h1>'
# Bold/italic
$html = $html -replace '\*\*(.+?)\*\*', '<strong>$1</strong>'
$html = $html -replace '\*(.+?)\*', '<em>$1</em>'
# Lists
$html = $html -replace '^- (.+)$', '<li>$1</li>'
# Code blocks
$html = $html -replace '```(\w*)\n([\s\S]*?)```', '<ac:structured-macro ac:name="code"><ac:parameter ac:name="language">$1</ac:parameter><ac:plain-text-body><![CDATA[$2]]></ac:plain-text-body></ac:structured-macro>'
# Inline code
$html = $html -replace '`(.+?)`', '<code>$1</code>'
# Paragraphs (lines not already tagged)
$lines = $html -split "`n"
$result = @()
foreach ($line in $lines) {
if ($line -match '^<' -or $line.Trim() -eq '') {
$result += $line
} else {
$result += "<p>$line</p>"
}
}
return $result -join "`n"
}
foreach ($File in $MdFiles) {
$Content = Get-Content $File.FullName -Raw -Encoding UTF8
# Use filename (without .md) as page title, or first # heading
$Title = $File.BaseName
if ($Content -match '^# (.+)$') { $Title = $Matches[1] }
Write-Host " $Title..." -NoNewline
$HtmlBody = Convert-MdToHtml $Content
$PageId = Create-Or-Update-Page -Title $Title -HtmlBody $HtmlBody -ParentId $RootPageId
if ($PageId) { Write-Host "" }
}
Write-Host ""
Write-Host "============================================================" -ForegroundColor Green
Write-Host " Done! $($MdFiles.Count) pages processed." -ForegroundColor Green
Write-Host "============================================================" -ForegroundColor Green
@@ -0,0 +1,41 @@
<#
.SYNOPSIS
Read specific Confluence pages by ID and save locally
.USAGE
powershell -ExecutionPolicy Bypass -File scripts/confluence-read.ps1 -PageIds "123,456,789"
#>
param([string]$PageIds = "")
. "$PSScriptRoot/../config.ps1"
. "$PSScriptRoot/lib/confluence-api.ps1"
Write-Host "=== Confluence Page Reader ===" -ForegroundColor Cyan
if (-not $PageIds) {
$PageIds = Read-Host "Enter page IDs (comma-separated)"
}
$Ids = $PageIds -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
$OutPath = Join-Path $ProjectDir "data/pages"
New-Item -ItemType Directory -Path $OutPath -Force | Out-Null
foreach ($Id in $Ids) {
Write-Host ""
Write-Host ">> Page $Id" -ForegroundColor Yellow
$Page = Get-PageContent -PageId $Id
if (-not $Page) { continue }
Write-Host " Title: $($Page.title) (v$($Page.version.number))" -ForegroundColor Green
$HtmlContent = ""
if ($Page.body -and $Page.body.storage) { $HtmlContent = $Page.body.storage.value }
$TextContent = Convert-HtmlToText $HtmlContent
$SafeTitle = $Page.title -replace '[\\/:*?"<>|]', '_'
$HtmlContent | Out-File "$OutPath/$($Page.id)_$SafeTitle.html" -Encoding UTF8
$TextContent | Out-File "$OutPath/$($Page.id)_$SafeTitle.md" -Encoding UTF8
Write-Host " Saved ($($HtmlContent.Length) chars)"
}
Write-Host ""
Write-Host "Done! Files in $OutPath" -ForegroundColor Green
@@ -0,0 +1,160 @@
# Shared Confluence API functions
# Source this file in other scripts: . "$PSScriptRoot/lib/confluence-api.ps1"
function Invoke-ConfluenceApi {
param([string]$Endpoint, [hashtable]$Params = @{}, [string]$Method = "Get", [string]$Body = $null)
$QS = ($Params.GetEnumerator() | ForEach-Object {
"$($_.Key)=$([Uri]::EscapeDataString($_.Value))"
}) -join "&"
$Uri = "$ApiBase$Endpoint"
if ($QS) { $Uri = "$Uri`?$QS" }
try {
if ($Body) {
$Resp = Invoke-WebRequest -Uri $Uri -Method $Method -Headers $Headers -Body ([System.Text.Encoding]::UTF8.GetBytes($Body)) -UseBasicParsing -ErrorAction Stop
} else {
$Resp = Invoke-WebRequest -Uri $Uri -Method $Method -Headers $Headers -UseBasicParsing -ErrorAction Stop
}
return ($Resp.Content | ConvertFrom-Json)
} catch {
Write-Host " ERROR at $Uri : $($_.Exception.Message)" -ForegroundColor Red
return $null
}
}
function Get-ChildPages {
param([string]$PageId)
$AllChildren = @()
$Start = 0
do {
$Result = Invoke-ConfluenceApi -Endpoint "/content/$PageId/child/page" -Params @{
start = "$Start"
limit = "$PerPage"
expand = "version"
}
if ($null -eq $Result) { break }
$Pages = $Result.results
if ($null -eq $Pages -or $Pages.Count -eq 0) { break }
$AllChildren += $Pages
$Start += $PerPage
$Size = 0
if ($Result.size) { $Size = $Result.size }
} while ($Size -ge $PerPage)
return $AllChildren
}
function Get-PageContent {
param([string]$PageId)
return Invoke-ConfluenceApi -Endpoint "/content/$PageId" -Params @{
expand = "body.storage,version,ancestors,metadata.labels,space"
}
}
function Convert-HtmlToText($html) {
if ($null -eq $html) { return "" }
$text = $html -replace '<br\s*/?>', "`n"
$text = $text -replace '<p[^>]*>', "`n"
$text = $text -replace '</p>', ""
$text = $text -replace '<li[^>]*>', "`n- "
$text = $text -replace '<h[1-6][^>]*>', "`n## "
$text = $text -replace '</h[1-6]>', "`n"
$text = $text -replace '<tr[^>]*>', "`n| "
$text = $text -replace '<td[^>]*>', " | "
$text = $text -replace '<th[^>]*>', " | "
$text = $text -replace '<[^>]+>', ''
Add-Type -AssemblyName System.Web -ErrorAction SilentlyContinue
try { $text = [System.Web.HttpUtility]::HtmlDecode($text) } catch {}
return $text.Trim()
}
function Create-Or-Update-Page {
param([string]$Title, [string]$HtmlBody, [string]$ParentId)
$SearchUri = "$ApiBase/content?spaceKey=$SpaceKey&title=$([Uri]::EscapeDataString($Title))&type=page"
try {
$SearchResp = Invoke-WebRequest -Uri $SearchUri -Headers $Headers -UseBasicParsing -ErrorAction Stop
$SearchData = $SearchResp.Content | ConvertFrom-Json
} catch {
Write-Host " ERROR searching: $($_.Exception.Message)" -ForegroundColor Red
return $null
}
if ($SearchData.results -and $SearchData.results.Count -gt 0) {
$PageId = $SearchData.results[0].id
try {
$VerResp = Invoke-WebRequest -Uri "$ApiBase/content/$PageId`?expand=version,body.storage" -Headers $Headers -UseBasicParsing -ErrorAction Stop
$VerData = $VerResp.Content | ConvertFrom-Json
$Version = $VerData.version.number + 1
$CurrentBody = ""
if ($VerData.body -and $VerData.body.storage) { $CurrentBody = $VerData.body.storage.value }
$NormCurrent = ($CurrentBody -replace '<[^>]+>' -replace '&\w+;' -replace '\s+', ' ').Trim()
$NormNew = ($HtmlBody -replace '<[^>]+>' -replace '&\w+;' -replace '\s+', ' ').Trim()
if ($NormCurrent -eq $NormNew) {
Write-Host " No changes (ID: $PageId)" -ForegroundColor DarkGray
return $PageId
}
} catch {
$Version = $SearchData.results[0].version.number + 1
}
Write-Host " Updating (ID: $PageId, v$Version)..." -NoNewline
$Body = @{
version = @{ number = $Version }
title = $Title
type = "page"
body = @{ storage = @{ value = $HtmlBody; representation = "storage" } }
} | ConvertTo-Json -Depth 10
try {
$Resp = Invoke-WebRequest -Uri "$ApiBase/content/$PageId" -Method Put -Headers $Headers -Body ([System.Text.Encoding]::UTF8.GetBytes($Body)) -UseBasicParsing -ErrorAction Stop
Write-Host " OK" -ForegroundColor Green
return ($Resp.Content | ConvertFrom-Json).id
} catch {
Write-Host " ERROR: $($_.Exception.Message)" -ForegroundColor Red
return $null
}
} else {
Write-Host " Creating new page..." -NoNewline
$Body = @{
type = "page"
title = $Title
space = @{ key = $SpaceKey }
ancestors = @(@{ id = $ParentId })
body = @{ storage = @{ value = $HtmlBody; representation = "storage" } }
} | ConvertTo-Json -Depth 10
try {
$Resp = Invoke-WebRequest -Uri "$ApiBase/content" -Method Post -Headers $Headers -Body ([System.Text.Encoding]::UTF8.GetBytes($Body)) -UseBasicParsing -ErrorAction Stop
Write-Host " OK" -ForegroundColor Green
return ($Resp.Content | ConvertFrom-Json).id
} catch {
Write-Host " ERROR: $($_.Exception.Message)" -ForegroundColor Red
return $null
}
}
}
function Move-Page($PageId, $NewParentId) {
$Page = Invoke-ConfluenceApi -Endpoint "/content/$PageId" -Params @{ expand = "version,ancestors" }
if (-not $Page) { return $false }
if ($Page.ancestors -and $Page.ancestors.Count -gt 0) {
if ($Page.ancestors[-1].id -eq $NewParentId) { return $true }
}
$Version = $Page.version.number + 1
$Json = @{
type = "page"; title = $Page.title
version = @{ number = $Version }
ancestors = @(@{ id = $NewParentId })
} | ConvertTo-Json -Depth 10
$Result = Invoke-ConfluenceApi -Endpoint "/content/$PageId" -Method "Put" -Body $Json
return ($null -ne $Result)
}
function Delete-Page($PageId) {
try {
Invoke-WebRequest -Uri "$ApiBase/content/$PageId" -Method Delete -Headers $Headers -UseBasicParsing -ErrorAction Stop
return $true
} catch {
Write-Host " ERROR deleting $PageId : $($_.Exception.Message)" -ForegroundColor Red
return $false
}
}