自定义 UI 扩展
使用任意前端框架获得完整的 UI 控制,以静态 SPA 形式加载。
自定义视图扩展是加载到隔离 BrowserWindow 中的静态 SPA,通过 kunkun-ext:// 协议提供。使用 React、Vue、Svelte——任何能构建为静态前端的框架。
当你需要完整的 UI 控制、转换现有 Web 应用,或想要特定框架特性时,选择自定义视图。如果你想要 Raycast 风格的列表/表单 UI 而不想自行管理渲染,请使用 Worker 扩展。
项目结构
项目设置
用 Vite 脚手架创建项目——任何能编译成静态前端的框架都行。React 放在最前,因为它最普及;其他标签页是相同步骤。
bun create vite my-extension --template react-ts
cd my-extension
pnpm add @kunkunsh/sdkbun create vite my-extension --template vue-ts
cd my-extension
pnpm add @kunkunsh/sdkbun create vite my-extension --template svelte-ts
cd my-extension
pnpm add @kunkunsh/sdkbun create vite my-extension --template vanilla-ts
cd my-extension
pnpm add @kunkunsh/sdknpm create vite@latest / pnpm create vite 效果相同。
自定义视图从扩展自己的 kunkun-ext:// 源加载,因此资源 URL 必须是相对的。在 vite.config.ts 中设置 base: './':
import { defineConfig } from 'vite'
// 导入框架插件:react() / vue() / svelte()
export default defineConfig({
base: './', // 必需:资源相对 kunkun-ext:// 加载
// plugins: [react()],
})更喜欢 SvelteKit、Nuxt 等元框架?也可以——只要输出静态 SPA(不要 SSR)。SvelteKit 用 @sveltejs/adapter-static,设置 ssr = false 并使用 hash 路由,让客户端路由在 kunkun-ext:// 下正常解析。下面的 main / dist / devMain 清单字段完全一致。
需要多页面?保持纯前端即可——加一个路由库,例如 React Router 或 TanStack Router(React),或 Vue Router(Vue)。优先使用 hash 或 memory 历史模式,这样深链接无需服务器就能在 kunkun-ext:// 源下解析。仅仅为了路由,你并不需要元框架。
清单
manifest 就是项目里已有 package.json 的 kunkun 字段——把它和 $schema 一起加进去,不要新建单独的文件:
{
"name": "my-extension",
"version": "0.1.0",
// ...你原有的 package.json 字段
"$schema": "https://schema.kunkun.sh/",
"kunkun": {
"identifier": "com.you.my-extension",
"name": "My Extension",
"icon": { "type": "iconify", "value": "mdi:puzzle", "invert": true },
"shortDescription": "简短描述",
"permissions": ["clipboard-read", "clipboard-write", "storage"],
"commands": [
{
"name": "main",
"title": "My Command",
"mode": "custom-view",
"main": "/",
"dist": "dist",
"devMain": "http://localhost:5173"
}
]
}
}| 字段 | 含义 |
|---|---|
main | SPA 内的路由路径 |
dist | 构建输出目录 |
devMain | 开发服务器 URL(开发时启用 HMR) |
窗口选项
{
"name": "window-demo",
"mode": "custom-view",
"main": "/window-demo",
"dist": "dist",
"window": { "titleBarStyle": "overlay", "transparent": true, "vibrancy": "sidebar", "width": 700, "height": 550 }
}titleBarStyle: "default" | "hidden" | "overlay" · transparent、vibrancy(macOS 模糊效果)、width/height。参见 window-control 以在运行时更改这些设置。
使用 SDK
在入口文件中先导入一次 @kunkunsh/sdk/ui/custom(在应用挂载之前)——这个副作用导入会接通扩展与宿主的 RPC 通道。其余 API(Clipboard、db、fs、showToast 等)都从 @kunkunsh/sdk 导入。
// src/main.tsx(React)/ src/main.ts(Vue、Svelte、原生)
import "@kunkunsh/sdk/ui/custom"; // 必须放在最前面
// ...照常启动应用:createRoot(el).render(<App />) / mount(App, ...) / createApp(App).mount(...)然后在组件中调用这些 API:
import { useState } from "react";
import { Clipboard, showToast, Toast } from "@kunkunsh/sdk";
export default function App() {
const [text, setText] = useState("");
async function read() {
try {
setText(await Clipboard.readText());
await showToast({ title: "已读取", style: Toast.Style.Success });
} catch (err) {
await showToast({ title: "错误", message: String(err), style: Toast.Style.Failure });
}
}
return (
<>
<button onClick={read}>读取剪贴板</button>
<p>{text}</p>
</>
);
}<script setup lang="ts">
import { ref } from "vue";
import { Clipboard, showToast, Toast } from "@kunkunsh/sdk";
const text = ref("");
async function read() {
try {
text.value = await Clipboard.readText();
await showToast({ title: "已读取", style: Toast.Style.Success });
} catch (err) {
await showToast({ title: "错误", message: String(err), style: Toast.Style.Failure });
}
}
</script>
<template>
<button @click="read">读取剪贴板</button>
<p>{{ text }}</p>
</template><script lang="ts">
import { Clipboard, showToast, Toast } from '@kunkunsh/sdk'
let text = $state('')
async function read() {
try {
text = await Clipboard.readText()
await showToast({ title: '已读取', style: Toast.Style.Success })
} catch (err) {
await showToast({ title: '错误', message: String(err), style: Toast.Style.Failure })
}
}
</script>
<button onclick={read}>读取剪贴板</button>
<p>{text}</p>对于持久化的结构化数据(列表、历史记录、书签),请使用记录存储而不是将 JSON 塞进 LocalStorage。
权限
声明你需要的内容,并将文件系统和网络范围限制到最小:
{
"permissions": [
"clipboard-read", "clipboard-write", "notifications", "storage",
{ "permission": "fs-read", "allow": ["$EXTENSION_SUPPORT/**", "$HOME/Documents/**"] },
{ "permission": "fs-write", "allow": ["$EXTENSION_SUPPORT/**"] },
{ "permission": "network", "domains": ["*.github.com", "api.example.com"] }
]
}请参阅权限了解路径别名和作用域形式。
构建与开发
pnpm build # 输出静态文件到 `dist`/`build`
pnpm dev # Kunkun 加载 `devMain` 并启用 HMR安装的 tarball 按原样提供——请捆绑你的依赖。Kunkun 不会在已安装的扩展内执行 npm install。
实际示例
仓库里的自定义视图示例恰好都是用 Svelte 写的,但上面的设置在各框架间完全一致——本指南里的 React/Vue 代码是直接内联编写的。
sample-custom-view-dev— 一个最小的 Svelte + Vite 入门项目(base: './'+main:"/"+dist的标准范式)ai-config-manager— 一个真实的 SvelteKit 自定义视图,管理作用域目录中的 AI 配置kkterminal/space-lens— 由生成的 backend 提供的自定义视图(参见 CLI + 插件)