Worker Extension
Build Raycast-style extensions with React components
Worker extensions run React components in sandboxed environments (Web Workers or Node.js processes) with the UI rendered by the host application. This provides a Raycast-like development experience with consistent UI.
Extension Modes
| Mode | Runtime | UI | Node.js Access |
|---|---|---|---|
worker-view | Browser Web Worker | Host renders | No |
node-view | Node.js process | Host renders | Yes |
worker-headless | Browser Web Worker | None | No |
node-headless | Node.js process | None | Yes |
Project Setup
Create Project
mkdir my-worker-ext
cd my-worker-ext
pnpm init
pnpm add @kunkunsh/api react valibot
pnpm add -D @types/react @types/bunCreate Build Script
Create build.ts using Bun's bundler:
import { dedupeReact, kunkunCommandPlugin } from "@kunkunsh/api/build";
import type { BunPlugin } from "bun";
// Browser target: runs in Web Worker
await Bun.build({
entrypoints: ["./src/App.tsx"],
outdir: "./dist",
target: "browser",
format: "esm",
minify: true,
sourcemap: "external",
naming: "[name].js",
plugins: [kunkunCommandPlugin({ mode: "view" }) as BunPlugin, dedupeReact(import.meta.dir)],
});
// Node target: runs in Node.js process
await Bun.build({
entrypoints: ["./src/App.tsx"],
outdir: "./dist/node",
target: "node",
format: "esm",
minify: true,
sourcemap: "external",
naming: "[name].js",
plugins: [kunkunCommandPlugin({ mode: "view" }) as BunPlugin, dedupeReact(import.meta.dir)],
});The kunkunCommandPlugin automatically:
- Injects the bootstrap code
- Wraps your component with the necessary RPC setup
- Handles communication with the host
Writing the Component
Create src/App.tsx:
import { useState } from "react";
import { Button, Div, H2, H3, P, Code, Pre, Hr } from "@kunkunsh/api/ui";
import {
popToRoot,
showToast,
Toast,
Clipboard,
getEnvironment,
LocalStorage,
} from "@kunkunsh/api";
export default function App() {
const [clipboardText, setClipboardText] = useState("");
const [envInfo, setEnvInfo] = useState("");
const [storageValue, setStorageValue] = useState("");
const [status, setStatus] = useState("");
return (
<Div className="p-6" style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
<H2>My Worker Extension</H2>
<P>Demonstrates worker-view plugin APIs.</P>
{/* Navigation */}
<Hr />
<H3>Navigation</H3>
<Button
title="Go Back"
variant="outline"
onClick={() => popToRoot()}
/>
{/* Toast */}
<Hr />
<H3>Toast</H3>
<Button
title="Show Toast"
variant="primary"
onClick={() => {
showToast({
title: "Hello!",
message: "Toast notification works.",
style: Toast.Style.Success,
});
}}
/>
{/* Clipboard */}
<Hr />
<H3>Clipboard</H3>
<Div style={{ display: "flex", gap: "8px" }}>
<Button
title="Read Clipboard"
variant="outline"
onClick={async () => {
const text = await Clipboard.readText();
setClipboardText(text);
}}
/>
<Button
title="Write to Clipboard"
variant="outline"
onClick={async () => {
await Clipboard.copy("Hello Kunkun!");
}}
/>
</Div>
{clipboardText && <Pre><Code>{clipboardText}</Code></Pre>}
{/* Storage */}
<Hr />
<H3>Storage</H3>
<Div style={{ display: "flex", gap: "8px" }}>
<Button
title="Store Value"
variant="outline"
onClick={async () => {
await LocalStorage.setItem("key", `Saved at ${new Date().toLocaleTimeString()}`);
}}
/>
<Button
title="Read Value"
variant="outline"
onClick={async () => {
const val = await LocalStorage.getItem("key");
setStorageValue(val ?? "(empty)");
}}
/>
</Div>
{storageValue && <P>Stored: <Code>{storageValue}</Code></P>}
</Div>
);
}UI Components
Import primitive components from @kunkunsh/api/ui:
import {
Button,
Div,
H2, H3, H4,
P,
Code,
Pre,
Hr,
Image,
List,
ListItem,
Grid,
Form,
TextField,
TextArea,
Select,
Checkbox,
} from "@kunkunsh/api/ui";These components are rendered by the host Svelte app, ensuring consistent styling.
List Component
<List>
<List.Item
title="Item Title"
subtitle="Subtitle"
icon="mdi:file"
onClick={() => console.log("clicked")}
/>
</List>Form Components
<Form>
<TextField
title="Name"
placeholder="Enter your name"
value={name}
onChange={setName}
/>
<TextArea
title="Description"
placeholder="Enter description"
value={desc}
onChange={setDesc}
/>
<Select
title="Choice"
value={choice}
onChange={setChoice}
options={[
{ label: "Option A", value: "a" },
{ label: "Option B", value: "b" },
]}
/>
</Form>Manifest Configuration
{
"name": "my-worker-ext",
"kunkun": {
"identifier": "com.example.my-worker-ext",
"name": "My Worker Extension",
"source": "kunkun",
"icon": {
"type": "iconify",
"invert": true,
"value": "mdi:puzzle"
},
"permissions": [
"clipboard-read",
"clipboard-write",
"storage",
"notifications"
],
"commands": [
{
"name": "main",
"title": "My Worker Command",
"description": "A worker-view command",
"mode": "worker-view",
"main": "dist/App.js"
},
{
"name": "node-version",
"title": "Node Version",
"description": "A node-view command with filesystem access",
"mode": "node-view",
"main": "dist/node/App.js"
}
]
}
}Command Modes
| Mode | Main Path | Runtime |
|---|---|---|
worker-view | dist/App.js | Browser Web Worker |
node-view | dist/node/App.js | Node.js process |
worker-headless | dist/Headless.js | Browser Web Worker (no UI) |
node-headless | dist/node/Headless.js | Node.js process (no UI) |
Building
bun run build.tsThis produces:
dist/App.js- Browser target (worker-view)dist/node/App.js- Node target (node-view)
Available APIs
Worker extensions have access to these APIs:
| API | Description |
|---|---|
Clipboard | Read/write clipboard content |
LocalStorage | Persistent key-value storage |
showToast | Show system notifications |
showHUD | Show head-up display overlay |
popToRoot | Close plugin and return to launcher |
getEnvironment | Get extension environment info |
fetch | Network requests with CORS bypass |
path | Path operations and aliases |
Node-view extensions additionally have:
| API | Description |
|---|---|
fs | File system operations |
shell | Execute shell commands |
dialog | Native file/dialogs |
permissions | Request/check permissions |
Full Example
See apps/sample-worker-ext in the repository for a complete example demonstrating:
- Worker-view and node-view commands
- Headless commands
- Service plugins
- Permission verification
- Deno runtime detection