컨텐츠로 건너뛰기
이동 열기 esc닫기

확장 만들기

@openchamber/sdk를 사용하면 OpenChamber에 나만의 패널을 추가할 수 있습니다. 확장은 OpenChamber가 오른쪽 레일에 표시하는 작은 웹 페이지입니다. 앱과는 connectHost()로 대화합니다. 현재 프로젝트와 세션 읽기, 토스트 표시, 채팅창에 텍스트 넣기, 작업을 세션에 첨부하기, 그리고 사용자가 승인하면 세션 시작과 프롬프트 전송까지 할 수 있습니다.

확장은 OpenChamber 웹과 데스크톱에서 실행됩니다. VS Code와 모바일은 아직 확장을 로드하지 않습니다.

확장이란

파일 세 개가 들어 있는 폴더입니다.

  • openchamber 블록(매니페스트)이 있는 package.json
  • panel/index.html, OpenChamber가 표시하는 페이지
  • panel/main.js, 여러분의 스크립트. 단일 classic 파일(ES 모듈이 아닌 IIFE)로 빌드한 것입니다. 페이지가 샌드박스 iframe에서 로드되기 때문입니다

OpenChamber는 여러분의 코드를 컴파일하지 않습니다. 빌드된 .js 파일을 함께 배포하세요. SDK에는 번들러 명령이 포함되어 있습니다. IIFE를 출력하는 번들러라면 어떤 것이든 됩니다.

Terminal window
npm install @openchamber/sdk
bunx openchamber-guest-bundle panel/main.ts panel/main.js

이 번들러 명령은 Bun에서 실행됩니다. Bun이 없다면 esbuild나 다른 번들러를 --format=iife --platform=browser 옵션으로 사용하세요.

파일 세 개가 모두 들어 있는 완전히 동작하는 확장은 예제 페이지에 있습니다. 복사한 다음 API와 목록만 바꾸세요.

index.html에서는 일반적인 <script src="main.js"></script>main.js를 불러옵니다.

작업하면서 설치하기

  1. OpenChamber를 웹이나 데스크톱에서 실행합니다.
  2. 설정 → 확장 을 엽니다.
  3. 폴더의 절대 경로를 붙여넣고 추가를 클릭합니다.

OpenChamber가 매니페스트를 읽고, 확장이 요구하는 항목을 나열한 대화상자를 보여줍니다(권한 참고). 승인하면 레일에 아이콘이 나타납니다. 클릭하면 페이지가 로드됩니다.

로컬 .zip 파일이나 git 저장소 또는 zip 파일의 https 링크도 추가할 수 있습니다. 업데이트할 수 있는 것은 git 설치뿐입니다. package.jsonversion을 올리고 푸시하면, 사용자가 다음에 설정 → 확장 을 열 때 OpenChamber가 업데이트를 제안합니다. URL의 #tag#branch로 무엇을 따라갈지 고정할 수 있습니다. 이들은 OpenChamber 데이터 폴더로 복사되어 그 복사본에서 실행되므로, node_modules나 TypeScript 소스가 아니라 빌드된 파일을 배포하세요. 확장을 제거하면 복사본도 삭제됩니다. 폴더 설치는 여러분의 폴더에서 직접 실행되므로 수정, 재빌드, 새로고침이 가능합니다.

매니페스트

{
"name": "@acme/hello",
"version": "1.0.0",
"openchamber": {
"apiVersion": 1,
"engines": { "openchamber": ">=1.24.0" },
"contributes": {
"panel": {
"id": "acme-hello",
"name": "Hello",
"icon": "window",
"entry": "panel/index.html"
},
"attach": "dialog",
"page": true,
"capabilities": ["prompt", "sessions"],
"integration": {
"name": "Acme",
"description": "Tasks from Acme",
"token": {
"apiOrigin": "https://api.acme.example",
"account": { "path": "/me", "name": "login" },
"scheme": "bearer"
},
"settings": [{ "id": "list-id", "label": "List ID" }]
}
}
}
}

version은 필수이며 semver여야 합니다. 설정 → 확장 의 카드에 표시됩니다.

apiVersion1입니다. OpenChamber는 다른 값을 거부합니다.

engines.openchamber는 선택 사항입니다. 확장이 동작하는 가장 오래된 OpenChamber 버전을 1.24.0 또는 >=1.24.0 형태로 지정하세요. 더 오래된 빌드는 나중에 실패하는 대신 설치 자체를 거부합니다.

contributes.panel은 레일 항목을 설명합니다. id는 kebab-case이며 설치된 확장 중에서 고유해야 합니다. icon은 Remixicon 이름(RiWindowLine이면 window)이거나 폴더 안의 SVG 파일(예: icon.svg)입니다. entry는 폴더 안의 HTML 파일입니다. tools만 선언하는 확장이라면 생략하세요(채팅에 표시되는 내 도구 참고).

contributes.attach는 선택 사항입니다. 채팅창 옆의 + 메뉴에 확장을 추가해서, 사용자가 작업을 골라 세션에 첨부할 수 있게 합니다.

  • "dialog"는 페이지를 창으로 엽니다
  • true 또는 "panel"은 대신 레일 패널을 엽니다
  • { "mode": "dialog", "entry": "panel/attach.html" }은 여러분의 별도 페이지를 창으로 열어서, 선택기가 레일 패널과 코드를 공유하지 않아도 되게 합니다
  • 생략하면 확장은 그 메뉴에 나타나지 않습니다

ctx.surfacepanel, dialog, page 중 하나입니다. 첨부 항목을 클릭하면 해당 데이터가 ctx.item에 들어가며, 그 외에는 null입니다.

contributes.page: true를 지정하면 세션 목록 위의 확장 프로그램 페이지 메뉴에서 전체 화면으로 열 수 있습니다. { "entry": "panel/page.html", "title": "Board" }로 별도의 HTML과 선택적 제목을 지정합니다. panel.entry는 필수이며 샌드박스와 권한은 같습니다. 사용자만 페이지를 열 수 있습니다. 새로고침, 일시 중지, 제거, 서버 전환 시 닫힙니다.

보드에는 host.storage와 프로젝트, worktree, 세션 목록 및 실시간 상태를 사용할 수 있습니다. startSession은 보드를 닫지 않고 다른 프로젝트를 지정합니다. Host API를 참고하세요.

contributes.integration은 선택 사항입니다. 설정 → 통합 에 카드를 추가하고, 사용자는 거기서 계정을 연결합니다. 계정과 네트워크를 참고하세요.

contributes.service는 선택 사항입니다. Docker 소켓처럼 웹 페이지가 닿을 수 없는 것을 위해, OpenChamber가 확장 옆에서 시작하는 로컬 프로세스를 선언합니다. 그 프로세스는 사용자의 전체 권한으로, 샌드박스 없이 실행되므로 승인 대화상자가 이를 경고합니다. 페이지로 처리할 수 없을 때만 선언하세요. 패키지 파일 GUEST_SERVICES.md를 참고하세요. provides: ["browser"]를 가진 서비스는 에이전트의 브라우저를 대신할 수도 있습니다. 서버에서 browser.* 동작에 응답하므로 데스크톱 앱을 열지 않아도 에이전트가 탐색할 수 있고, 사용자는 설정 → OpenChamber 도구에서 선택합니다. 이런 서비스에는 패널이 필요 없습니다.

권한

패널을 그리는 것과 현재 세션을 읽는 것에는 권한이 필요 없습니다. 사용자를 대신해 무언가를 하는 것에는 필요합니다. 그런 것들을 contributes.capabilities에 나열하세요.

권한허용되는 것
prompt사용자의 세션에 메시지 보내기(prompt({ send: true }), text가 있는 startSession)
sessions프로젝트, worktree, 세션 상태 조회 및 등록된 프로젝트에서 세션 생성과 열기
files열린 프로젝트 안의 파일 읽고 쓰기(상대 경로를 쓰는 readFile, writeFile, listDir, stat)
model세션 밖에서 사용자의 Small Model로 일회성 텍스트 생성(generate)

네 가지가 자동으로 추가됩니다. integration을 선언하면 network, service를 선언하면 service, filesystem 패턴을 선언하면 filesystem, 세션 작업이 메시지를 요청하면 conversation입니다(작업, 명령, 배지 참고).

사용자는 확장을 설치할 때 전체 목록을 한 번 보고, 승인하거나 확장을 제거합니다. 새 버전이 더 많은 권한을 요구하면 대화상자가 다시 나타납니다. 사용자가 승인하지 않은 권한이 필요한 호출은 NOT_GRANTED로 실패합니다.

텍스트 생성

model 권한이 있으면 host.generate는 사용자의 Small Model에 요약, 제목, 초안 같은 일회성 답변을 요청합니다. 세션에는 아무것도 들어가지 않고 기록도 남지 않으며, 확장이 프로바이더를 고르는 일도 없습니다. OpenChamber는 자신의 백그라운드 작업에 쓰는 것과 같은 모델을 사용합니다. 설정 → 세션 → Small Model 에서 고르거나, 사용자가 로그인한 프로바이더 중에서 자동으로 선택됩니다.

const { text } = await host.generate({
prompt: task.description,
system: "Write a one-line summary. Return only the summary.",
maxOutputTokens: 200,
});

사용할 수 있는 모델이 없으면 호출은 NO_MODEL로 실패하고, 모델이 오류를 내면 MODEL_FAILED입니다. 프롬프트는 최대 64,000자이며, 호출은 최대 90초까지 기다립니다. 사용자에게는 이 권한이 “Small Model 사용”으로 표시되고 토큰을 사용하므로, 프롬프트는 짧게 유지하고 키 입력마다가 아니라 클릭할 때 호출하세요.

계정과 네트워크

페이지는 샌드박스에서 실행되며 인터넷을 직접 호출할 수 없습니다. integration을 선언하면 OpenChamber가 host.request를 통해 사용자의 토큰을 붙여 대신 호출합니다. 토큰은 절대 페이지에 도달하지 않습니다.

  • token: 사용자가 설정 → 통합 카드에 API 토큰을 붙여넣습니다. apiOriginrequest가 호출할 수 있는 유일한 origin입니다. account는 선택 사항으로, GET 경로와 필드 이름을 지정하면 카드에 누가 연결되어 있는지 표시할 수 있습니다. scheme은 토큰을 보내는 방식을 지정합니다. 아래 표를 참고하세요. 어떤 헤더를 기대하는지는 프로바이더의 API 문서에서 확인하세요.
  • oauth: 사용자가 클라이언트 ID를 붙여넣고, OpenChamber가 인증 흐름을 실행합니다. authorizeUrl, tokenUrl, apiOrigin이 필요합니다.
  • host: { "provider": "linear" }: OpenChamber에 이미 연결된 Linear 계정을 재사용합니다. 클라이언트 ID가 필요 없습니다.

scheme이 보내는 것:

schemeOpenChamber가 보내는 헤더사용 시점
생략Authorization: <token>API가 헤더에 접두어 없는 토큰을 문서화한 경우
"bearer"Authorization: Bearer <token>API가 bearer 토큰 또는 personal access token을 문서화한 경우
"basic"Authorization: Basic base64(username:token)API가 사용자 이름(흔히 이메일)과 API 토큰을 쓰는 HTTP Basic 인증을 문서화한 경우. 카드는 둘 다 묻고, usernameLabel이 첫 번째 입력란의 이름이 됩니다

settings는 카드에 일반 텍스트 입력란을 추가합니다. 그 값은 ctx.settings로 전달됩니다.

파일

페이지 자체는 디스크를 건드릴 수 없습니다. OpenChamber가 사용자가 승인한 범위 안에서 대신 읽고 씁니다.

  • 상대 경로(README.md, src/index.ts, .)는 열린 프로젝트를 뜻합니다. files 권한이 필요합니다. 열린 프로젝트가 없으면 NO_DIRECTORY입니다.
  • / 또는 ~/로 시작하는 경로는 그 밖의 어디든 뜻합니다. contributes.filesystem에 선언한 패턴 중 하나와 일치해야 하며, 사용자는 승인 대화상자에서 그 패턴을 그대로 봅니다:
"filesystem": ["~/.config/opencode/opencode.json", "~/notes/**"]

패턴에서 *는 경로 세그먼트 하나, **는 임의의 깊이를 뜻합니다. 패턴 밖의 것은 승인 후에도 BAD_PATH입니다. ..는 절대 허용되지 않습니다.

const config = await host.readFile("~/.config/opencode/opencode.json");
await host.writeFile("~/.config/opencode/opencode.json", nextJson);
const { entries } = await host.listDir(".");

쓰기는 원자적입니다. OpenChamber가 임시 파일에 쓴 뒤 이름을 바꾸므로, 읽는 쪽이 반쯤 쓰인 파일을 보는 일은 없습니다. 파일은 UTF-8 텍스트로 읽고 쓰며, 최대 2 MB입니다.

작업, 명령, 배지

레일과 + 메뉴 외에도 확장은 세 곳에 더 나타날 수 있습니다. 모두 칩을 클릭했을 때와 같은 방식으로 확장에 item을 넘깁니다.

메시지와 세션의 작업. 메뉴 항목을 선언하면 OpenChamber가 기본 항목 옆에 보여줍니다.

"actions": [
{ "id": "create-task", "label": "Create task", "icon": "add-line", "where": "message", "roles": ["assistant"] },
{ "id": "summarize", "label": "Summarize session", "where": "session", "payload": ["messages"] }
]

메시지 작업은 kind: "message"ctx.item과 함께 확장을 엽니다. 세션 id와 제목, 프로젝트 폴더, 메시지 id, 역할, 텍스트가 들어 있습니다. 세션 작업은 kind: "session"과 세션 id, 제목, 폴더와 함께 엽니다. "payload": ["messages"]를 추가하면 항목에 대화 전체가 오래된 순서로 담기며, Markdown 내보내기가 만드는 것과 같은 텍스트입니다. 여기에는 conversation 권한이 필요하고, 사용자는 승인 대화 상자에서 이를 별도 줄로 봅니다. item.action은 어떤 항목을 클릭했는지 알려줍니다.

첨부하는 슬래시 명령. 명령을 선언하고 페이지에서 처리합니다.

"commands": [{ "name": "task", "description": "Attach a task by id" }]
host.onResolve(async ({ command, args }) => {
const task = await findTask(args.trim());
return task ? { providerId: "acme-tasks", id: task.id, title: task.title, url: task.url } : null;
});

사용자가 채팅창에 /task ABC-12를 입력하면 OpenChamber는 확장에 해석을 요청하고, 돌려준 것을 아무것도 열지 않은 채 칩으로 첨부합니다. 찾은 것이 없으면 null을 반환하세요. 패널이 닫혀 있으면 OpenChamber가 이 호출을 위해 백그라운드에서 패널을 불러옵니다. OpenChamber나 OpenCode가 이미 쓰는 이름은 무시됩니다.

레일 아이콘의 배지. host.setBadge(3)은 아이콘에 숫자(예: 열린 작업 수)를 표시하고, host.setBadge(null)은 지웁니다. 패널을 열어도 지워집니다.

채팅에 표시되는 내 도구

OpenCode 플러그인이나 MCP 서버가 도구를 추가하면 채팅은 그 호출을 일반 아이콘과 원본 출력으로 보여줍니다. 대신 코드 없이 어떻게 보일지 선언하세요.

"tools": [
{
"match": "mcp.jira.*",
"name": "Jira",
"icon": "task-line",
"title": "{input.key}",
"subtitle": "{output.status}",
"output": "table",
"columns": ["key", "summary", "status"]
}
]

match는 OpenCode가 보고하는 도구 이름이며, 끝의 *는 접두사와 일치합니다. icon은 Remixicon 이름이거나 패키지 안의 .svg 파일이며, panel.icon과 같습니다. titlesubtitle은 호출의 input, output, metadata를 쓰는 템플릿입니다. output은 본문을 그리는 방식을 고릅니다. text, json(트리), markdown, language가 있는 code, columns가 있는 table(행은 출력 배열이나 output.items에서 가져옵니다), 또는 기본값인 auto입니다. 정확한 match가 와일드카드보다 우선하고, 동점이면 먼저 설치된 확장이 이깁니다. 권한은 필요 없습니다. 이미 채팅에 있는 데이터를 그리는 방식만 바꾸기 때문입니다.

도구 모양만 꾸미는 확장에는 페이지가 전혀 필요 없습니다. panel.entry를 생략하면 레일에 아이콘이 없고, 설정 → 확장 에 카드만 표시됩니다. 페이지가 없으면 tools만 선언하고 다른 것은 선언하지 않아도 됩니다.

패널의 첫 줄

import { connectHost } from "@openchamber/sdk";
const host = connectHost();
host.onReady((ctx) => {
document.body.dataset.theme = ctx.theme.mode;
document.body.dataset.surface = ctx.surface;
});

connectHost는 OpenChamber 안에서만 동작합니다. 일반 파일로 열면 모든 호출이 HOST_UNAVAILABLE로 reject됩니다.

패널은 먼저 @openchamber/sdk/ui의 구성 요소로 만드세요. 버튼, 입력란, 드롭다운, 탭, 목록 등이 앱의 색상과 글꼴로 스타일링되어 있어 패널이 OpenChamber의 일부처럼 느껴집니다. 직접 HTML과 CSS를 작성하는 것은 키트에 없는 것에만 한정하세요. UI 키트를 참고하세요.

관련 문서

  • 확장에서 SSH 설치와 서버의 Git ID 선택 확인
  • Host API: 모든 connectHost 메서드, 제한, 오류 코드
  • UI 키트: 구성 요소
  • 예제: 복사해서 쓸 수 있는 파일 세 개짜리 완전한 확장