mirror of
https://github.com/jnMetaCode/superpowers-zh.git
synced 2026-09-02 22:54:06 +08:00
上游同步: - hooks/session-start: 新增 Copilot CLI 平台检测(COPILOT_CLI 环境变量) - .opencode/plugins/superpowers.js: bootstrap 从 system prompt 改为 user message 注入,避免 token 膨胀和 Qwen 兼容问题 - skills/using-superpowers: 新增 Copilot CLI 使用说明和工具映射引用 - 新增 references/copilot-tools.md(中文化) 本地优化: - 清理 OpenCode 插件死代码(normalizePath/os/configDir) - 统一版本号为 1.1.6(修复插件 JSON 与 package.json 不一致) - .gitignore 排除安装器生成的 skills 副本 - package.json files 数组精简,避免打包重复的 skills 副本 - 安装器新增 copilot/copilot-cli 别名 - 工具数量更新为 15 款
96 lines
3.6 KiB
JavaScript
96 lines
3.6 KiB
JavaScript
/**
|
|
* Superpowers plugin for OpenCode.ai
|
|
*
|
|
* Injects superpowers bootstrap context via user message transform.
|
|
* Auto-registers skills directory via config hook (no symlinks needed).
|
|
*/
|
|
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
// Simple frontmatter extraction (avoid dependency on skills-core for bootstrap)
|
|
const extractAndStripFrontmatter = (content) => {
|
|
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
if (!match) return { frontmatter: {}, content };
|
|
|
|
const frontmatterStr = match[1];
|
|
const body = match[2];
|
|
const frontmatter = {};
|
|
|
|
for (const line of frontmatterStr.split('\n')) {
|
|
const colonIdx = line.indexOf(':');
|
|
if (colonIdx > 0) {
|
|
const key = line.slice(0, colonIdx).trim();
|
|
const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, '');
|
|
frontmatter[key] = value;
|
|
}
|
|
}
|
|
|
|
return { frontmatter, content: body };
|
|
};
|
|
|
|
export const SuperpowersPlugin = async ({ client, directory }) => {
|
|
const superpowersSkillsDir = path.resolve(__dirname, '../../skills');
|
|
|
|
// Helper to generate bootstrap content
|
|
const getBootstrapContent = () => {
|
|
// Try to load using-superpowers skill
|
|
const skillPath = path.join(superpowersSkillsDir, 'using-superpowers', 'SKILL.md');
|
|
if (!fs.existsSync(skillPath)) return null;
|
|
|
|
const fullContent = fs.readFileSync(skillPath, 'utf8');
|
|
const { content } = extractAndStripFrontmatter(fullContent);
|
|
|
|
const toolMapping = `**Tool Mapping for OpenCode:**
|
|
When skills reference tools you don't have, substitute OpenCode equivalents:
|
|
- \`TodoWrite\` → \`todowrite\`
|
|
- \`Task\` tool with subagents → Use OpenCode's subagent system (@mention)
|
|
- \`Skill\` tool → OpenCode's native \`skill\` tool
|
|
- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Your native tools
|
|
|
|
Use OpenCode's native \`skill\` tool to list and load skills.`;
|
|
|
|
return `<EXTREMELY_IMPORTANT>
|
|
You have superpowers.
|
|
|
|
**IMPORTANT: The using-superpowers skill content is included below. It is ALREADY LOADED - you are currently following it. Do NOT use the skill tool to load "using-superpowers" again - that would be redundant.**
|
|
|
|
${content}
|
|
|
|
${toolMapping}
|
|
</EXTREMELY_IMPORTANT>`;
|
|
};
|
|
|
|
return {
|
|
// Inject skills path into live config so OpenCode discovers superpowers skills
|
|
// without requiring manual symlinks or config file edits.
|
|
// This works because Config.get() returns a cached singleton — modifications
|
|
// here are visible when skills are lazily discovered later.
|
|
config: async (config) => {
|
|
config.skills = config.skills || {};
|
|
config.skills.paths = config.skills.paths || [];
|
|
if (!config.skills.paths.includes(superpowersSkillsDir)) {
|
|
config.skills.paths.push(superpowersSkillsDir);
|
|
}
|
|
},
|
|
|
|
// Inject bootstrap into the first user message of each session.
|
|
// Using a user message instead of a system message avoids:
|
|
// 1. Token bloat from system messages repeated every turn (#750)
|
|
// 2. Multiple system messages breaking Qwen and other models (#894)
|
|
'experimental.chat.messages.transform': async (_input, output) => {
|
|
const bootstrap = getBootstrapContent();
|
|
if (!bootstrap || !output.messages.length) return;
|
|
const firstUser = output.messages.find(m => m.info.role === 'user');
|
|
if (!firstUser || !firstUser.parts.length) return;
|
|
// Only inject once
|
|
if (firstUser.parts.some(p => p.type === 'text' && p.text.includes('EXTREMELY_IMPORTANT'))) return;
|
|
const ref = firstUser.parts[0];
|
|
firstUser.parts.unshift({ ...ref, type: 'text', text: bootstrap });
|
|
}
|
|
};
|
|
};
|