⚡ OpenClaw Skill 生成器

自訂你的龍蝦技能,一鍵下載使用

📝 第一步:選擇 Skill 類型

📁
磁碟盤點
☁️
雲端上傳
🕷️
網頁爬蟲
排程提醒
📂
檔案整理
💾
自動備份
🔧
自訂 Skill

⚙️ 第二步:自訂設定

只能用英文、數字和連字號
💡 提示:生成後的檔案需要放到 ~/.openclaw/workspace/skills/你的skill名稱/ 資料夾

👀 第三步:預覽與下載

# 請先在左側選擇 Skill 類型並填寫設定
📂 安裝位置:
C:\Users\你的使用者名稱\.openclaw\workspace\skills\[skill名稱]\

📁 資料夾結構:
skills/
└── [skill名稱]/
    ├── SKILL.md
    └── scripts/
        └── run.ps1
', ''\n"; script += " $content = $content -replace ']*>[\\s\\S]*?', ''\n\n"; script += ' # 移除 HTML 標籤,保留文字\n'; script += " $text = $content -replace '<[^>]+>', ' '\n"; script += " $text = $text -replace '\\s+', ' '\n"; script += ' $text = $text.Trim()\n\n'; script += ' # 取前 2000 字元\n'; script += ' if ($text.Length -gt 2000) {\n'; script += ' $text = $text.Substring(0, 2000) + "..."\n'; script += ' }\n\n'; script += ' $report += "' + FENCE + PSN + '$text' + PSN + FENCE + PSN + PSN + '"\n'; script += ' Write-Host "成功抓取 $($text.Length) 字元" -ForegroundColor Green\n\n'; script += ' } catch {\n'; script += ' $report += "錯誤:$($_.Exception.Message)' + PSN + PSN + '"\n'; script += ' Write-Host "抓取失敗: $($_.Exception.Message)" -ForegroundColor Red\n'; script += ' }\n'; script += '}\n\n'; script += '$report | Out-File -FilePath $reportPath -Encoding UTF8\n'; script += 'Write-Host "\\n報告已產生:$reportPath" -ForegroundColor Green\n'; return { skillMd: skillMd, script: script }; } // 排程提醒生成器 function generateScheduler(name, desc) { const content = document.getElementById('reminderContent').value || '這是一則提醒'; const time = document.getElementById('scheduleTime').value || '09:00'; const freq = document.getElementById('scheduleFreq').value; const freqDesc = { 'daily': '每天', 'weekday': '週一到週五', 'weekly': '每週', 'hourly': '每小時' }; const skillMd = '# ' + name + '\n\n' + desc + '\n\n' + '## 提醒內容\n' + content + '\n\n' + '## 排程設定\n' + '- 執行時間:' + time + '\n' + '- 重複頻率:' + freqDesc[freq] + '\n\n' + '## 使用方式\n' + '跟龍蝦說:「設定提醒」或「執行 ' + name + '」\n\n' + '## 手動執行\n' + FENCE + 'powershell\n' + '& "~/.openclaw/workspace/skills/' + name + '/scripts/remind.ps1"\n' + FENCE + '\n'; const script = '# ' + name + ' - ' + desc + '\n' + '# 自動生成於 ' + new Date().toLocaleDateString('zh-TW') + '\n\n' + 'param(\n' + ' [string]$Message = @"\n' + content + '\n' + '"@\n' + ')\n\n' + '$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"\n\n' + 'Write-Host "========================================" -ForegroundColor Cyan\n' + 'Write-Host "⏰ 提醒時間: $timestamp" -ForegroundColor Yellow\n' + 'Write-Host "========================================" -ForegroundColor Cyan\n' + 'Write-Host ""\n' + 'Write-Host $Message -ForegroundColor Green\n' + 'Write-Host ""\n' + 'Write-Host "========================================" -ForegroundColor Cyan\n\n' + '# 可選:發送 Windows 通知\n' + '# [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null\n' + '# 這需要額外的設定,暫時用 Write-Host 顯示\n\n' + '# 記錄到日誌\n' + '$logDir = "$env:USERPROFILE\\.openclaw\\workspace\\logs"\n' + 'if (!(Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force }\n' + '$logFile = "$logDir\\reminders.log"\n' + '"$timestamp - $Message" | Out-File -FilePath $logFile -Append -Encoding UTF8\n'; return { skillMd, script }; } // 檔案整理生成器 function generateFileOrganizer(name, desc) { const source = document.getElementById('sourceFolder').value || '$env:USERPROFILE\\Downloads'; const rule = document.getElementById('organizeRule').value; const moveFiles = document.getElementById('optMoveFiles').checked; const PSN = '`n'; // PowerShell newline const ruleDesc = { 'extension': '依副檔名分類', 'date': '依日期分類', 'size': '依大小分類' }; var skillMd = '# ' + name + '\n\n' + desc + '\n\n'; skillMd += '## 功能\n'; skillMd += '- 整理規則:' + ruleDesc[rule] + '\n'; skillMd += '- 來源資料夾:' + source + '\n'; skillMd += '- 模式:' + (moveFiles ? '移動檔案' : '只產生報告(不移動)') + '\n\n'; skillMd += '## 使用方式\n'; skillMd += '跟龍蝦說:「整理下載資料夾」或「執行 ' + name + '」\n'; var script = '# ' + name + ' - ' + desc + '\n'; script += '# 自動生成於 ' + new Date().toLocaleDateString('zh-TW') + '\n'; script += '# 整理規則:' + ruleDesc[rule] + '\n\n'; script += 'param(\n'; script += ' [string]$SourceFolder = "' + source + '",\n'; script += ' [switch]$MoveFiles = $' + moveFiles + '\n'; script += ')\n\n'; script += '$expandedPath = $ExecutionContext.InvokeCommand.ExpandString($SourceFolder)\n\n'; script += 'if (!(Test-Path $expandedPath)) {\n'; script += ' Write-Host "錯誤:來源資料夾不存在 - $expandedPath" -ForegroundColor Red\n'; script += ' exit 1\n'; script += '}\n\n'; script += 'Write-Host "開始整理: $expandedPath" -ForegroundColor Cyan\n'; script += 'Write-Host "模式: ' + (moveFiles ? '移動檔案' : '只產生報告') + '" -ForegroundColor Yellow\n\n'; script += '$files = Get-ChildItem -Path $expandedPath -File\n\n'; script += '$report = "# 檔案整理報告 - $(Get-Date -Format \'yyyy-MM-dd HH:mm\')' + PSN + PSN + '"\n'; script += '$report += "來源:$expandedPath' + PSN + '"\n'; script += '$report += "檔案數:$($files.Count)' + PSN + PSN + '"\n\n'; if (rule === 'extension') { script += '# 依副檔名分類\n'; script += '$groups = $files | Group-Object Extension\n'; script += 'foreach ($group in $groups | Sort-Object Count -Descending) {\n'; script += ' $ext = if ($group.Name) { $group.Name } else { "無副檔名" }\n'; script += ' $report += "## $ext ($($group.Count) 個檔案)' + PSN + '"\n'; script += ' foreach ($file in $group.Group) {\n'; script += ' $report += "- $($file.Name)' + PSN + '"\n'; script += ' if ($MoveFiles) {\n'; script += " $destDir = Join-Path $expandedPath $ext.TrimStart('.')\n"; script += ' if (!(Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir -Force | Out-Null }\n'; script += ' Move-Item $file.FullName $destDir -Force\n'; script += ' }\n'; script += ' }\n'; script += ' $report += "' + PSN + '"\n'; script += '}\n\n'; } if (rule === 'date') { script += '# 依日期分類\n'; script += '$groups = $files | Group-Object { $_.LastWriteTime.ToString("yyyy-MM") }\n'; script += 'foreach ($group in $groups | Sort-Object Name -Descending) {\n'; script += ' $report += "## $($group.Name) ($($group.Count) 個檔案)' + PSN + '"\n'; script += ' foreach ($file in $group.Group) {\n'; script += ' $report += "- $($file.Name)' + PSN + '"\n'; script += ' if ($MoveFiles) {\n'; script += ' $destDir = Join-Path $expandedPath $group.Name\n'; script += ' if (!(Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir -Force | Out-Null }\n'; script += ' Move-Item $file.FullName $destDir -Force\n'; script += ' }\n'; script += ' }\n'; script += ' $report += "' + PSN + '"\n'; script += '}\n\n'; } if (rule === 'size') { script += '# 依大小分類\n'; script += '$categories = @{\n'; script += ' "01-小檔案(小於1MB)" = { $_.Length -lt 1MB }\n'; script += ' "02-中檔案(1-100MB)" = { $_.Length -ge 1MB -and $_.Length -lt 100MB }\n'; script += ' "03-大檔案(100MB-1GB)" = { $_.Length -ge 100MB -and $_.Length -lt 1GB }\n'; script += ' "04-超大檔案(大於1GB)" = { $_.Length -ge 1GB }\n'; script += '}\n'; script += 'foreach ($cat in $categories.GetEnumerator() | Sort-Object Key) {\n'; script += ' $catFiles = $files | Where-Object $cat.Value\n'; script += ' if ($catFiles.Count -gt 0) {\n'; script += ' $report += "## $($cat.Key) ($($catFiles.Count) 個檔案)' + PSN + '"\n'; script += ' foreach ($file in $catFiles) {\n'; script += ' $sizeMB = [math]::Round($file.Length / 1MB, 2)\n'; script += ' $report += "- [$sizeMB MB] $($file.Name)' + PSN + '"\n'; script += ' if ($MoveFiles) {\n'; script += ' $destDir = Join-Path $expandedPath $cat.Key\n'; script += ' if (!(Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir -Force | Out-Null }\n'; script += ' Move-Item $file.FullName $destDir -Force\n'; script += ' }\n'; script += ' }\n'; script += ' $report += "' + PSN + '"\n'; script += ' }\n'; script += '}\n\n'; } script += '$reportDir = "$env:USERPROFILE\\.openclaw\\workspace\\reports\\' + name + '"\n'; script += 'if (!(Test-Path $reportDir)) { New-Item -ItemType Directory -Path $reportDir -Force }\n'; script += "$reportPath = \"$reportDir\\$(Get-Date -Format 'yyyy-MM-dd').md\"\n"; script += '$report | Out-File -FilePath $reportPath -Encoding UTF8\n\n'; script += 'Write-Host "\\n報告已產生:$reportPath" -ForegroundColor Green\n'; script += 'if ($MoveFiles) { Write-Host "檔案已移動完成!" -ForegroundColor Cyan }\n'; return { skillMd: skillMd, script: script }; } // 自動備份生成器 function generateBackup(name, desc) { const sources = document.getElementById('backupSources').value || '$env:USERPROFILE\\Documents'; const dest = document.getElementById('backupDest').value || 'D:\\Backups'; const mode = document.getElementById('backupMode').value; const modeDesc = { 'copy': '完整複製', 'sync': '同步', 'incremental': '增量備份' }; const sourceList = sources.split('\n').filter(function(s) { return s.trim(); }).map(function(s) { return ' - ' + s.trim(); }).join('\n'); const sourceArray = sources.split('\n').filter(function(s) { return s.trim(); }).map(function(s) { return '"' + s.trim() + '"'; }).join(',\n '); var skillMd = '# ' + name + '\n\n' + desc + '\n\n'; skillMd += '## 設定\n'; skillMd += '- 備份來源:\n'; skillMd += sourceList + '\n'; skillMd += '- 備份目的地:' + dest + '\n'; skillMd += '- 備份模式:' + modeDesc[mode] + '\n\n'; skillMd += '## 使用方式\n'; skillMd += '跟龍蝦說:「執行備份」或「備份我的檔案」\n'; var rcloneCmd = mode === 'sync' ? 'sync' : 'copy'; var script = '# ' + name + ' - ' + desc + '\n'; script += '# 自動生成於 ' + new Date().toLocaleDateString('zh-TW') + '\n'; script += '# 備份模式:' + modeDesc[mode] + '\n\n'; script += 'param(\n'; script += ' [string[]]$Sources = @(\n'; script += ' ' + sourceArray + '\n'; script += ' ),\n'; script += ' [string]$Destination = "' + dest + '"\n'; script += ')\n\n'; script += '$timestamp = Get-Date -Format "yyyy-MM-dd_HHmmss"\n'; script += '$logDir = "$env:USERPROFILE\\.openclaw\\workspace\\logs"\n'; script += 'if (!(Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force }\n'; script += '$logFile = "$logDir\\backup_$timestamp.log"\n\n'; script += 'Write-Host "========================================" -ForegroundColor Cyan\n'; script += 'Write-Host "開始備份 - $timestamp" -ForegroundColor Yellow\n'; script += 'Write-Host "目的地: $Destination" -ForegroundColor Gray\n'; script += 'Write-Host "========================================" -ForegroundColor Cyan\n\n'; script += '"備份開始: $timestamp" | Out-File $logFile -Encoding UTF8\n\n'; script += 'foreach ($source in $Sources) {\n'; script += ' $expandedSource = $ExecutionContext.InvokeCommand.ExpandString($source)\n\n'; script += ' if (!(Test-Path $expandedSource)) {\n'; script += ' Write-Host "跳過(不存在): $expandedSource" -ForegroundColor Yellow\n'; script += ' "跳過: $expandedSource (不存在)" | Out-File $logFile -Append -Encoding UTF8\n'; script += ' continue\n'; script += ' }\n\n'; script += ' $folderName = Split-Path $expandedSource -Leaf\n'; script += ' $destPath = if ($Destination -match ":") {\n'; script += ' # 如果是 rclone remote(如 gdrive:Backups)\n'; script += ' "$Destination/$folderName"\n'; script += ' } else {\n'; script += ' Join-Path $Destination $folderName\n'; script += ' }\n\n'; script += ' Write-Host "備份中: $expandedSource -> $destPath" -ForegroundColor Cyan\n\n'; script += ' if ($Destination -match "^[a-zA-Z]+:") {\n'; script += ' # 使用 rclone(雲端備份)\n'; script += ' rclone ' + rcloneCmd + ' "$expandedSource" "$destPath" --progress\n'; script += ' } else {\n'; script += ' # 本機備份\n'; script += ' if (!(Test-Path $destPath)) { New-Item -ItemType Directory -Path $destPath -Force | Out-Null }\n\n'; script += ' $files = Get-ChildItem -Path $expandedSource -Recurse -File\n'; script += ' $count = 0\n'; script += ' foreach ($file in $files) {\n'; script += ' $relativePath = $file.FullName.Substring($expandedSource.Length)\n'; script += ' $targetPath = Join-Path $destPath $relativePath\n'; script += ' $targetDir = Split-Path $targetPath -Parent\n'; script += ' if (!(Test-Path $targetDir)) { New-Item -ItemType Directory -Path $targetDir -Force | Out-Null }\n'; script += ' Copy-Item $file.FullName $targetPath -Force\n'; script += ' $count++\n'; script += ' }\n'; script += ' Write-Host "已複製 $count 個檔案" -ForegroundColor Green\n'; script += ' }\n\n'; script += ' "完成: $expandedSource -> $destPath" | Out-File $logFile -Append -Encoding UTF8\n'; script += '}\n\n'; script += 'Write-Host "========================================" -ForegroundColor Cyan\n'; script += 'Write-Host "備份完成!" -ForegroundColor Green\n'; script += 'Write-Host "日誌檔: $logFile" -ForegroundColor Gray\n'; script += '"備份完成: $(Get-Date -Format \'yyyy-MM-dd HH:mm:ss\')" | Out-File $logFile -Append -Encoding UTF8\n'; return { skillMd: skillMd, script: script }; } // 自訂 Skill 生成器 function generateCustom(name, desc) { const trigger = document.getElementById('customTrigger').value || '執行我的任務'; const userScript = document.getElementById('customScript').value || 'Write-Host "Hello from my custom skill!"'; const skillMd = '# ' + name + '\n\n' + desc + '\n\n' + '## 觸發指令\n' + '跟龍蝦說:「' + trigger + '」\n\n' + '## 功能說明\n' + '自訂技能,執行以下腳本。\n\n' + '## 使用方式\n' + FENCE + '\n' + trigger + '\n' + FENCE + '\n'; const fullScript = '# ' + name + ' - ' + desc + '\n' + '# 自動生成於 ' + new Date().toLocaleDateString('zh-TW') + '\n' + '# 觸發指令:' + trigger + '\n\n' + userScript + '\n'; return { skillMd, script: fullScript }; } // 下載單一檔案 function downloadFile(type) { if (!generatedFiles[type]) { alert('請先生成 Skill!'); return; } const file = generatedFiles[type]; const blob = new Blob([file.content], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = file.filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } // 下載全部(模擬 ZIP,實際上分別下載) function downloadAll() { // 由於純前端難以建立真正的 ZIP,改為逐一下載 downloadFile('skill-md'); setTimeout(() => downloadFile('script'), 500); setTimeout(() => downloadFile('install'), 1000); } // 初始化 updateDefaultName();