import type { Plugin } from "@opencode-ai/plugin"
import * as fs from "fs"
import * as path from "path"
import crypto from "crypto"
const logFile = path.join(__dirname, "install_error.log")
function log(message: string) {
const timestamp = new Date().toISOString()
fs.appendFileSync(logFile, `[${timestamp}] ${message}\n`)
}
function getFileHash(filePath: string): string | null {
if (!fs.existsSync(filePath)) return null
try {
const content = fs.readFileSync(filePath, 'utf-8')
return crypto.createHash('md5').update(content).digest('hex')
} catch (error) {
return null
}
}
interface SkillState {
name: string
exists: boolean
hash: string | null
}
function findGitRoot(startDir: string): string {
let dir = startDir
while (dir !== path.dirname(dir)) {
if (fs.existsSync(path.join(dir, ".git"))) {
return dir
}
dir = path.dirname(dir)
}
return startDir
}
export const InstallSkillsPlugin: Plugin = async ({ $, directory }) => {
const rootDir = findGitRoot(directory)
const installSkills = async () => {
try {
const skillsToCheck = [
{ name: 'gitcode-pr', path: path.join(rootDir, ".claude", "skills", "gitcode-pr", "SKILL.md") },
{ name: 'gitcode-issue', path: path.join(rootDir, ".claude", "skills", "gitcode-issue", "SKILL.md") }
]
const beforeStates: SkillState[] = skillsToCheck.map(skill => ({
name: skill.name,
exists: fs.existsSync(skill.path),
hash: getFileHash(skill.path)
}))
const isWindows = process.platform === 'win32'
if (isWindows) {
process.stdout.write(`💡 提示:如果需要安装或更新默认skill,请输入指令"安装默认skill"\n\n`)
return
}
const scriptPath = path.join(rootDir, ".claude", "skills", "default-skills", "scripts", "install-default-skills.sh")
await $`bash ${scriptPath} > /dev/null`
const afterStates: SkillState[] = skillsToCheck.map(skill => ({
name: skill.name,
exists: fs.existsSync(skill.path),
hash: getFileHash(skill.path)
}))
let hasChanges = false
const changedSkills: string[] = []
for (let i = 0; i < beforeStates.length; i++) {
const before = beforeStates[i]
const after = afterStates[i]
if (!before.exists && after.exists) {
hasChanges = true
changedSkills.push(`${before.name} 新安装`)
} else if (before.exists && after.exists && before.hash !== after.hash) {
hasChanges = true
changedSkills.push(`${before.name} 已更新`)
}
}
if (hasChanges && changedSkills.length > 0) {
setTimeout(() => {
process.stdout.write(`💡 ${changedSkills.join(', ')},重启opencode才能完全生效\n\n`)
}, 1000)
}
} catch (error) {
log(`Command failed: ${error.message}`)
if (error.stderr) log(`stderr from error: ${error.stderr}`)
const errorMarkerPath = path.join(rootDir, ".opencode_skills_error")
const detail = error.stderr ? `${error.message}\n${error.stderr}` : error.message
const errorMessage = `❌ 安装默认技能时出错了,请输入指令"安装默认skill"重新安装\n错误详情: ${detail}\n`
fs.writeFileSync(errorMarkerPath, errorMessage)
setTimeout(() => {
process.stdout.write(`❌ 安装默认技能时出错了,请输入指令"安装默认skill"重新安装\n`)
process.stdout.write(` 错误详情: ${detail}\n`)
process.stdout.write(` 错误详情请查看: ${errorMarkerPath}\n\n`)
}, 2000)
}
};
installSkills();
return {
event: async ({ event }) => {}
}
}