Build an extension
Create, validate, install, and publish a Vibecape extension with the current SDK and CLI.
Vibecape extensions can add document actions, dialogs, full main views, settings panels, custom document types, and privileged main-process actions. Treat the installed @vibecape/extension-kit and @vibecape/cli as the source of truth.
Prerequisites
- Node.js 18 or later
- pnpm
- Vibecape installed locally for installation testing
Before using an old example, inspect the current contracts:
pnpm exec vibecape extension --helpAlso check manifest.schema.json and the exported declarations from @vibecape/extension-kit. Do not import private modules from the Vibecape App.
Initialize the project
pnpm dlx @vibecape/cli extension init ./my-extension \
--id com.example.my-extension \
--name "My Extension"
cd my-extension
pnpm installThe generated package follows this structure:
my-extension/
manifest.json
package.json
README.md
tsconfig.json
assets/
icon.svg
main/
index.ts
renderer/
index.tsxDo not hand-write dist files. The CLI owns the main and renderer bundles, serialized UI description, release archive, and vibecape-extension.json.
Define the manifest
Start with the smallest manifest that describes the extension:
{
"$schema": "./node_modules/@vibecape/extension-kit/manifest.schema.json",
"id": "com.example.my-extension",
"name": "My Extension",
"description": "One concrete sentence describing the user outcome.",
"icon": "./assets/icon.svg",
"main": "./dist/main/index.js",
"renderer": "./dist/renderer/index.js",
"activationEvents": ["onStartup"],
"extensionApiVersion": 1
}package.json.version is the only release version. Keep all paths inside the package root, add engines.vibecape only after testing the real minimum host version, and declare contributes.documentTypes only for a real custom format.
Register main actions
Privileged work belongs in an ExtensionMain action. Validate every input because calls cross a process boundary.
import { ExtensionMain } from "@vibecape/extension-kit";
export default class MyExtension extends ExtensionMain {
readonly id = "com.example.my-extension";
activate(): void {
this.extension.action({
id: "example.readDocument",
title: "Read current document",
run: async (input) => {
const record = input && typeof input === "object" ? input : {};
const directId = "docId" in record ? record.docId : undefined;
const doc =
"doc" in record && record.doc && typeof record.doc === "object"
? record.doc
: {};
const contextId = "id" in doc ? doc.id : undefined;
const docId =
typeof directId === "string"
? directId
: typeof contextId === "string"
? contextId
: "";
if (!docId) throw new Error("docId is required");
const file = await this.app.files.read({ id: docId });
this.logger.info("Read document", { docId });
return { id: file.id, name: file.name, content: file.content };
},
});
}
}Keep action IDs stable and namespaced. Inputs and results must be JSON-serializable. Secrets, authentication, network access, filesystem work, and expensive processing stay in the main runtime.
The structured bridge includes workspace, file, asset, external URL, Finder, and clipboard operations. Use the installed type declarations for the exact signatures.
Contribute renderer UI
Default-export defineRenderer and export every component referenced by a contribution:
import { defineRenderer } from "@vibecape/extension-kit/react";
export function HomePage() {
return <div>Extension main view</div>;
}
export function ExportDialog() {
return <div>Export workflow</div>;
}
export function ConfigPanel() {
return <div>Extension settings</div>;
}
export default defineRenderer(({ ui }) => {
ui.extension.mainview({
id: "home",
title: "My Extension",
component: HomePage,
});
ui.customize.mainview({
id: "home-entry",
title: "My Extension",
page: "home",
});
ui.doc.menu.root({ id: "open", title: "Open", order: 10 }).mainview("home");
ui.doc.menu.export({ id: "export", title: "Export", order: 20 }).dialog(ExportDialog);
ui.doc.menu.root({ id: "read", title: "Read", order: 30 }).run("example.readDocument");
ui.config.panel({ id: "settings", title: "My Extension", component: ConfigPanel });
});Every .run(actionId) needs a matching main action. Every .mainview(pageId) must reference a declared extension main view. A document action receives the current document context; a Customize entry can open without a document, so handle an absent doc.
Validate and install-test
Run the scripts provided by the generated project:
pnpm run validate
pnpm run typecheck
pnpm run build
pnpm run testBefore considering the extension complete, verify:
- The manifest passes the current CLI validator.
- Main and renderer bundles exist when declared.
- Every serialized component is exported.
- Every serialized action has a main registration.
- The release descriptor matches the archive version, size, and SHA-256.
- The extension installs from Extensions → More → Install from folder.
- Each entry point works with and without a current document where applicable.
- Stopping and restarting removes and restores contributions cleanly.
Publish with GitHub Releases
Third-party extensions are distributed from a public GitHub repository:
- Bump
package.json.versionusing semantic versioning. - Re-run validation, tests, and build.
- Create a stable tag such as
v1.2.0. - Upload the generated archive and
vibecape-extension.jsonwithout renaming them.
There is no third-party vibecape extension publish command. Vibecape resolves the latest stable GitHub Release from the repository URL.