지난 글 Opencode 사용후기 및 사용방법 정리에 이어, 이번에는 직접 Opencode 플러그인을 개발해본 경험을 공유합니다.
Opencode 플러그인이란?
Opencode 플러그인은 JavaScript/TypeScript 코드로, 이벤트 훅(hook)을 기반으로 Opencode의 동작을 확장할 수 있는 기능입니다.
마치 브라우저 확장 프로그램을 만들듯이, Opencode에 대한 확장 프로그램을 개발할 수 있습니다.
export const MyPlugin: Plugin = async ({ client, $, directory }) => {
return {
"tool.execute.before": async (input, output) => {
/* 도구 실행 전 가로채기 */
},
tool: {
mytool: tool({
description: "...",
args: {...},
execute: async (args) => "결과"
})
}
}
}Claude Code Plugin과의 차이점
Claude Code의 경우 /plugin , /marketplace 통해 .claude/ 디렉토리의 설정들을 외부와 공유할 수 있습니다. 반면 Opencode는 모든 이벤트에 대한 접근과 컨트롤이 가능하기 때문에, 훨씬 더 자유롭게 동작을 커스터마이징할 수 있다는 차이점이 있습니다.
사내에 배포 가능한 플러그인 만들기
이번 플러그인의 주요 사용 대상은 비개발자 팀원들입니다.
AI Agent에 익숙하지 않은 사람도 쉽게 사용할 수 있고, 공유된 스킬/MCP에 어떤 기능이 있는지 일일이 찾아보지 않아도 바로 활용할 수 있게 만들고 싶었습니다.
플러그인 배포 환경 구축
Opencode 플러그인은 다른 JS/Node 프로젝트처럼 npm으로 패키지 설치 가 가능합니다. 하지만 사내 이용 목적이기에 당장은 public 배포가 어려웠습니다. 그래서 Private NPM Registry를 직접 호스팅하기로 했습니다.
https://verdaccio.gpters.org/ 에 NPM registry를 세팅하고, GitHub Action에서 자동으로 배포되도록 CI/CD를 구성하였습니다
공유된 플러그인을 설치하는건 opencode.json 파일을 수정하는 것으로 가능합니다.
(*전역설치 = ~/.config/opencode/opencode.json)
{
"plugin": [
"oh-my-opencode",
"[email protected]",
"opencode-openai-codex-auth",
"@gpters-internal/opencode@latest"
],
...
}따라서 쉽게 설치할 수 있도록 다음과 같은 스크립트를 만들어 공유하였습니다:
grep -q "verdaccio.gpters.org" ~/.opencode/.npmrc 2>/dev/null || echo '@gpters-internal:registry=https://verdaccio.gpters.org//verdaccio.gpters.org/:_authToken=<>' >> ~/.opencode/.npmrc && \
[ ! -f ~/.opencode/opencode.json ] && echo '{"$schema":"https://opencode.ai/config.json","plugin":[]}' > ~/.opencode/opencode.json; \
bun -e "const fs=require('fs'),f=process.env.HOME+'/.opencode/opencode.json',c=JSON.parse(fs.readFileSync(f,'utf8'));c.plugin=c.plugin||[];c.plugin.includes('@gpters-internal/opencode')||c.plugin.push('@gpters-internal/opencode@latest');fs.writeFileSync(f,JSON.stringify(c,null,2))"플러그인 자동 업데이트
공식적으로 자동 업데이트에 대해 문서에 언급된 내용이 없어, 처음에는 npm 캐시를 지우고 업데이트하는 로직을 별도로 구현했습니다.
하지만 알고 보니 @latest 태그만 달아두면 항상 최신 버전으로 유지된다는 것을 알게 됐습니다. 관련 내용은 GitHub Issue에서도 논의된 바 있습니다.
Git 관련 기능: 비개발자를 위한 자동화
비개발자들이 처음에 어려워하는 개념 중 하나가 바로 Git입니다. 언제 커밋을 해야 하고, 브랜치를 어떻게 사용해야 하는지에 대한 개념이 모호하기 때문입니다. 그래서 따로 신경 쓰지 않더라도 자동으로 브랜치를 만들어주고 커밋을 해주는 기능을 플러그인으로 개발했습니다.
Branch Guard
main 브랜치에서 작업을 시작하면, 자동으로 새 브랜치를 생성하고 그쪽으로 이동시켜서 작업을 진행하게 합니다. 이후 push하고 GitHub에서 머지가 완료되면, 자동으로 main 브랜치로 checkout 시켜줍니다.
Auto Commit
자동 커밋은 다음 조건에서 동작합니다:
TODO가 모두 완료되었을 때
TODO가 없는 상황에서 파일 수정이 발생했을 때
subagent가 빠른 모델을 이용해 현재 세션의 diff만 커밋
/gpters:commit명령 시에는 모든 변경사항을 커밋
생각보다 편해서 계속 잘 사용할 예정입니다.
동작 예시)
MCP와 유용한 Slash 커맨드 추가
MCP 추가하기
플러그인 개발 시 다음과 같이 config 객체를 수정하는 것으로 MCP를 추가할 수 있습니다:
config: async (config) => {
config.mcp ??= {}
config.mcp = {
"gpters-ai-toolkit": {
enabled: true,
type: "remote",
url: "https://ai-toolkit.gpters.org/api/mcp",
oauth: {}
}
}
}아래에서 설명할 GPTers Marketplace 플러그인, 팀원들이 많 이 사용하는 Linear MCP 등을 기본으로 추가해 두었습니다.
플러그인으로 설치한 MCP는 로그인이 안 된다?
여기서 한 가지 문제에 부딪혔습니다. MCP OAuth 로그인은 터미널에서 opencode mcp auth 명령어를 통해 진행하는데, 이 시점에는 플러그인이 아직 로드되지 않은 상태라 로그인이 불가능했습니다.
해결책으로, 플러그인 내부에서 API를 통해 로그인 호출이 가능하다는 점을 활용해 /gpters:plugin-setup이라는 커스텀 커맨드를 만들었습니다:
const authRes = await client.mcp.auth.authenticate({
path: { name: MCP_NAME },
})솔직히 이 접근법이 맞는 건지는 잘 모르겠습니다. ㅋㅋㅋ 하지만 일단 동작은 잘 합니다.
추가한 커맨드들
각종 슬래시 커맨드들도 MCP오 동일하게 config 객체를 수정하는 것으로 추가할 수 있습니다:
config: async (config) => {
command ??= {}
config.command[`${COMMAND_PREFIX}:prd-review`] = COMMAND_PRD_REVIEW
config.command[`${COMMAND_PREFIX}:plugin-setup`] = COMMAND_PLUGIN_SETUP
config.command[`${COMMAND_PREFIX}:commit`] = COMMAND_COMMIT
config.command[`${COMMAND_PREFIX}:git-push-pr`] = COMMAND_GIT_PUSH_PR
config.agent ??= {}
config.agent[COMMIT_AGENT_NAME] = COMMIT_AGENT_CONFIG
config.agent[GIT_PUSH_PR_AGENT_NAME] = GIT_PUSH_PR_AGENT_CONFIG
}결국 Opencode 플러그인은 모든 설정을 비 동기적으로 불러올 수 있기 때문에,
후술할 마켓플레이스와 연동하여 현재 세션과 연관된 커맨드만 동적으로 가져오는 것도 추후 해볼만하다 생각하였습니다.
사내 스킬 공유 기능
저희 팀에는 이런 MCP가 있습니다:
마켓플레이스 등록만 하면 Claude Code가 알아서 필요한 스킬을 찾아서 쓰게 할 수 없을까? https://www.gpters.org/mcp-43q62kh1/post/creating-ai-skill-hub-D3MBVfbSHoXveNO
하지만 단순 MCP만으로는 한계가 있었습니다. 매번 list하는 데 시간도 오래 걸리고, 필요한 스킬을 못 찾는 경우도 많이 발생했습니다.
그래서 플러그인을 통해 이를 개선해보고자 하였습니다.
시도 1: System Prompt만으로 컨트롤하기
제일 단순한 방법이라 처음 시도해 보았습니다. 초반에는 잘 따랐지만, context window가 커짐에 따라 시스템 프롬프트에 넣어둔 규칙을 점점 무시하게 되는 문제가 발생했습니다. 계속 리마인드를 해주지 않으면 잘 찾지 못하게 되더군요.
시도 2: 키워드 트리거
단순한 접근 방식으로, uss(Use Shared Skills)라는 텍스트가 포함되면 스킬을 검색하게 만들어봤습니다.
음... 이러면 아무도 안 쓸 것 같았습니다. 최초의 목표와 전혀 얼라인되지 않는 방식이었죠.
시도 3: Hook으로 매번 실행하기
이번에는 hook을 활용해 매번 스킬을 검색하게 해봤습니다. 하지만 여러 문제가 있었습니다:
시간이 너무 오래 걸림 (리스트업 후 표시까지 최소 20~30초)
같이 사용하고 있는
Oh my opencode플러그인과 호환되지 않음 (자체적인 task tool이 있어서 충돌)Oh my opencode 플러그인을 포크해서 구축하자니 라이선스 문제로 불가능
최종 해결책: 유사한 스킬을 검색하여 시스템 프롬프트에 포함
결국 AI Agent의 스킬 동작 방식을 이해하고, 그에 맞는 접근법을 찾았습니다.
일반적인 AI Agent의 SKILL 동작 방식
"스킬"이라고 하면 굉장히 거창해 보이지만, 사실 별거 없습니다. 시스템 프롬프트의 Tool description에 스킬 이름과 설명이 포함되어 있고, AI가 필요하다고 판단하면 해당 스킬을 로드하는 방식입니다.
예를 들어, 사용자가 react-best-practices라는 스킬을 설치해둔 상황이라면:
Tool name: mcp_skill
Description:
<available_skills>
<skill>
<name>react-best-practices</name>
<description>React and Next.js performance optimization guidelines.</description>
</skill>
</available_skills>
Parameters:
• name (required): string - The skill nameAI가 위 tool(스킬)이 필요하다고 느끼는 시점에 mcp_skill(name="react-best-practices")를 실행하여 스킬 내용을 컨텍스트로 가져오게 됩니다.
Plugin에서의 구현
비슷한 접근을 하되, 조금 더 SKILL 을 잘 찾을 수 있도록 구현을 하였습니다.
GPTers 마켓플레이스의 스킬들을 벡터 인덱싱
AI에게 채팅을 입력할 때마다 전체 프롬프트를 기준으로 유사도 검사
연관성 높은 5개 결과를 시스템 프롬프트에 포함
AI 가 그중 필요하다고 생각하는 스킬에 대해 자동으로 로드
시스템 프롬프트 추가 예시:
<available-skills>
## Available Skills
Based on your request, these skills may be helpful:
${skillLines}
**How to use**: If any skill seems relevant, load it using \`mcp_gpters-ai-toolkit_get_plugin_content(pluginId="<skill-id>")\` to get detailed instructions.
</available-skills>*Opencode의 시스템 프롬프트 커스텀은 대략 아래와 같은 코드로 가능합니다:
"experimental.chat.system.transform": async (...) => {
...
const cached = sessionSkillCache.get(input.sessionID)
const skillsPrompt = formatAvailableSkillsPrompt(cached.skills)
if (skillsPrompt) {
output.system.push(skillsPrompt)
logger.info(`Injected ${cached.skills.length} skills into system prompt`)
}
},이후 스킬은 다음과 같이 동작하게 됩니다:
필요할 만한 스킬만 컨텍스트에 담아두기 때문에, 여러 번 테스트한 결과 필요한 스킬들을 잘 찾아내는 것을 확인할 수 있었습니다. 한 번에 목록에 표시하는 스킬은 5개로 제한되어 있어서, 팀에서 공유하는 스킬이 수백, 수천 개가 되더라도 부담 없이 동작합니다.
후기
Agent의 작동 원리를 그동안은 막연하게만 알고 있었는데, 이번에 직접 이해하고 커스텀까지 해볼 수 있어서 좋았습니다. 다만 늘 그렇듯 이런 종류의 오픈소스 프로젝트는 문서화가 잘 되어있지 않았던 부분이 아쉬웠습니다. 내장된 기능이나 복잡한 라이프사이클 같은 핵심 내용들이 공식 문서(opencode.ai/docs/)에는 충분히 반영되어 있지 않더라고요.
한편으로는 이런 Agent AI를 다루는 게 확실히 쉽지 않다는 생각도 들었습니다. AI 동작을 정말 다양한 방식으로 커스텀할 수 있다 보니 구현할 수 있는 선택지가 많았습니다. 어떤 건 프롬프트 만으로도 해결이 가능하고, 어떤 건 hook으로 강제해야 잘 동작하고, 경우에 따라서는 아예 로직으로 작성해야지만 잘 동작하였습니다. 다룰 수 있는 방법이 많아질수록 오히려 더 어렵게 느껴지는 순간들이 있었고, 결국 그 중간 어디쯤에서 “지금 상황에 가장 적절한 해결책이 뭔지”를 찾아내는게 중요한 것 같습니다.