deploy-to-openclaw.ps1 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. # OpenClaw VOC Skills Deploy Script v3.0.0
  2. #
  3. # Deploys skills, workshop playbook, and memory templates to OpenClaw
  4. #
  5. # Usage:
  6. # .\deploy-to-openclaw.ps1 # Deploy everything
  7. # .\deploy-to-openclaw.ps1 -DryRun # Preview only
  8. # .\deploy-to-openclaw.ps1 -SkillsOnly # Deploy skills only
  9. # .\deploy-to-openclaw.ps1 -WorkshopOnly # Deploy workshop only
  10. # .\deploy-to-openclaw.ps1 -OpenClawDir "D:\path" # Custom OpenClaw directory
  11. param(
  12. [string]$OpenClawDir = "$env:USERPROFILE\.openclaw",
  13. [string]$ProjectRoot = (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent),
  14. [switch]$SkillsOnly,
  15. [switch]$WorkshopOnly,
  16. [switch]$DryRun
  17. )
  18. $ErrorActionPreference = "Stop"
  19. $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
  20. # ============================================
  21. # Paths
  22. # ============================================
  23. $SkillsTarget = Join-Path $OpenClawDir "skills"
  24. $WorkspaceTarget = Join-Path $OpenClawDir "workspace"
  25. $MemoryTarget = Join-Path $WorkspaceTarget "memory"
  26. $SkillCategories = @(
  27. "voc",
  28. "social-media",
  29. "competitor-analysis",
  30. "review-analysis",
  31. "synthesis",
  32. "social-voc",
  33. "douyin",
  34. "xiaohongshu",
  35. "video-creation",
  36. "jimeng",
  37. "payment",
  38. "test"
  39. )
  40. # ============================================
  41. # Helper Functions
  42. # ============================================
  43. function Remove-Bom {
  44. param([string]$FilePath)
  45. $bytes = [System.IO.File]::ReadAllBytes($FilePath)
  46. if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
  47. $clean = $bytes[3..($bytes.Length - 1)]
  48. [System.IO.File]::WriteAllBytes($FilePath, $clean)
  49. return $true
  50. }
  51. return $false
  52. }
  53. function Test-SkillValid {
  54. param([string]$SkillDir, [string]$SkillName)
  55. $skillMd = Join-Path $SkillDir "SKILL.md"
  56. $apiConf = Join-Path $SkillDir "api-config.json"
  57. if (-not (Test-Path $skillMd)) {
  58. Write-Host " [SKIP] $SkillName - missing SKILL.md" -ForegroundColor DarkGray
  59. return $false
  60. }
  61. # Validate YAML frontmatter
  62. $mdContent = [System.IO.File]::ReadAllText($skillMd, [System.Text.Encoding]::UTF8)
  63. if ($mdContent -notmatch "^---\s*\r?\n") {
  64. Write-Host " [WARN] $SkillName - SKILL.md missing YAML frontmatter" -ForegroundColor Yellow
  65. }
  66. # Validate api-config.json if exists
  67. if (Test-Path $apiConf) {
  68. try {
  69. $null = Get-Content $apiConf -Raw -Encoding UTF8 | ConvertFrom-Json
  70. } catch {
  71. Write-Host " [ERROR] $SkillName - invalid JSON in api-config.json" -ForegroundColor Red
  72. return $false
  73. }
  74. }
  75. return $true
  76. }
  77. function Deploy-File {
  78. param([string]$Source, [string]$Dest, [string]$Label)
  79. if ($DryRun) {
  80. $tag = if (Test-Path $Dest) { "UPDATE" } else { "NEW" }
  81. Write-Host " [$tag] $Label" -ForegroundColor White
  82. } else {
  83. $destDir = Split-Path $Dest -Parent
  84. if (-not (Test-Path $destDir)) {
  85. New-Item -ItemType Directory -Path $destDir -Force | Out-Null
  86. }
  87. Copy-Item $Source -Destination $Dest -Force
  88. }
  89. }
  90. # ============================================
  91. # Banner
  92. # ============================================
  93. Write-Host ""
  94. Write-Host " ====================================================" -ForegroundColor Cyan
  95. Write-Host " OpenClaw VOC Skills Deploy v3.0.0" -ForegroundColor Cyan
  96. Write-Host " ====================================================" -ForegroundColor Cyan
  97. Write-Host ""
  98. Write-Host " Project: $ProjectRoot"
  99. Write-Host " OpenClaw: $OpenClawDir"
  100. if ($DryRun) { Write-Host " Mode: DRY RUN (preview only)" -ForegroundColor Yellow }
  101. if ($SkillsOnly) { Write-Host " Scope: Skills only" -ForegroundColor Yellow }
  102. if ($WorkshopOnly) { Write-Host " Scope: Workshop only" -ForegroundColor Yellow }
  103. Write-Host ""
  104. # Pre-flight check
  105. if (-not (Test-Path $OpenClawDir)) {
  106. Write-Host " [ERROR] OpenClaw directory not found: $OpenClawDir" -ForegroundColor Red
  107. Write-Host " Is OpenClaw installed? Specify -OpenClawDir to override." -ForegroundColor Yellow
  108. exit 1
  109. }
  110. $totalDeployed = 0
  111. $totalSkipped = 0
  112. $totalErrors = 0
  113. $bomFixed = 0
  114. # ============================================
  115. # Part 1: Deploy Skills
  116. # ============================================
  117. if (-not $WorkshopOnly) {
  118. Write-Host " [1/3] Deploying Skills" -ForegroundColor Cyan
  119. Write-Host " $('-' * 48)"
  120. if (-not $DryRun -and -not (Test-Path $SkillsTarget)) {
  121. New-Item -ItemType Directory -Path $SkillsTarget -Force | Out-Null
  122. }
  123. foreach ($cat in $SkillCategories) {
  124. $catPath = Join-Path $ProjectRoot $cat
  125. if (-not (Test-Path $catPath)) { continue }
  126. $skillDirs = Get-ChildItem -Directory -Path $catPath -ErrorAction SilentlyContinue
  127. if ($null -eq $skillDirs -or $skillDirs.Count -eq 0) { continue }
  128. Write-Host " [$cat]" -ForegroundColor DarkCyan
  129. foreach ($skillDir in $skillDirs) {
  130. $skillName = $skillDir.Name
  131. if (-not (Test-SkillValid $skillDir.FullName $skillName)) {
  132. $totalSkipped++
  133. continue
  134. }
  135. $destDir = Join-Path $SkillsTarget $skillName
  136. if ($DryRun) {
  137. $tag = if (Test-Path $destDir) { "UPDATE" } else { "NEW" }
  138. Write-Host " [$tag] $skillName" -ForegroundColor White
  139. } else {
  140. if (Test-Path $destDir) {
  141. Remove-Item -Recurse -Force $destDir
  142. }
  143. New-Item -ItemType Directory -Path $destDir -Force | Out-Null
  144. Get-ChildItem -File -Path $skillDir.FullName | ForEach-Object {
  145. Copy-Item $_.FullName -Destination $destDir
  146. }
  147. # Fix UTF-8 BOM in SKILL.md (causes OpenClaw parsing failures)
  148. $deployedMd = Join-Path $destDir "SKILL.md"
  149. if (Test-Path $deployedMd) {
  150. if (Remove-Bom $deployedMd) {
  151. $bomFixed++
  152. }
  153. }
  154. Write-Host " [OK] $skillName" -ForegroundColor Green
  155. }
  156. $totalDeployed++
  157. }
  158. }
  159. # Also deploy workshop skills (e.g. brand-context-builder)
  160. $workshopSkillsPath = Join-Path $ProjectRoot "workshop"
  161. if (Test-Path $workshopSkillsPath) {
  162. $wsSkills = Get-ChildItem -Directory -Path $workshopSkillsPath | Where-Object {
  163. Test-Path (Join-Path $_.FullName "SKILL.md")
  164. }
  165. if ($wsSkills.Count -gt 0) {
  166. Write-Host " [workshop skills]" -ForegroundColor DarkCyan
  167. foreach ($ws in $wsSkills) {
  168. $skillName = $ws.Name
  169. $destDir = Join-Path $SkillsTarget $skillName
  170. if ($DryRun) {
  171. $tag = if (Test-Path $destDir) { "UPDATE" } else { "NEW" }
  172. Write-Host " [$tag] $skillName" -ForegroundColor White
  173. } else {
  174. if (Test-Path $destDir) { Remove-Item -Recurse -Force $destDir }
  175. New-Item -ItemType Directory -Path $destDir -Force | Out-Null
  176. Get-ChildItem -File -Path $ws.FullName | ForEach-Object {
  177. Copy-Item $_.FullName -Destination $destDir
  178. }
  179. $deployedMd = Join-Path $destDir "SKILL.md"
  180. if (Test-Path $deployedMd) { if (Remove-Bom $deployedMd) { $bomFixed++ } }
  181. Write-Host " [OK] $skillName" -ForegroundColor Green
  182. }
  183. $totalDeployed++
  184. }
  185. }
  186. }
  187. Write-Host ""
  188. }
  189. # ============================================
  190. # Part 2: Deploy Workshop Playbooks (v3 split-file architecture)
  191. # ============================================
  192. if (-not $SkillsOnly) {
  193. Write-Host " [2/3] Deploying Workshop Playbooks" -ForegroundColor Cyan
  194. Write-Host " $('-' * 48)"
  195. # v3: Deploy all workshop .md files (global rules + session files + supporting docs)
  196. $workshopMdFiles = Get-ChildItem -File -Path (Join-Path $ProjectRoot "workshop") -Filter "*.md" -ErrorAction SilentlyContinue
  197. if ($workshopMdFiles -and $workshopMdFiles.Count -gt 0) {
  198. foreach ($mdFile in $workshopMdFiles) {
  199. $dest = Join-Path $WorkspaceTarget $mdFile.Name
  200. Deploy-File $mdFile.FullName $dest $mdFile.Name
  201. if (-not $DryRun) { Write-Host " [OK] $($mdFile.Name)" -ForegroundColor Green }
  202. $totalDeployed++
  203. }
  204. } else {
  205. Write-Host " [SKIP] No workshop .md files found" -ForegroundColor DarkGray
  206. }
  207. Write-Host ""
  208. }
  209. # ============================================
  210. # Part 3: Deploy Memory Templates
  211. # ============================================
  212. if (-not $SkillsOnly) {
  213. Write-Host " [3/3] Deploying Memory Templates" -ForegroundColor Cyan
  214. Write-Host " $('-' * 48)"
  215. $memSrc = Join-Path $ProjectRoot "workshop\memory-templates"
  216. if (Test-Path $memSrc) {
  217. if (-not $DryRun -and -not (Test-Path $MemoryTarget)) {
  218. New-Item -ItemType Directory -Path $MemoryTarget -Force | Out-Null
  219. }
  220. Get-ChildItem -File -Path $memSrc -Filter "*.json" | ForEach-Object {
  221. $dest = Join-Path $MemoryTarget $_.Name
  222. Deploy-File $_.FullName $dest $_.Name
  223. if (-not $DryRun) { Write-Host " [OK] $($_.Name)" -ForegroundColor Green }
  224. $totalDeployed++
  225. }
  226. } else {
  227. Write-Host " [SKIP] memory-templates directory not found" -ForegroundColor DarkGray
  228. }
  229. Write-Host ""
  230. }
  231. # ============================================
  232. # Part 4: Credentials Template
  233. # ============================================
  234. if (-not $DryRun -and -not $WorkshopOnly) {
  235. $credTemplate = Join-Path $ProjectRoot "__config\voc-credentials.template.json"
  236. $credTarget = Join-Path $OpenClawDir "voc-credentials.template.json"
  237. $credFile = Join-Path $OpenClawDir "voc-credentials.json"
  238. if (Test-Path $credTemplate) {
  239. Copy-Item $credTemplate -Destination $credTarget -Force
  240. if (-not (Test-Path $credFile)) {
  241. Write-Host " [INFO] Credentials template copied. Fill in vocToken in:" -ForegroundColor Yellow
  242. Write-Host " $credFile" -ForegroundColor Yellow
  243. }
  244. }
  245. }
  246. # ============================================
  247. # Summary
  248. # ============================================
  249. Write-Host " ====================================================" -ForegroundColor Cyan
  250. Write-Host " Deploy Summary" -ForegroundColor Cyan
  251. Write-Host " ====================================================" -ForegroundColor Cyan
  252. Write-Host ""
  253. Write-Host " Deployed: $totalDeployed"
  254. Write-Host " Skipped: $totalSkipped"
  255. Write-Host " Errors: $totalErrors"
  256. if ($bomFixed -gt 0) {
  257. Write-Host " BOM fixed: $bomFixed" -ForegroundColor Yellow
  258. }
  259. Write-Host ""
  260. if ($DryRun) {
  261. Write-Host " Remove -DryRun to execute actual deployment." -ForegroundColor Yellow
  262. } else {
  263. Write-Host " Done! Restart OpenClaw gateway to load new skills." -ForegroundColor Green
  264. Write-Host ""
  265. Write-Host " Verify:" -ForegroundColor DarkGray
  266. Write-Host " openclaw skills list" -ForegroundColor DarkGray
  267. Write-Host " openclaw skills info <skill-name>" -ForegroundColor DarkGray
  268. }
  269. Write-Host ""