跳转到内容

下载 Termii · v0.4.5

三分钟,装进你的 Dock

原生构建,包体仅数 MB。应用内自动更新,始终新鲜。

安装说明

macOS:若提示「无法打开」,在终端执行 xattr -dr com.apple.quarantine /Applications/Termii.app。Windows:首次运行可能触发 SmartScreen,选择「仍要运行」即可。

快速开始

用官方模板 Termii-App/plugin-template 把第一个插件跑起来。整个过程只需要一条构建命令和一个安装动作。

  • 已安装 Termii(任意较新版本,示例要求 ≥ 0.3.6)
  • Node.js(npm 可用)
  • Git
Terminal window
git clone https://github.com/Termii-App/plugin-template.git
cd plugin-template
npm install # 安装 @termii/plugin-sdk(git 依赖,prepare 自动构建 dist)

模板包含两个可运行示例:

目录 说明
hello/ 入门模板:视图 + 命令 + 设置分区 + 快捷键,纯 JS(.jsx)
sidecar-sysinfo/ 原生能力模板:sidecar 二进制桥(macOS + python3)
Terminal window
bash hello/build.sh
# → 生成 hello/main.js(单文件 ES module,压缩后约几 KB)

构建脚本内部等价于:

Terminal window
npx termii-plugin-sdk build src/main.jsx --outfile main.js --minify

脚手架会把 react / lucide-react alias 到 SDK 的 shims(运行时从宿主 共享实例取),因此产物体积很小,且不会与宿主产生 React 上下文冲突。

打开 Termii:设置 → 插件 → 从磁盘安装,选择 hello/ 目录(包含 plugin.json 与 main.js 的那个目录)。

首次启用会弹出信任确认——hello 未声明任何 L1 能力(capabilities 缺省 为 []),属于纯 UI 插件,确认基础信任即可。

安装启用后:

  • 侧栏出现「Hello 面板」视图(也可按 ⌘K 搜索 Hello)
  • 面板上有「向活跃终端写入一行」按钮:打开一个终端 tab 后点击, 终端里会出现 echo "hello from plugin #N" 的输出
  • ⌘K 命令面板里出现 Say Hi 命令
  • 设置页出现插件的「Hello World」分区(里面有个开关)
  • 按 Mod+Shift+H 直接跳转到插件视图
hello/
├── build.sh # 构建 main.js
├── plugin.json # 清单(id / name / version / apiVersion / contributes)
└── src/
├── main.jsx # definePlugin({ manifest, activate })——插件本体
└── manifest.js # 与 plugin.json 同步的 bundle 内 manifest

两个要点:

  • src/manifest.js 与 plugin.json 必须一致:loader 会校验 bundle 内 manifest.id 与插件目录名一致(防目录伪造),修改 plugin.json 时记得同步 manifest.js。
  • 入口是 .jsx:esbuild 的 JSX 解析不支持 TS 语法,因此模板只 import 运行时 definePlugin;TS 模板可再写 import type { TermiiPlugin } 获得完整类型上下文(见 SDK 与打包脚手架)。

activate(ctx) 里注册了四类贡献点,是最小的完整示例:

import { definePlugin } from "@termii/plugin-sdk";
import React from "react";
import { Smile, Zap } from "lucide-react";
import manifest from "./manifest.js";
function Panel({ ctx }) {
const [count, setCount] = React.useState(() => ctx.storage.get("runCount", 0));
return (
<div className="view-body" style={{ padding: 24 }}>
<button
className="btn"
onClick={async () => {
const next = count + 1;
setCount(next);
ctx.storage.set("runCount", next);
const ok = await ctx.terminal.writeActive(`echo "hello from plugin #${next}"\n`);
if (!ok) {
ctx.ui.toast.error({ title: "没有活跃的终端" });
}
}}
>
向活跃终端写入一行
</button>
</div>
);
}
export default definePlugin({
manifest,
activate(ctx) {
ctx.ui.registerView({
id: "hello-world.panel",
icon: Smile,
labelKey: "panelTitle",
ns: "plugin-hello-world",
component: () => <Panel ctx={ctx} />,
});
ctx.ui.registerCommand({
id: "hello-world.sayHi",
group: "Hello World",
title: "Say Hi",
icon: Zap,
run: () => ctx.ui.toast.info({ title: "Hi from hello-world" }),
});
// …registerSettingsSection / registerShortcut / i18n.addBundle
},
});