# @michinarai/sdk 中文文档

规范地址：https://npmcn.com/package/%40michinarai/sdk/

版本：0\.3\.4
npm 元数据更新时间：2026-11-01T20:24:08.438Z
中文译文更新时间：2026-11-01T21:13:32.482Z
软件包许可：Apache-2\.0

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

原始 npm 页面：https://www.npmjs.com/package/%40michinarai/sdk
原文对照：https://npmcn.com/package/%40michinarai/sdk/?lang=original
项目主页：https://github.com/Monster-Orange/mo-narai-sdk#readme
源码仓库：https://github.com/Monster-Orange/mo-narai-sdk

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

- 包体积（解压后）：585 kB，77 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=20
- 运行依赖：1 个
- 首次发布：2026-10-31T06:16:32.962Z
- 最新版本发布：2026-11-01T20:24:08.134Z
- 已发布版本数：10（近 12 个月 10 个）

## 中文简介

构建在Narai课程中运行的工具：清单、嵌入运行时和本地测试工具。

## 安装

```sh
npm install @michinarai/sdk
```

也可以使用：`pnpm add @michinarai/sdk`、`yarn add @michinarai/sdk`、`bun add @michinarai/sdk`

## README

# @michinarai / sdk

构建在Narai课程中运行的工具。

一个工具是一个小的web应用程序，Narai将其安装在自己的原始沙盒框架中。它显示了学习者的能力 乐器（指板、色轮、画布）并报告学习者所做的。Narai保持着 回答问题，运行每个裁判，拥有学习者模型：工具从不评分，也从不知道学习者是谁 是，除非学习者将他们的帐户与工具的发布者联系起来，并选择让工具知道他们。

-   **学习:** 这门课 [为Narai做一个工具](https://beta.michinar.ai/go/course/narai-tools) 从一个空文件夹 到你的第一个工具，一步一步来。
-   **开始:** 命令行， [`@michinarai/cli`](https://github.com/Monster-Orange/mo-narai-cli) （`narai init`， `narai dev`， `narai validate`， `narai build`）。
-   **在课堂上放一个：** 一个Narai账户和开发者测试版 `narai login`， `narai tools register` 和 `narai tools push`． 步骤是CLI的 [获得](https://github.com/Monster-Orange/mo-narai-cli#getting-access)． 建筑和当地的线束需要 没有帐户。
-   **链接帐户:** 拥有自己账户的出版商可以让学习者将它们链接起来，并提供Sign in with Narai； [账户链接](https://github.com/Monster-Orange/mo-narai-sdk/blob/main/docs/account-linking.md).
-   **阅读真实的：** [mo-narai-labs](https://github.com/Monster-Orange/mo-narai-labs)Narai自己的工具（指板） 一个间歇训练器，一个节奏敲击器)，只建立在这个包。

## 安装

```sh
npm install @michinarai/sdk
```

每个版本都是从这个存储库的发布工作流中发布的，并且会显示出处（npm会显示出处） 提交它已构建)。每一个变化都在 `CHANGELOG.md`.

## 你的第一个工具

清单说明工具显示和报告的内容； `connect` 等待Narai的主人； `answer` 报告学习者 所做的。

```js
// tool.js, loaded by your page as <script type="module" src="./tool.js">
import { connect, defineTool, pickLocale } from "@michinarai/sdk";

const manifest = defineTool({
  id: "you/hello", version: "0.1.0", sdk: "^1",
  publisher: { name: "You", url: "https://example.com" },
  surfaces: { embed: { url: "https://example.com/hello/", sdk: "^1" } },
  payloads: { "hello.answer@1": { type: "object", properties: { text: { type: "string" } }, required: ["text"] } },
  answers: { "hello.say": "hello.answer@1" },   // a task kind, and the payload its answer comes in
  locales: ["en", "ja"],
  capabilities: { audio_out: false, microphone: false, network: "none" },
});

const tool = await connect(manifest);                  // only Narai's host may connect
tool.ready(pickLocale(tool.start, manifest.locales));  // rendered, in the learner's language if the tool has it
document.querySelector("#say").onclick = () =>
  tool.answer({ schema: "hello.answer@1", text: "hello" });   // what the learner did, never whether it's right
```

`surfaces.embed.url` 是您的页面所在的位置：您托管的https URL，或工具的一个文件(`index.html`) Narai主机在工具自己的起源。页面导入 `@michinarai/sdk` 按名称：使用任何捆绑器或导入映射。 `narai init` 将所有这些（清单）写入 `narai.tool.json`（页面，构建）。

## 运行它

`narai dev` 将工具安装在当地的马具上，就像一节课一样：纳莱的主人 `http://localhost`, 工具在沙盒框架上 `http://127.0.0.1`． 马具显示每条消息及其判决，并发送 按照导师的要求来执行命令。从代码来看，它是 `startHarness` （下面是挽具）。

## API，在一个地方

```ts
import { connect, defineTool, pickLocale } from "@michinarai/sdk";

const manifest = defineTool({ id: "you/echo", version: "0.1.0", sdk: "^1", /* … */ }); // throws a ManifestError, every reason
// or a manifest kept as JSON: defineTool(json) checks it at runtime and returns a ToolManifest
const tool = await connect(manifest);   // takes Narai's host (NARAI_HOST_ORIGINS), waits CONNECT_TIMEOUT_MS

tool.start;                       // the locale, its fallbacks, the step's preset, the task (if Narai judges one)
tool.start.subject;               // only when the learner linked their account with your publisher and chose to let
//   its tools know them (the manifest declares surfaces.account_link): their id with you, the same in every tool you
//   publish. Never a name, an email or a Narai id; absent otherwise, so a tool works without it.
//   how a publisher links accounts: the Account linking guide (top of this page)
tool.onCommand("highlight", async (payload) => { /* only commands the manifest declares arrive */ });
//   Narai is answered once the handler is done: an ack, or an error when it throws (`failed`);
//   throw new CommandRefused("unsupported", "No capo on this neck.") to say why it wasn't done
tool.state({ schema: "echo.state@1", shown: "hello" });  // what the tool shows, in the manifest's `state` payload;
//   sent on every change, and the latest answers Narai's requests. Never anything about the learner
await tool.storage.set("last.tempo", { bpm: 90 }); // this launch's own storage, kept by Narai: a key, any JSON ≤ 16 KB
await tool.storage.get("last.tempo");               // { ok: true, value } or { ok: false, error: "not_found" | "quota" | "denied" }
const locale = pickLocale(tool.start, manifest.locales); // the first of the learner's locales the tool has, else its default
tool.onLocale((message) => { /* the learner switched language: pickLocale(message, manifest.locales) */ });
tool.ready(locale);               // rendered, in the locale actually shown
tool.answer({ schema: "echo.answer@1", text: "hello" });   // what the learner did on the task; never a verdict
tool.practice("drill", { duration_s: 30, attempts: 4, payload: { schema: "echo.drill@1", said: 4 } });
tool.close("done");
```

### 电线

握手：工具说 `tool.hello` 当它在听的时候；纳莱的主人在窗户上回答 `narai.tool.connect` 和一个 `MessagePort`，只来自列明的产地；以后的每条消息都在端口上传播。一个工具 重新加载的帧再次说hello，并在新的端口上再次连接到相同的启动（其存储空间保留） 出发送再来，准备说再来)；老港口关闭了，所以只有一个还活着。从其他页面发来的问候 起源从来没有答案。双方 对照信封检查每封邮件(`schema/envelope.schema.json`)和清单，并拒绝它不需要的东西 声明：它没有列出的命令，另一个模式中的有效负载，判决(`correct`， `score`， `passed` 任何地方…) 在回复中，一个超过64kb的有效载荷，一条关于另一次发射的消息。

有效载荷是 `{ schema: "<family>.<name>@<N>", ...body }`． 清单在其下声明的JSON模式 `payloads` 描述了 这个 **身体只有**： SDK需要 `schema` 在检查它之前关闭它，所以一个模式需要 `schema` 每个有效载荷都失败 （`manifestWarnings()` 这么说)。

```json
"payloads": { "echo.answer@1": { "type": "object", "properties": { "text": { "type": "string" } }, "required": ["text"] } }
```

Narai的主人需要10分钟的时间 `tool.practice`，然后每分钟6次，每次发射：批量练习(多余的被拒绝， 和计算)。

每一个 `narai.tool.command` 携带一个 `id`，工具回答了一次： `tool.command.ack`，或者 `tool.command.error` 带有代码(`unknown_command`， `invalid_payload`， `unsupported`， `failed`）. SDK会帮你发送这些；主机的 `command()` 求解到答案，并求解到 `failed` 后 `COMMAND_TIMEOUT_MS` （10秒）没有。 `narai.tool.state.get` 请求工具的状态，该状态返回为 `tool.state` 命名请求(`re`）。

发射器的存储： `tool.storage.get|set|delete` 和身着传统的 `narai.tool.storage.result`． 宿主控制着它 启动时，永远不要一个工具的名字；键是 `^[a-z0-9._-]{1,64}$`，值是不超过16kb的任何JSON，启动值为 最多64个键和256 KB。主机通过 `storage` 来 `mountTool`： `httpStorage({ base })` 通过Narai的API，或者 `memoryStorage()` (利用)。

`@michinarai/sdk/payloads` 根据清单声明的JSON模式检查有效负载主体(它对它们进行编译，因此它是 对于主机、线束和CLI，其中允许使用eval)。

**谁可以连接。** 一个工具只从它的父窗口进行连接，在Narai的起源之一： `NARAI_HOST_ORIGINS` （`https://michinar.ai`， `https://beta.michinar.ai`)，或 `hostOrigins` 你通过。一个工具 发展，从这台机器或以下服务 `local.michinar.ai`，也乘坐当地的奈良 （`https://local.michinar.ai`)和主机；其他地方的工具永远不会。 `connect` 放弃后 `CONNECT_TIMEOUT_MS` (10; `timeoutMs: Infinity` 等待)，Narai的主机等待的时间与它说工具没有等待的时间相同 负载。

**身着传统的主机** 导入 `@michinarai/sdk/host`： `mountTool`， `toolOrigin`， `CONNECT_TIMEOUT_MS` 和 `NARAI_HOST_ORIGINS`，而不是工具的运行时。严格CSP下的主机(no `'unsafe-eval'`)不能编译 有效负载模式，所以它离开了 `payloads` 不设置并依赖于服务器，它验证每个答案和练习 报告身体与舱单的对比。其他的一切(信封，公布的姓名和图式，判决禁令， 大小上限（这个启动和任务，首先准备好）由预编译代码检查并保持在那里。

## 测试工具

`@michinarai/sdk/testing` 启动一个工具的会话对Narai自己的主机在一个真实的 `MessageChannel`像电线一样 浏览器连接iframe：

```ts
import { launch } from "@michinarai/sdk/testing";

const run = await launch(manifest, { locale: "ja", task });
run.tool.ready("ja");
run.tool.answer({ schema: "echo.answer@1", text: "hello" });
await run.settle();
run.received.answers;   // what the host took; run.received.refused: what it didn't, with the reason
run.forge({ type: "tool.answer" /* … */ });   // straight onto the port, around the SDK
run.postToTool({ type: "narai.tool.connect", v: 1 }, { origin: "https://evil.example" });   // as another page
run.postToHost({ type: "tool.hello", v: 1 });   // on the host's window, as the frame (or { origin, source })
run.connects;           // where the host has posted a connect: once, at the tool's origin
run.close();
```

## 这个装具模块

```ts
import { startHarness } from "@michinarai/sdk/harness";

const harness = await startHarness({ manifest, toolDir: "dist", locale: "ja", task });
// harness.hostUrl: Narai's host on http://localhost; the tool on http://127.0.0.1 in a sandboxed iframe
```

主机页面显示每条消息，上面有马具的判决，并以导师的身份发送清单的命令 会。 `narai dev` 运行它。

## 模式

`schema/manifest.schema.json` 和 `schema/scopes.json` 在mo-narai中生成(参见 `schema/README.md`);从来没有编辑 他们在这里。 `schema/envelope.schema.json`在服务器像清单一样生成之前，电汇信封一直在这里 然后它，和判决词在一起 `src/verdicts.ts`，也来自mo-narai)。 `npm run generate` 写 `src/generated/` （类型和预编译的验证器）；陈旧的测试会失败。

## 开发

```sh
npm ci
npm test
npm run lint
npm run typecheck
```

## 稳定

0.合约（清单、连接、注册中心的API）仍然可以在小版本之间更改 更新日志(`CHANGELOG.md`)说怎么做。1.0会hold住它。

## 释放

npm的可信发布：没有npm令牌，无论是在CI中还是在其他任何地方。工作流程如下 `.github/workflows/release.yml`.

**每一个版本。** 合并一个有问题的PR `version` 在包中。Json(并说明在 `CHANGELOG.md`): 释放。每次推送到main时，工作流都会询问npm该版本是否已发布。当它不是，它 工作流运行闸门，打包tarball及其 `SHA256SUMS`，标记提交。 `v<version>`， **发布** 它在NPM上 超过OIDC (`npm publish`)，并在GitHub上发布这两个文件。一个已经在npm上的版本：什么都不会发生。 没有人的步骤，没有标签可以推动，也没有什么需要批准。

**包的第一次发布，一次（完成：0.1.0）。** 可信发布不能创建包，因此维护者 用自己的机器手工出版了第一个版本：

1.  手动运行工作流（操作›释放›运行工作流）。这是一次演习，它能保留焦油和 `SHA256SUMS` 随着 `release` 工件。下载它。
2.  在那个文件夹里： `sha256sum -c SHA256SUMS` (在Mac上， `shasum -a 256 -c SHA256SUMS`),然后 `npm login --auth-type=web && npm publish <tarball> --access public && npm logout`.
3.  在npmjs.com上，软件包›设置›可信发布者›GitHub Actions：组织 `Monster-Orange`、存储库 `mo-narai-sdk`、工作流 `release.yml`，允许直接发表。
4.  同一页面›发布权限： **要求双因素身份验证，不允许令牌**． 信任的出版 仍能工作。

思想:

-   可信发布者字段是区分大小写的，npm在保存它们时不会检查它们。不匹配失败 只有在发布时间，作为一个 `E404` 看起来像是丢失了一个包裹。package.json的 `repository.url` 一定是这个 存储库。
-   新的可信发布者配置必须在2天内发布，否则将过期。
-   NPM信任工作流的文件名， `release.yml`：重命名会破坏发布 `E404`)，直到 受信任的发布者更改为匹配。
-   来源是on的：npm在可信发布下为公共存储库和公共包生成它 国旗。package.json的 `repository.url` 是NPM检查它的依据。一次性第一 从维护人员的机器上发布则没有。

## 常见问题

### @michinarai/sdk 是什么？

构建在Narai课程中运行的工具：清单、嵌入运行时和本地测试工具。

### 如何安装 @michinarai/sdk？

在项目目录运行 npm install @michinarai/sdk，或使用 pnpm add / yarn add / bun add。

### @michinarai/sdk 支持 TypeScript 吗？

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

### @michinarai/sdk 支持 ESM 还是 CommonJS？

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

### @michinarai/sdk 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=20。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @michinarai/sdk 可以商用吗？

@michinarai/sdk 使用 Apache-2\.0 许可。宽松许可：允许商业使用、修改和再分发，并附带明确的专利授权；分发时需保留许可与 NOTICE 文件，修改过的文件要注明改动。以上为概括，不构成法律意见，请以许可原文为准。

### @michinarai/sdk 还在维护吗？

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

