Crea una extensión
Usa @openchamber/sdk para añadir tu propio panel a OpenChamber. Una extensión es una pequeña página web que OpenChamber muestra en la barra lateral derecha. Habla con la app a través de connectHost(): puede leer el proyecto y la sesión actuales, mostrar avisos, poner texto en el cuadro de chat, adjuntar una tarea a una sesión y, con la aprobación del usuario, iniciar sesiones y enviar prompts.
Las extensiones funcionan en OpenChamber web y de escritorio. VS Code y móvil todavía no las cargan.
Qué es una extensión
Una carpeta con tres archivos:
package.jsoncon un bloqueopenchamber(el manifiesto)panel/index.html, la página que muestra OpenChamberpanel/main.js, tu script, compilado en un único archivo clásico (un IIFE, no un módulo ES, porque la página se carga en un iframe aislado)
OpenChamber nunca compila tu código. Distribuye el archivo .js ya compilado. El SDK incluye un comando de empaquetado; cualquier otro bundler que genere un IIFE también sirve.
npm install @openchamber/sdkbunx openchamber-guest-bundle panel/main.ts panel/main.jsEl comando de empaquetado se ejecuta con Bun. Si no tienes Bun, usa esbuild o cualquier otro bundler con --format=iife --platform=browser.
Enlaza index.html con main.js mediante un <script src="main.js"></script> normal.
Una extensión completa y funcional, con los tres archivos, está en la página Ejemplo. Cópiala y cambia la API y la lista.
Instálala mientras trabajas
- Ejecuta OpenChamber en web o escritorio.
- Abre Ajustes → Extensiones.
- Pega la ruta absoluta de tu carpeta y haz clic en Añadir.
OpenChamber lee el manifiesto y muestra un diálogo con lo que pide la extensión (consulta Capacidades). Apruébalo y tu icono aparecerá en la barra lateral. Haz clic en él y se cargará tu página.
También puedes añadir un .zip local o un enlace https a un repositorio de git o a un archivo zip. Solo una instalación por git puede actualizarse: sube version en package.json, haz push y OpenChamber comprueba poco después de abrirse y cada pocas horas, avisa al usuario una vez y ofrece la actualización en Ajustes → Extensiones. Un enlace a openchamber://extensions/install?url=<encoded repository URL> en cualquier página abre la aplicación de escritorio con ese repositorio en el diálogo de añadir, así que puedes ofrecer un botón de instalación. Un #tag o #branch en la URL fija cuál sigue. Estos se copian a la carpeta de datos de OpenChamber y se ejecutan desde esa copia, así que distribuye los archivos compilados, no node_modules ni fuentes en TypeScript. Al quitar la extensión se elimina la copia. Una instalación desde carpeta se ejecuta directamente desde tu carpeta, así que puedes editar, recompilar y recargar.
Manifiesto
{ "name": "@acme/hello", "version": "1.0.0", "openchamber": { "apiVersion": 1, "engines": { "openchamber": ">=1.24.0" }, "id": "acme-hello", "name": "Hello", "icon": "window", "contributes": { "panel": { "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 es obligatorio y debe ser semver. Ajustes → Extensiones lo muestra en la tarjeta.
apiVersion es 1. OpenChamber rechaza cualquier otro valor.
engines.openchamber es opcional. Indica la versión más antigua de OpenChamber con la que funciona tu extensión, como 1.24.0 o >=1.24.0. Las versiones anteriores se niegan a instalarla en vez de fallar más tarde.
id, name e icon identifican la extensión. id va en kebab-case y debe ser único entre las extensiones instaladas. icon es un nombre de Remixicon (RiWindowLine se convierte en window) o un archivo SVG dentro de tu carpeta, como icon.svg. contributes.panel es el panel de la extensión en la barra lateral: su entry es el archivo HTML dentro de tu carpeta. Una extensión sin panel en la barra lateral omite panel. Los paquetes antiguos mantienen id, name e icon dentro de panel, y eso sigue funcionando.
contributes.attach es opcional. Añade tu extensión al menú + junto al cuadro de chat, para que el usuario pueda elegir una tarea y adjuntarla a una sesión.
"dialog"abre tu página en una ventanatrueo"panel"abre el panel de la barra lateral en su lugar{ "mode": "dialog", "entry": "panel/attach.html" }abre en la ventana una página aparte tuya, para que el selector no tenga que compartir código con el panel de la barra lateral- si lo omites, la extensión no aparece en ese menú
ctx.surface es panel, dialog, page, status o block. Al pulsar un elemento adjunto, ctx.item contiene sus datos; en los demás casos es null.
contributes.page: true ofrece el panel a pantalla completa en el menú Páginas de extensiones, encima de las sesiones. { "entry": "panel/page.html", "title": "Board" } usa otro HTML y un título opcional. Sigue necesitando panel.entry y usa el mismo aislamiento y permisos. Solo el usuario abre la página. Se cierra al recargar, pausar, eliminar o cambiar de servidor.
Para un tablero, usa host.storage y las listas de proyectos, worktrees y sesiones con estados en directo. startSession puede apuntar a otro proyecto sin cerrar el tablero. Consulta Host API.
contributes.integration es opcional. Añade una tarjeta en Ajustes → Integraciones donde el usuario conecta una cuenta. Consulta Cuentas y red.
contributes.service es opcional. Declara un servicio local (un proceso que OpenChamber inicia junto a la extensión) para cosas a las que una página web no puede llegar, como un socket de Docker. Ese proceso se ejecuta con el acceso completo del usuario y sin sandbox, así que el diálogo de aprobación avisa sobre ello; declara uno solo cuando la página no pueda hacer el trabajo. Consulta el archivo GUEST_SERVICES.md del paquete. Un servicio con provides: ["browser"] también puede sustituir al navegador del agente: responde a las acciones browser.* en el servidor, así los agentes navegan sin ninguna aplicación de escritorio abierta, y el usuario lo elige en Ajustes → Herramientas de OpenChamber. Un servicio así no necesita panel. Un servicio también puede enterarse de lo que pasa en las sesiones del usuario: declara contributes.sessionEvents a su lado, y OpenChamber envía al servicio cada turno que termina o falla y cada permiso o pregunta que pide el agente, desde todos los proyectos, sin ninguna ventana abierta. Ver Eventos de sesión. Y un servicio con provides: ["notifications"] entrega las notificaciones del usuario junto al escritorio y al teléfono, a Slack, Matrix o a un servidor propio; el usuario conecta la cuenta en Ajustes → Integraciones y el token se queda en OpenChamber. Ver Canales de notificación. Un servicio con provides: ["tracker"] es un gestor de incidencias junto a Linear, con su tarjeta de cuenta bajo Cuentas de extensiones; ver Gestores de incidencias. Y un servicio con provides: ["quota"] informa del uso junto a los proveedores integrados, leyendo las credenciales que OpenCode guarda para los proveedores que enumera en el manifiesto; ver Proveedores de uso.
Capacidades
Dibujar un panel y leer la sesión actual no requiere permisos. Todo lo que actúa en nombre del usuario sí. Enumera esas capacidades en contributes.capabilities:
| Capacidad | Qué permite |
|---|---|
prompt | enviar mensajes a la sesión del usuario (prompt({ send: true }), startSession con text) |
sessions | listar proyectos, worktrees y estados de sesiones; crear y abrir sesiones en proyectos registrados |
files | leer y escribir archivos dentro del proyecto abierto (readFile, writeFile, listDir, stat con una ruta relativa) |
model | generación de texto puntual con el Small Model del usuario (generate), fuera de cualquier sesión |
Otras once se añaden por ti: network cuando declaras una integration, service cuando declaras un service, filesystem cuando declaras patrones filesystem, origins cuando declaras origins, conversation cuando una acción de sesión pide los mensajes (ver Acciones, comandos y la insignia), transcript cuando sessionEvents o sessionControl piden content (ver Eventos de sesión), sessionControl cuando tu servicio actúa sobre sesiones (ver Llamar a OpenChamber), sessionAnswers cuando responde en nombre del usuario a las solicitudes de permiso y los formularios del agente (sessionControl: { answers: true }), instructions cuando añade contenido a lo que leen los agentes, y skills cuando declaras contributes.skills, una carpeta del paquete con skills que llegan a todos los agentes mientras la extensión está activa, y opencodePlugin cuando declaras contributes.opencodePlugin (ver más abajo).
El usuario ve la lista completa una vez, al instalar la extensión, y la aprueba o quita la extensión. Si una versión nueva pide más, el diálogo vuelve a aparecer. Una llamada que necesita una capacidad que el usuario no ha aprobado falla con NOT_GRANTED.
Un plugin de OpenCode en tu paquete
Una extensión puede incluir un plugin de OpenCode, para que la gente instale una sola cosa en vez de dos. Declara "opencodePlugin": "plugin", una carpeta dentro del paquete con index.js o server.js en su raíz. OpenCode no lee main, así que para una entrada en otro lugar define name y exports en el package.json de la carpeta. La entrada exporta por defecto { id, setup }, la forma que carga OpenCode; no importa nada, porque OpenCode la carga con un import() simple desde tu carpeta, donde no hay nada instalado. El input de una herramienta es JSON Schema plano, y su execute responde { content }.
{ "contributes": { "panel": { "id": "teapot-tools", "name": "Teapot Tools", "icon": "cup" }, "opencodePlugin": "plugin" }}El diálogo de aprobación recibe una fila más, Añadir un plugin a OpenCode, que dice con claridad que el código del plugin se ejecuta dentro de OpenCode con todo el acceso del usuario. Una vez permitido, OpenChamber lista tu carpeta en la configuración de OpenCode que gestiona, el mismo archivo que lleva contributes.skills; el OpenCode que ejecuta vigila ese archivo y carga el plugin en un par de segundos. Desactivar o eliminar la extensión lo retira del mismo modo. No se escribe nada en el opencode.json del usuario: ese archivo es suyo, y un plugin allí sobreviviría a tu extensión y se ejecutaría en cada OpenCode de la máquina.
Cuando ese archivo no llega a OpenCode, la pantalla de la extensión lo dice y ofrece Añadir a la configuración global, que escribe tu carpeta en la configuración global de OpenCode del usuario como una entrada que desde entonces es suya. Eso ocurre cuando OpenChamber no inició el OpenCode con el que habla, o cuando el entorno del servidor define su propio OPENCODE_CONFIG (el plugin se carga entonces en el siguiente arranque de OpenCode). Un OpenCode en otra máquina no puede cargar una carpeta de esta; la pantalla lo dice y no ofrece nada. Un plugin que no carga lo informa OpenCode en Ajustes → Plugins cuando tiene una entrada allí; tu extensión sigue funcionando en cualquier caso. La instalación rechaza un paquete cuya carpeta del plugin falta o cuyo package.json no indica un archivo de entrada.
VS Code y las apps móviles no cargan extensiones, así que ningún plugin llega a ellas. Una sesión dentro de un espacio aislado ejecuta su propio OpenCode, que nunca lee la configuración del host. El modo empresa trata el plugin como un servicio local: solo se instala desde un repositorio aprobado por el administrador. El SDK tiene un ejemplo funcional, examples/opencode-plugin.
Generar texto
Con la capacidad model, host.generate pide al Small Model del usuario una respuesta puntual: un resumen, un título, un borrador. Nada entra en una sesión, no se guarda historial, y la extensión nunca elige un proveedor. OpenChamber usa el mismo modelo que usa para su propio trabajo en segundo plano, elegido en Ajustes → Sesiones → Small Model, o el modelo pequeño del proveedor con el que está trabajando el usuario, o el modelo predeterminado de su configuración de OpenCode.
const { text } = await host.generate({ prompt: task.description, system: "Write a one-line summary. Return only the summary.", maxOutputTokens: 200,});Cuando no hay ningún modelo disponible, la llamada falla con NO_MODEL; un modelo que dio error es MODEL_FAILED. Los prompts están limitados a 64.000 caracteres, y la llamada espera hasta 90 segundos. El usuario ve este permiso como “Usar tu Small Model” y le cuesta tokens, así que mantén los prompts cortos y llámalo al hacer clic, no en cada pulsación de tecla.
Cuentas y red
Tu página se ejecuta en un entorno aislado y no puede acceder a la red por sí sola: scripts, estilos, imágenes, fuentes y multimedia se cargan solo desde tu paquete, y fetch solo alcanza los archivos de tu paquete. Incluye las fuentes y las imágenes dentro del paquete en lugar de cargarlas desde un CDN.
Para hablar con un servicio externo, declara una integration y OpenChamber hará las llamadas por ti a través de host.request, con el token del usuario adjunto. El token nunca llega a tu página.
Para cualquier otra cosa que tu página necesite directamente, como fuentes o imágenes de un CDN o una API pública, enumera hasta 8 orígenes https:
"origins": ["https://fonts.example.com"]El usuario los ve en el diálogo de aprobación como direcciones a las que tu extensión puede enviar datos y de las que puede recibirlos, y vuelve a aprobarlos si una actualización añade uno. Los orígenes aprobados quedan abiertos a fetch, imágenes, fuentes, estilos y multimedia, pero nunca a scripts: el código tiene que ir en tu paquete. El origen de la página es null, así que para leer una respuesta de fetch el servidor debe permitir CORS para ese origen.
En modo empresa, una extensión con origins, una integration o un service solo se instala desde un repositorio Git que el administrador haya incluido en la lista. Si tu extensión puede funcionar sin ellos, sigue siendo instalable para esos equipos. Para desarrollar una en una máquina así, pide al administrador allowLocalExtensions, que te deja instalarla desde su carpeta.
token: el usuario pega un token de API en la tarjeta de Ajustes → Integraciones.apiOrigines el único origen al querequestpuede llamar.accountes opcional: una ruta GET y un nombre de campo, para que la tarjeta muestre quién está conectado.schemeindica cómo se envía el token; mira la tabla de abajo. Consulta la documentación de la API del proveedor para saber qué cabecera espera.oauth: el usuario pega un client id y OpenChamber ejecuta el flujo de autorización. NecesitaauthorizeUrl,tokenUrlyapiOrigin.host: { "provider": "linear" }: reutiliza la cuenta de Linear ya conectada en OpenChamber. No hace falta client id.
Qué envía cada scheme:
scheme | Cabecera que envía OpenChamber | Cuándo usarlo |
|---|---|---|
| omitido | Authorization: <token> | la API documenta un token sin prefijo en la cabecera |
"bearer" | Authorization: Bearer <token> | la API documenta un token bearer o un personal access token |
"basic" | Authorization: Basic base64(username:token) | la API documenta HTTP Basic auth con un usuario (a menudo un email) y un token de API; la tarjeta pide ambos, y usernameLabel da nombre al primer campo |
settings añade campos de texto sencillos a la tarjeta. Sus valores llegan en ctx.settings. Un campo con "secret": true se enmascara en la tarjeta y nunca llega a una página; tu servicio lo lee con readSettings().
Archivos
Tu página no puede tocar el disco por sí misma. OpenChamber lee y escribe por ella, dentro de los límites que el usuario aprobó.
- Una ruta relativa (
README.md,src/index.ts,.) significa el proyecto abierto. Necesita la capacidadfiles. Sin proyecto abierto esNO_DIRECTORY. - Una ruta que empieza por
/o~/significa cualquier otro lugar. Debe coincidir con uno de los patrones que declaras encontributes.filesystem, y el usuario ve esos patrones exactos en el diálogo de aprobación:
"filesystem": ["~/.config/opencode/opencode.json", "~/notes/**"]Los patrones usan * para un segmento de ruta y ** para cualquier profundidad. Todo lo que quede fuera de ellos es BAD_PATH, incluso después de la aprobación. .. nunca está permitido.
const config = await host.readFile("~/.config/opencode/opencode.json");await host.writeFile("~/.config/opencode/opencode.json", nextJson);const { entries } = await host.listDir(".");Las escrituras son atómicas: OpenChamber escribe un archivo temporal y lo renombra, así que un lector nunca ve un archivo a medio escribir. Los archivos se leen y escriben como texto UTF-8, hasta 2 MB.
Una sección en Work Status
contributes.statusSection añade tu propia sección al panel Work Status junto al chat. Los usuarios la activan, la desactivan y la mueven en la lista de secciones de ese panel, igual que las secciones integradas.
"statusSection": { "entry": "status/index.html", "title": "Recent commits", "height": 160 }truereutilizapanel.entry.entryes una página HTML aparte en tu carpeta. Con ella no necesitaspanel.entry, así que la extensión puede ser solo esta sección, sin icono en la barra lateral.titlesustituye el nombre del panel en el encabezado de la sección, hasta 60 caracteres.heightes la altura inicial en píxeles, de 24 a 320 (120 por defecto). Llama ahost.setHeight()cuando cambie el tamaño de tu contenido.
Tu página ve ctx.surface como status. OpenChamber la carga solo mientras el panel Work Status está visible y tu sección está desplegada, y la descarga cuando el usuario la pliega, así que no guardes ahí nada que no puedas reconstruir. Una extensión que solo tiene la sección puede usar permisos, un servicio, una integración y archivos, pero no page, attach, actions ni commands. Solo web y escritorio. El ejemplo git-graph-status es una sección completa.
Un editor para tu tipo de archivo
contributes.fileEditors abre archivos en tu página en lugar del editor de texto. Cuando el usuario abre un archivo que coincide en Files, OpenChamber muestra ahí tu editor, con el mismo conmutador de código fuente, botón de pantalla completa y botón de guardar que tienen los visores integrados.
"fileEditors": [ { "id": "canvas", "title": "Excalidraw", "match": ["*.excalidraw", "*.excalidraw.md"], "entry": "editor/index.html" }]matchcompara solo nombres de archivo, sin distinguir mayúsculas y minúsculas.*coincide con cualquier secuencia de caracteres y?con uno solo. Un patrón no puede contener/ni estar formado solo por comodines.entryes una página HTML en tu carpeta. No necesitaspanel.entry, así que la extensión puede ser solo el editor, sin icono en la barra lateral.contentes"text"(el valor por defecto) o"binary", ver más abajo.- Hasta 8 editores por extensión, con hasta 16 patrones cada uno.
OpenChamber se encarga del archivo. Tu página nunca lo lee ni lo escribe y no necesita ningún permiso para ello. OpenChamber te pasa el texto, te pide el texto editado al guardar y gestiona Cmd/Ctrl+S, el guardado automático, el aviso de cambios sin guardar, los finales de línea y los cambios hechos en disco:
import { connectHost, createFileSaveTracker } from "@openchamber/sdk";
const host = connectHost();let tracker = createFileSaveTracker(null);
host.onFileOpen((file) => { if (file.encoding !== "text") return; // un editor de texto siempre recibe texto load(file.content); // también file.path, file.name, file.readOnly tracker = createFileSaveTracker(versionOf(currentText()));});
// Llámalo después de cada edición. `edited` retiene el guardado automático hasta que el usuario se detiene.const changed = () => host.reportFileChange(tracker.observe(versionOf(currentText())));
host.onFileSnapshot(() => ({ content: currentText(), version: versionOf(currentText()) }));
// Las ediciones hechas mientras OpenChamber escribía dejan el archivo sin guardar.host.onFileSaved((version) => host.reportFileChange({ dirty: tracker.markSaved(version), edited: false }));version es cualquier cadena corta que cambie cuando cambia el documento, como un hash. Si un archivo no es algo que tu editor pueda abrir, llama a host.reportFileUnsupported() y OpenChamber mostrará su código fuente con un aviso. Los archivos de más de 20 MB se quedan en la vista de código fuente.
Archivos binarios
Pon "content": "binary" para abrir archivos que no son texto, como hojas de cálculo, documentos o imágenes. Tu editor recibe los bytes del archivo y devuelve bytes; todo lo demás funciona igual.
host.onFileOpen((file) => { if (file.encoding !== "binary") return; open(file.bytes); // un Uint8Array, exactamente como está en disco});
host.onFileSnapshot(() => ({ bytes: currentBytes(), version: versionOf(currentBytes()) }));Un editor binario puede quedarse con cualquier archivo que coincida con sus patrones, incluidos los que OpenChamber previsualiza por sí mismo, como imágenes y PDF. No tiene vista de código fuente, y cuando el usuario lo lleva a pantalla completa o lo saca de ella con cambios sin guardar, OpenChamber los guarda primero. Leer y escribir el formato le toca a tu editor: OpenChamber escribe exactamente los bytes que devuelves.
Tu página ve ctx.surface como file. OpenChamber la recarga, volviendo a pasarle el archivo, cuando el archivo cambia en disco, el usuario descarta los cambios o el editor entra o sale de pantalla completa. Gana la primera extensión activada cuyo patrón coincida, por delante de las vistas previas propias de OpenChamber. Una extensión que solo tiene el editor puede usar permisos, un servicio, una integración y archivos, pero no page, attach, actions ni commands. Solo web y escritorio. El ejemplo checklist-editor es un editor completo.
Acciones, comandos y la insignia
Además de la barra lateral y el menú +, una extensión puede aparecer en tres sitios más. Todos entregan a la extensión un item igual que un clic en un chip.
Acciones en mensajes y sesiones. Declara entradas de menú y OpenChamber las muestra junto a las integradas:
"actions": [ { "id": "create-task", "label": "Create task", "icon": "add-line", "where": "message", "roles": ["assistant"] }, { "id": "summarize", "label": "Summarize session", "where": "session", "payload": ["messages"] }]where también acepta selection (texto seleccionado en un mensaje), file (una fila en Files) y diff (líneas seleccionadas de un archivo o de un diff). Sus elementos llevan un kind con el mismo nombre: selection trae el texto, file la ruta, diff la ruta y el rango de líneas. En una selección, display indica cómo se muestra la acción: label (el valor predeterminado), icon con la etiqueta como descripción emergente, o both.
Una acción de mensaje abre tu extensión con un ctx.item de kind: "message": el id y el título de la sesión, la carpeta del proyecto, el id del mensaje, su rol y su texto. Una acción de sesión la abre con kind: "session" y el id, el título y la carpeta de la sesión. Añade "payload": ["messages"] y el elemento también lleva toda la conversación, de la más antigua a la más reciente, el mismo texto que produce la exportación a Markdown. Eso necesita la capacidad conversation, que el usuario ve como una línea aparte en el diálogo de aprobación. item.action te dice qué entrada se pulsó.
Comandos de barra que adjuntan. Declara un comando y atiéndelo en la página:
"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;});Cuando el usuario escribe /task ABC-12 en el cuadro de chat, OpenChamber pide a tu extensión que lo resuelva y adjunta lo que devuelves como un chip, sin abrir nada. Devuelve null para “no se encontró nada”. Si tu panel está cerrado, OpenChamber lo carga en segundo plano para esa llamada. Un nombre que OpenChamber u OpenCode ya usan se ignora.
Insignia en el icono de la barra lateral. host.setBadge(3) pone un punto en tu icono y muestra el número en su descripción emergente, por ejemplo tareas abiertas; host.setBadge(null) lo quita. Abrir el panel también lo quita.
Tus herramientas en el chat
Cuando tu plugin de OpenCode o servidor MCP añade una herramienta, el chat muestra sus llamadas con un icono genérico y la salida en bruto. Declara en su lugar cómo deben verse, sin código:
"tools": [ { "match": "mcp.jira.*", "name": "Jira", "icon": "task-line", "title": "{input.key}", "subtitle": "{output.status}", "output": "table", "columns": ["key", "summary", "status"] }]match es el nombre de la herramienta tal como lo reporta OpenCode; un * final coincide con un prefijo. icon es un nombre de Remixicon o un archivo .svg en tu paquete, como panel.icon. title y subtitle son plantillas sobre input, output y metadata de la llamada. output elige cómo se renderiza el cuerpo: text, json (un árbol), markdown, code con un language, table con columns (las filas vienen del array de salida o de output.items) o auto para el comportamiento por defecto. Un match exacto gana a un comodín, y en caso de empate gana la primera extensión instalada. No hace falta ningún permiso: esto solo cambia cómo se dibujan datos que ya están en el chat.
Una extensión que solo da estilo a herramientas no necesita página: omite panel.entry y no tendrá icono en la barra lateral, solo una tarjeta en Ajustes → Extensiones. Sin página puede declarar tools y nada más. La otra excepción es una sección de Work Status o un editor de archivos con su propio entry, ver arriba.
Primeras líneas en el panel
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 solo funciona dentro de OpenChamber. Si abres la página como un archivo suelto, todas las llamadas se rechazan con HOST_UNAVAILABLE.
Construye el panel primero con los componentes de @openchamber/sdk/ui: botones, campos, desplegables, pestañas, listas y más, con los colores y fuentes de la app, para que el panel se sienta parte de OpenChamber. Escribe tu propio HTML y CSS solo para lo que el kit no cubra. Consulta Kit de UI.
Relacionado
- Extensiones para instalar por SSH y elegir la identidad Git del servidor
- API del host para cada método de
connectHost, sus límites y códigos de error - Kit de UI para los componentes
- Ejemplo para una extensión completa de tres archivos que puedes copiar