# @fungi\.computer/whistle 中文文档

规范地址：https://npmcn.com/package/%40fungi.computer/whistle/

版本：0\.3\.0
npm 元数据更新时间：2026-10-28T12:21:36.465Z
中文译文更新时间：2026-11-15T18:01:12.361Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40fungi.computer/whistle
原文对照：https://npmcn.com/package/%40fungi.computer/whistle/?lang=original
项目主页：https://github.com/fungi-computer/whistle#readme
源码仓库：https://github.com/fungi-computer/whistle

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：215 kB，21 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：1 个
- peer 依赖：@opentui/keymap 0\.5\.10
- 首次发布：2026-10-15T07:59:26.609Z
- 最新版本发布：2026-10-28T12:21:36.055Z
- 近 30 天下载量：17576（2026-10-16 至 2026-11-14，npm 官方下载统计）
- 已发布版本数：6（近 12 个月 6 个）

## 中文简介

语义命令图、预测和线解析

## 安装

```sh
npm install @fungi.computer/whistle
```

也可以使用：`pnpm add @fungi.computer/whistle`、`yarn add @fungi.computer/whistle`、`bun add @fungi.computer/whistle`

## README

# 口哨声( W)`@fungi.computer/whistle`(中文(简体) )

Whistle是应用软件的输入命令图,它在许多程序中暴露了相同的动作 地点。 你写一个命令定义。 口哨有六个不可改变的观点 从它:密钥绑定、命令调色板、斜线命令、帮助、菜单和代理 查看 AI 工具。 每个呼叫者,从按键到代理,都执行命令 通过一个键入 `execute` 门并获得一个输入结果返回.

当键盘快捷键、调色板条目、a `/slash` 命令和一个 代理工具应该都意味着相同的动作,显示相同的可用性,并且失败 同样的方式。 Whistle拥有命令图. 您的主机保持密钥匹配, 制作、运输、授权和真实工作.

## 安装或设置

```sh
npm install @fungi.computer/whistle
```

口哨是一个以Zod为唯一运行时间依赖的无害环境管理软件包. 它运行在 节点,浏览器,和工人。 它有五个切入点:

-   `@fungi.computer/whistle`运行时间, `scopeSnapshot`,键图适配器, 和命令类型.
    
-   `@fungi.computer/whistle/wire`: Zod schemas and 剖析命令 定义、提示、通知和迅速回答.
    
-   `@fungi.computer/whistle/interaction`:一个渲染器中性交互存储.
    
-   `@fungi.computer/whistle/catalog`:解析后端 `whistle.catalog/v1` 命令目录并将其变成命令.
    
-   `@fungi.computer/whistle/html`:将命令预测绑定在一个 DOM 键图上. HTML 用户安装可选端口 `@opentui/keymap@0.5.10`; 核心用户这样做 不需要这个.
    

## 快开始

这将创建运行时间, 向调色板和斜线贡献一个命令 表面,读取快照,并运行命令:

```ts
import { createWhistle } from "@fungi.computer/whistle";

const whistle = createWhistle();

const lease = whistle.contribute({
  sourceId: "editor",
  namespace: "editor",
  commands: [
    {
      id: "save",
      title: "Save file",
      category: "File",
      projections: {
        palette: {},
        slash: { aliases: ["save"] },
        bindings: { values: ["mod+s"] },
      },
      handler: () => ({ saved: true }),
    },
  ],
});

const snapshot = whistle.snapshot();
console.log(snapshot.palette.map((row) => row.command)); // [{ namespace: "editor", id: "save" }]
console.log(snapshot.slash.map((row) => row.alias)); // ["save"]

const outcome = await whistle.execute(
  { namespace: "editor", id: "save" },
  { origin: "palette" },
);
if (outcome.status === "handled") console.log(outcome.result); // { saved: true }

lease.dispose();
whistle.close();
```

命令以其输入的密钥命名, `{ namespace, id }`。每个投影行 带着那个键作为 `command`,所以点击调色板、按键和斜线 命令全部调用相同的 `execute` 带着它 一行货 `commandId` 是一个不透明 制取密钥 Whistle mints: 用于反键或去dupe, 绝不构建或解析 这个 命名空间可能包含冒号( App 表面, 如 `installation:inkcap`);命令id可能不会.

### 捆绑浏览器键盘

```sh
npm install @opentui/keymap@0.5.10
```

```ts
import { createWhistle } from "@fungi.computer/whistle";
import { bindHtmlKeymap } from "@fungi.computer/whistle/html";

const whistle = createWhistle();
whistle.contribute({
  sourceId: "browser",
  namespace: "browser",
  commands: [
    {
      id: "save",
      title: "Save",
      category: "File",
      projections: { bindings: { values: ["mod+s"] } },
      handler: () => ({ saved: true }),
    },
  ],
});
const keyboard = bindHtmlKeymap(document.body, whistle, "keyboard");
// Contributions and availability updates refresh the projected key layer.
keyboard.dispose();
whistle.close();
```

`dispose` 删除 Whistle 层和订阅,可以两次调用. HTML 密钥映射主机遵循文档的寿命; 此帮助器 不删除它的 DOM 听器。 命令仍然通过Whistle执行.

## 核心概念

### 指挥、捐助和租赁

命令有一个 `id`页:1 `title`页:1 `category`,可选 `description`页:1 `handler`选择 `projections`, (中文(简体) ), `action` 财务报告和审定财务报表 `availability`命令 只出现在表面 `projections` 名称.

`contribute` 接受一组命令 `sourceId` 变成一个 `namespace`,可选择在焦点内 `scope` 例如打开一个窗口. 招生是原子:全部贡献在任何贡献去之前都经过验证 活着 当一个命名空间已经属于另一个源, 当一个 同一范围内的犬形ID或斜口别名重复,或当一个字段是 空无一物.

每一笔捐款都为这确切的一代人提供租约. `lease.update` 从解剖角度交换命令. `lease.dispose` 将取去. 处理废品 租借从不取消替换。 两通电话都是一流的.

### 一. 快照和预测

`snapshot()` 返回六个被冻结的数组: `bindings`, (中文(简体) ), `palette`, (中文(简体) ), `slash`, (中文(简体) ), `help`, (中文(简体) ), `menu` 财务报告和审定财务报表 `agent`。行是不可改变的副本。 每个表面的种类 依其 明确 `order`,然后根据UTF-8字节顺序中的命令ID,所以命令永远不会 取决于插入。 一个快照保持其身份直到图表或一些 可用性变化. `subscribe(listener)` 当图表改变时告诉你.

代理视图下降 `presentation` 元数据并保留语义 `inputSchema` 财务报告和审定财务报表 `outputSchema`,所以一个代理人得到工具描述,从不 a 用户界面布局. `scopeSnapshot(snapshot, scope)` 给人类和代理人 一个重点范围:全球行外加范围行外加重点斜线 化名或绑定同一全球的阴影。 代理行携带其范围 遵循相同的过滤器。 完整的运行时间快照仍然列出所有瞄准镜.

### 执行

`execute(command, input)` 只有这样 操作员才能运行. `input.origin` 是一个 八个封闭的起源: `agent`, (中文(简体) ), `browser`, (中文(简体) ), `keyboard`, (中文(简体) ), `menu`, (中文(简体) ), `palette`, (中文(简体) ), `programmatic`, (中文(简体) ), `slash` 财务报告和审定财务报表 `terminal`其结果是:

-   `handled`:处理器完成. 返回值为 `result`这样做 并非意味着实际工作或完成的持久工作流程.
-   `unavailable`: 命令内容 `availability()` 说不。 其由回还.
-   `failed`: 处理器扔了. `error` 这是JSON: `{ code, message, details? }`。 。 。 .
-   `missing`:没有现场指挥部有身份证.
-   `invalid`:命令ID为空白.
-   `closed`:运行时间先被关闭.

### 可用性

命令可以提供同步 `availability()` 浏览当前主机 事实. `snapshot()` 将无法使用的命令按其理由显示,以及 `execute()` 在拨打处理器前, 重新读取可用性 . 可用性是一种便利检查。 这不是授权,所以处理者 还是接受真正的要求 主持人更新自己的事实并出版 自己联系的快照 口哨没有投票或广播.

### 失败

掷出 `WhistleActionError(code, message, details?)` 选择失败代码 并附上JSON的细节。 任何其他 `Error` 变成 `command_failed`堆叠和 非错误抛出值从不交叉执行结果.

### 互动

运行中的命令可以和它的人通话 通过 `input.interaction`。 。 。 . `prompt` 询问一个 `choice`, (中文(简体) ), `text` (可选择) `secret`)或(或) `confirmation` 问. `notify` 显示 `info`, (中文(简体) ), `progress`, (中文(简体) ), `handoff` (可选择打开URL) 输入代码)或 `success`。词汇名称是答案的类型,从不 部件.

呼叫者可以通过自己的交互. 否则 `present(presenter)` 登记册 上面的客户端 打开一个 用于每次人种处决。 探员和 计划性处决没有结果。 口哨解决会话 `handled`, (中文(简体) ), `cancelled` 或者说 `failed` 当处理器结束。 最新介绍者获胜直到 其处置者运行.

`createWhistleInteractionHost` 从 `./interaction` 是一个渲染器中性存储 一个互动。 它分析每一个迅速,注意和回答 `./wire` schemas,以及一个终端或浏览器渲染器绘制它的快照.

在回答或再次尝试后,主机重用当前进度消息,或 当制片人不提供时, 命令标题位于已运行行中. 这不会为内在操作打开一个对话 从未要求过 问或显示进展/移交.

`interaction.run(operation)` 标记一个序列操作为可重试。 一旦 对话已经提问或显示进度/交接,失败是 显示并等待 `host.retry()` 或开除。 在此之前,它传播 通常:失败的内线动作不会突然打开对话. 重试 仅以抓取的输入重复此操作; 原处理器保留 暂停,所以没有重播先前成功的步骤和问题。 开除 中止等待。 外部失败 `run` 正常结束处理器而不结束 广告整个命令的回放。 仅通知的相互作用 执行 `run` 一次并传播失败, 不进行交互重试等待 .

### 斜线参数

斜线投影可以携带 `arguments`,然后是文本的仅数据语法 还有别名 固定 `words` 用于完成参数对象的映射. `text` 剩下的放进去 字段,可选择与请求 身份重复的ID字段。 客户端分析, 完整并提示从此 数据,所以没有客户端在代码中命名命令.

### 目录

后端将其命令作为广告 `whistle.catalog/v1` 相同的目录 数据定义. `parseCatalog` 在客户端边界验证它. `catalogCommands(catalog, invoke)` 把它变成命令 处理者呼唤  `invoke` 函数。 每项答复都是一个步骤: `done` (非强制性通知和 结果)或 `prompt` (一个问题加上下一个电话的全部论点, 答案将输入一个指定字段。 多步骤流动仍然处于无国籍状态 电线和运行 通过呼叫者的互动。 每个目录引用是 一个可重试的操作。 保留非秘密答复和请求身份; 秘密答案不会 续作保留了秘密问题, 在每次尝试中询问他们,并删除他们的价值观 有效载荷一旦解决,无论是成功还是失败。 只重试重试 那些秘密问题,不是早期的非秘密选择。 如果稍后的后端步骤 仍然需要一个秘密,它必须再次输入 引用这一点.

### 闭幕

`close()` 是同步的,是一元的,返回 `undefined`。第一个电话 释放每一个活性贡献,并留下共享的空快照。 租赁 之前的发行 变得无害。 一个 `execute` 答应你回来之前 `close` 仍然安顿下来,可能安顿下来 `handled`。稍后时间 `execute` 返回时 `closed`,以及 后一个有效 `contribute` 掷出 `WhistleClosedError`。 。 。 .

### 打入可重复使用的处理器

使用索引访问于 `WhistleCommand` 在命令之外声明处理器 字义 :

```ts
import type { WhistleCommand } from "@fungi.computer/whistle";

type SaveArguments = Readonly<{ path: string }>;
type SaveResult = Readonly<{ saved: string }>;

const save: WhistleCommand<SaveArguments, SaveResult>["handler"] = ({
  arguments: input,
}) => (input === undefined ? undefined : { saved: input.path });
```

处理者收到 `WhistleExecutionInput<SaveArguments>`:来源, 可选参数、取消信号和可选互动。 这个 返回时 `SaveResult`, (中文(简体) ), `undefined`或承诺其中之一. `WhistleCommandHandler` 名称此召回类型。 不同命令 参数和结果类型可共享 `readonly WhistleCommand<never>[]` 捐款。 贡献在运行时去掉这些普通字,所以处理器 仍然验证它实际得到的论据.

### 演示文稿提示

`action.inputSchema` 财务报告和审定财务报表 `action.outputSchema` 描述结构数据并保存 不透明的JSON说明. `action.presentation` 是一个可选的本地提示 : `action`, (中文(简体) ), `choice`, (中文(简体) ), `form`, (中文(简体) ), `confirmation`,或 `custom` 以不透明 JSON 表示 拥有客户端自己的字段或手势绑定. 服务器能力不能 指定其形式、菜单或其他渲染。 人类的预测能保持暗示. 代理投影器放弃.

## API 参考

见生成 [API 参考](https://npmcn.com/package/%40fungi.computer/whistle/docs/api/README.md)覆盖根部 入境点和入境点 `wire`, (中文(简体) ), `interaction` 财务报告和审定财务报表 `catalog` 潜行道.

## 实例

### 显示命令为不可用

```ts
import { createWhistle } from "@fungi.computer/whistle";

let signedIn = false;
const whistle = createWhistle();
whistle.contribute({
  sourceId: "account",
  namespace: "account",
  commands: [
    {
      id: "sync",
      title: "Sync now",
      category: "Account",
      projections: { palette: {}, agent: {} },
      availability: () =>
        signedIn
          ? { status: "available" }
          : { status: "unavailable", reason: "Sign in first." },
      handler: () => undefined,
    },
  ],
});

console.log(whistle.snapshot().palette[0]?.availability);
// { status: "unavailable", reason: "Sign in first." }
console.log(
  await whistle.execute(
    { namespace: "account", id: "sync" },
    { origin: "agent" },
  ),
);
// { status: "unavailable", reason: "Sign in first." }
signedIn = true;
```

### 代码失败

```ts
import { WhistleActionError, createWhistle } from "@fungi.computer/whistle";

const whistle = createWhistle();
whistle.contribute({
  sourceId: "files",
  namespace: "files",
  commands: [
    {
      id: "open",
      title: "Open file",
      category: "File",
      handler: () => {
        throw new WhistleActionError("not_found", "No such file", {
          path: "a.txt",
        });
      },
    },
  ],
});

const outcome = await whistle.execute(
  { namespace: "files", id: "open" },
  { origin: "terminal" },
);
// { status: "failed", error: { code: "not_found", message: "No such file", details: { path: "a.txt" } } }
```

### 装入后端目录

```ts
import { createWhistle } from "@fungi.computer/whistle";
import { catalogCommands, parseCatalog } from "@fungi.computer/whistle/catalog";

const catalog = parseCatalog(await (await fetch("/commands")).json());
const whistle = createWhistle();
whistle.contribute({
  sourceId: "backend",
  namespace: "backend",
  commands: catalogCommands(catalog, async (commandId, args, signal) => {
    const response = await fetch(`/commands/${commandId}`, {
      method: "POST",
      body: JSON.stringify(args),
      signal,
    });
    return response.json();
  }),
});
```

### 检查

从一次检查中, 单元测试覆盖运行时间, 电线计划, 交互 主机和目录 :

```sh
pnpm --filter @fungi.computer/whistle test
```

## 限制和错误

-   运行时间最多可维持64个现场贡献,256个现场指挥.
-   一个命令最多有16个绑定值和16个斜切别名. 斜线语法 最多32个字.
-   一个目录最多有128个指令,一个目录流在16个步骤后停止 与 `flow_too_long`。需要快捷键的目录命令失败 `interaction_required` 当没有互动时,总是如此 用于代理和方案来源.
-   即时通知文本的上限为8192个字符。 选择提示 1 到 256个选项。 通知 URL 必须是 `http` 或者说 `https`。 。 。 .
-   命名空间不得含有冒号,并命令ID、标题、类别和 标签不得为空白。 违章行为 `Error` 从 `contribute` 或者说 `lease.update`。 。 。 .
-   这些界限是执行过程中的防御. 他们不是稳定的API 承诺.

口哨不解析原始文本中的按键或斜线输入, 渲染 UI, 运行 运输,或保持耐用交货状态。 它没有浏览器,终端,DOM, 反应、产品或授权状态,没有定时器或背景工作。 使用 页:1 `./wire` 解析未信任的输入, 然后再成为命令、 即时或 备注.

## 贡献和许可证

麻省理工学院. 见 [自由主义](https://npmcn.com/package/%40fungi.computer/whistle/LICENSE)。 。 。 .

## 常见问题

### @fungi\.computer/whistle 是什么？

语义命令图、预测和线解析

### 如何安装 @fungi\.computer/whistle？

在项目目录运行 npm install @fungi\.computer/whistle，或使用 pnpm add / yarn add / bun add。

### @fungi\.computer/whistle 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @fungi\.computer/whistle 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @fungi\.computer/whistle 可以商用吗？

@fungi\.computer/whistle 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @fungi\.computer/whistle 的下载量有多少？

根据 npm 官方下载统计，@fungi\.computer/whistle 在 2026/10/16 – 2026/11/14 这 30 天内被下载 17,576 次，日均约 586 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @fungi\.computer/whistle 还在维护吗？

最新版本 0\.3\.0 发布于 2026/10/28（18 天前）；至今共发布 6 个版本，其中过去 12 个月发布了 6 个；首次发布于 2026/10/15。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

