@vibecape/extension-kit
v0.3.1
Published
Public SDK for building Vibecape extensions.
Downloads
541
Readme
@vibecape/extension-kit
Public SDK for building Vibecape extensions.
Use this package inside an extension project to describe extension metadata, implement main-process capabilities, and register renderer UI into Vibecape-provided slots.
pnpm add -D @vibecape/extension-kit @vibecape/cliMental model
A Vibecape extension owns its own UI and behavior. The Vibecape app should stay generic: it exposes runtime APIs and UI slots, while the extension declares how it wants to appear.
Renderer development is intentionally direct:
- where: the app UI slot, such as
doc.menu.exportorextension.mainview - what: the component or operation the extension contributes
- how: the presentation, such as
dialog,run, ormainview
The renderer should make that shape obvious at the top level:
import { defineRenderer } from "@vibecape/extension-kit/react";
import { FeishuMainview } from "./FeishuMainview";
import { PublishDialog } from "./PublishDialog";
export default defineRenderer(function render({ ui }) {
ui.doc.menu
.export({
title: "Publish to Feishu",
icon: "send",
})
.dialog(PublishDialog);
ui.extension.mainview({
id: "main",
title: "Feishu",
component: FeishuMainview,
});
});Do not move extension-specific dialogs, panels, menus, or business logic into the Vibecape app. If a Feishu extension needs a publish dialog, that dialog belongs to the Feishu extension package.
Installation and runtime activation are separate. Publishing or installing an extension only makes it available on disk; the user must start it before Vibecape loads its main process, renderer UI, actions, or optional agent plugin. Stopping an extension keeps it installed but removes its UI slots, actions, and agent plugin from the host.
Package entrypoints
import { ExtensionMain } from "@vibecape/extension-kit";
import type { ExtensionManifest } from "@vibecape/extension-kit";
import { ExtensionMain as MainBase } from "@vibecape/extension-kit/main";
import { defineRenderer } from "@vibecape/extension-kit/react";
import type { ExtensionRendererContext } from "@vibecape/extension-kit/react";The root entrypoint re-exports the main runtime types and renderer types. Subpath exports are available when you want clearer import boundaries. Vibecape extensions are trusted local code, so the manifest does not declare a permissions model.
0.2.0 replaces the former string-based this.app.invoke(...) API with the structured AppBridge API. Extensions targeting earlier SDK versions must be migrated before upgrading.
Suggested extension layout
my-extension/
manifest.json
package.json
main/
index.ts
renderer/
index.tsx
PublishDialog.tsx
FeishuMainview.tsx
assets/
icon.svgThe Vibecape CLI builds this layout into dist/, extracts renderer UI contributions into dist/renderer/ui.json, and packs the extension into a zip archive.
manifest.json
An extension requires at least id, name, and main.
{
"$schema": "./node_modules/@vibecape/extension-kit/manifest.schema.json",
"id": "vibecape.feishu",
"name": "Feishu",
"description": "Publish Vibecape documents to Feishu.",
"icon": "./assets/icon.svg",
"main": "./dist/main/index.js",
"renderer": "./dist/renderer/index.js",
"activationEvents": ["onStartup"],
"engines": {
"vibecape": ">=1.1.0"
},
"extensionApiVersion": 1
}During development, keep source files in main/ and renderer/. The CLI rewrites release entries to the built dist/ files when packing.
The CLI and Vibecape App use the same validator exported from
@vibecape/extension-kit/manifest. Removed fields such as permissions and
agentPlugin are rejected consistently during validation, build, and install.
The published JSON Schema is available as
@vibecape/extension-kit/manifest.schema.json for editor completion and Coding
Agent inspection. The runtime validator remains authoritative.
Main runtime
The main runtime is for extension capabilities that need host APIs, long-running work, network calls, or document operations.
import { ExtensionMain } from "@vibecape/extension-kit";
export default class FeishuMain extends ExtensionMain {
async activate() {
this.logger.info("Feishu extension activated");
this.extension.action({
id: "feishu.publish",
title: "Publish to Feishu",
kind: "sync",
async run(input) {
return { ok: true, input };
},
});
}
deactivate() {
this.logger.info("Feishu extension deactivated");
}
}AppBridge
this.app is a structured AppBridge for typed access to app capabilities:
const doc = await this.app.files.read({
id: "document-id",
});
await this.app.system.clipboard.writeText({
text: doc.content,
});
await this.app.system.clipboard.writeHtml({
html: "<strong>Rich text</strong>",
text: "Rich text",
});
await this.app.system.openExternal({
url: "https://example.com",
});Current AppBridge capability groups include:
workspace.getandworkspace.treefiles.readandfiles.writeassets.resolveLocalPathsystem.openExternalandsystem.showItemInFoldersystem.clipboard.writeTextandsystem.clipboard.writeHtmllog.debug/info/warn/error
Renderer runtime
The renderer entry must export a default render function created with defineRenderer.
import { defineRenderer } from "@vibecape/extension-kit/react";
export function PublishDialog() {
return <div>Publish UI</div>;
}
export function FeishuMainview() {
return <div>Feishu UI</div>;
}
export default defineRenderer(function render({ ui }) {
ui.doc.menu.export({ title: "Publish" }).dialog(PublishDialog);
ui.extension.mainview({
id: "main",
title: "Feishu",
component: FeishuMainview,
});
ui.command.palette({ title: "Publish" }).run("feishu.publish");
});Components passed to slots must be either:
- a string component reference supported by the host, or
- an exported function component from the renderer entry module
The CLI validates this during vibecape extension build.
Supported UI slots
ui.doc.menu.root/export/share(meta).dialog(Component)
Adds an extension item to the current document menu. root, export, and
share describe the action semantics and ordering group. Vibecape currently
shows all three groups inside the document menu's top-level Extensions submenu.
Dialog components receive the current doc context.
ui.doc.menu
.export({
id: "publish-feishu",
title: "Publish to Feishu",
icon: "send",
group: "publish",
order: 20,
})
.dialog(PublishDialog);ui.doc.menu.*(meta).run(actionId)
Runs a main-process action and injects the current document as { doc }.
ui.doc.menu
.share({ id: "copy-link", title: "Copy share link" })
.run("share.copyLink");The injected document context is JSON-serializable:
type ExtensionDocumentContext = {
id: string;
title: string;
format: string;
metadata: Record<string, unknown>;
};Use a dialog or mainview component when the workflow needs additional dynamic input, then call extension.invoke(actionId, { docId: doc.id, ...params }) explicitly.
ui.extension.mainview(input)
Declares the extension-owned page. Every renderer must declare exactly one mainview. Vibecape automatically adds it to the Customize list, and document menu items can open the same page by id and pass the current document context.
ui.extension.mainview({
id: "main",
title: "Feishu",
component: FeishuMainview,
});
ui.doc.menu.root({ title: "Feishu" }).mainview("main");Mainview components should use the host-provided semantic components instead of recreating page padding, surfaces, controls, and responsive layout:
import type { ExtensionMainviewProps } from "@vibecape/extension-kit/react";
export function FeishuMainview({ ui }: ExtensionMainviewProps) {
const { Button, Group, Page, Row, Toolbar } = ui.components;
return (
<Page>
<Toolbar title="Publishing" />
<Group>
<Row
label="Destination"
description="Choose where documents are published"
trailing={<Button>Connect</Button>}
/>
</Group>
</Page>
);
}The current component contract includes:
- layout:
Page,Section,Group,Row,Stack,Inline,SplitPane; - navigation and actions:
Toolbar,Tabs,Button,IconButton; - collections:
List,ListItem; - content and state:
Text,Badge,Status,Callout,EmptyState,LoadingState,Code,CodeBlock; - controls:
Input.
The host owns the mainview outer padding, scrolling, theme, and Overview.
Extensions own their business hierarchy and compose it from ui.components.
Do not import private App or Settings components, set page-level padding, or
reimplement shared controls with a mainview-wide <style> block.
ui.command.palette(meta).run(actionId)
Adds an extension command to the global command palette. The action id must be registered by the extension main runtime with this.extension.action(...).
ui.command
.palette({
id: "publish-feishu-command",
title: "Publish to Feishu",
group: "publish",
keywords: ["publish", "feishu"],
})
.run("feishu.publish");Type reference
Commonly used types:
ExtensionMainExtensionManifestExtensionActivationEventExtensionActionExtensionDocumentContextAppBridgeExtensionRendererContextExtensionRendererUiExtensionMainviewPropsExtensionRendererUIExtensionUIComponentsExtensionRendererContributionExtensionUiWhere
Build with the CLI
Initialize a new extension directly:
pnpm dlx @vibecape/cli extension init my-extensionInside an initialized extension, run:
pnpm install
pnpm run validate
pnpm run typecheck
pnpm run buildSee @vibecape/cli for command details.
