跳转到内容

下载 Termii · v0.4.5

三分钟,装进你的 Dock

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

安装说明

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

流式进程与 sidecar

「跑命令并持续消费输出」是插件最常见的高级需求。宿主提供两条路径: 流式进程(ctx.process / hosts.execStream,纯 JS 即可)和 sidecar(随包分发的原生二进制,需要原生能力时用)。

流式进程(ctx.process / hosts.execStream)

Section titled “流式进程(ctx.process / hosts.execStream)”

hosts.exec / execLocal 是一次性的:等进程退出拿全部输出,撑不起 日志 follow、拉取进度、事件流。流式原语(本地进程与 SSH exec 通道同构) 返回同一个 ProcessHandle:

process: {
spawn(cmd: string, args: string[], opts?: {
cwd?: string;
env?: Record<string, string>;
}): Promise<ProcessHandle>;
}
hosts: {
execStream(hostId: string, command: string): Promise<ProcessHandle>;
}
interface ProcessHandle {
readonly id: string;
write(data: string): Promise<void>; // 写入 stdin(本地进程 / SSH 通道同构)
kill(): Promise<void>; // 本地 kill 子进程;SSH 关闭 exec 通道
onData(cb: (chunk: ProcChunk) => void): Disposer;
onExit(cb: (info: { code: number | null; error?: string }) => void): Disposer;
}
interface ProcChunk {
seq: number;
data: string;
stream: "stdout" | "stderr"; // stdout / stderr 分流
}

要点:

  • process.spawn 以 argv 方式拉起本地进程(不做 shell 展开;需要 shell 语义时显式 spawn("/bin/sh", ["-c", cmd]))。
  • chunk 带 stdout / stderr 分流——构建进度通常在 stderr,解析必须 分流。
  • kill() 语义:本地终止子进程;SSH 关闭 exec 通道(远端进程收到 EOF / SIGPIPE)。
  • 能力门禁(L1):spawn 与 execStream 都要求用户授予 process 能力(未获授予即明确报错,插件其余功能可用)。
  • 孤儿防护:插件去激活时,宿主强制回收其名下全部进程,无需插件 自行清理。
const handle = await ctx.process.spawn("docker", ["logs", "-f", id], { cwd: "/" });
const offData = handle.onData(({ data, stream }) => {
if (stream === "stderr") logPanel.append(data, "err");
});
const offExit = handle.onExit(({ code, error }) => {
offData();
offExit();
if (code !== 0) ctx.ui.toast.error({ title: `进程退出 ${code ?? error}` });
});

取消语义:一次性命令的取消(如旧实现里的 *_stop(opId))在流式模型里 变成 handle.kill() + AbortController,模式一一对应。

sidecar 让插件把「需要原生能力」的部分(系统调用、新协议、重计算)放进 一个随包分发的本地二进制,宿主以子进程托管,插件经 ctx.sidecar.call 调用。它是信任模型的升级而非替代:普通外部插件仍是纯 JS;声明 sidecar 的插件需要用户单独确认「允许运行原生代码」。

{
"id": "sysinfo",
"apiVersion": 3,
"capabilities": ["sidecar"], // 声明 sidecar 隐含 process(L1)
"sidecar": {
"binaries": {
"darwin-aarch64": "bin/sysinfo-darwin-aarch64",
"darwin-x86_64": "bin/sysinfo-darwin-x86_64",
"windows-x86_64": "bin/sysinfo-windows-x86_64.exe"
},
"args": [] // 可选,固定启动参数
}
}

当前平台无对应二进制 → 插件可加载,但 ctx.sidecar.call 返回明确 错误(见下)。

  • 生命周期:宿主在插件激活时拉起子进程,去激活 / 卸载 / 应用退出时 回收。进程异常退出(退出码非 0)→ 标记该插件 sidecar 不可用,前端 toast 提示。
  • 协议:stdio 上 NDJSON 编码的 JSON-RPC 2.0,一行一条请求 / 响应:
    • 请求 {"jsonrpc":"2.0","id":<u64>,"method":"...","params":{...}}
    • 响应 {"jsonrpc":"2.0","id":<u64>,"result":...} 或 {"jsonrpc":"2.0","id":<u64>,"error":{"code":..,"message":".."}}
    • id 由宿主递增分配,按 id 关联应答;超时(默认 10s,call 可传) reject。
  • 健康信号:宿主以进程存活为健康信号——二进制侧不需要回应 keepalive,崩溃即整插件报错。
sidecar: {
call<T = unknown>(method: string, params?: unknown, timeoutMs?: number): Promise<T>;
}
// 返回二进制回应的 result(Rust 侧 JSON 反序列化后透传)
const info = await ctx.sidecar.call("getSystemInfo", { extra: true }); // params 可选
const slow = await ctx.sidecar.call("heavyJob", { n: 1e6 }, 30_000); // timeoutMs 可选,默认 10000

错误分支(各自抛明确错误,插件应捕获并展示):

场景 行为
manifest 未声明 sidecar 报「插件未声明 sidecar」
已声明但未获用户确认(L1 未授予) 报「未获用户确认运行原生代码:请到设置 → 插件确认信任」
当前平台无对应二进制 报「当前平台无对应二进制(需要 <os>-<arch> 键)」
子进程未能启动 / 崩溃 整插件报错,call reject

官方模板的 sidecar-sysinfo/ 是可运行的 sidecar 示例:极简二进制实现 getSystemInfo 方法,插件视图调用 ctx.sidecar.call("getSystemInfo") 并渲染。构建 / 安装说明见该模板 README。

场景 选择
调用已存在的 CLI / 命令(docker、git、ssh …) ctx.process.spawn / hosts.execStream
需要系统调用、新协议、重计算,没有现成命令 sidecar 二进制
只是想「拿个输出」 hosts.exec / execLocal 即可,无需流式