<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Blog on Kmoon Blog</title>
    <link>https://blog.kmoon.fun/blog/</link>
    <description>Recent content in Blog on Kmoon Blog</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    
      <managingEditor>hushan@kmoon.fun (Kmoon)</managingEditor>
    
    
      <webMaster>hushan@kmoon.fun (Kmoon)</webMaster>
    
    
    <copyright>Copyright © 2020-2026. Kmoon.</copyright>
    
    
    <lastBuildDate>Fri, 26 Jun 2026 13:10:41 +0800</lastBuildDate>
    
    
    <follow_challenge>
        <feedId>00000000000000000</feedId>
        <userId>00000000000000000</userId>
    </follow_challenge>
    
    <atom:link href="https://blog.kmoon.fun/blog/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>QUERY：全新的 HTTP 请求方法</title>
      <link>https://blog.kmoon.fun/query%E5%85%A8%E6%96%B0%E7%9A%84-http-%E8%AF%B7%E6%B1%82%E6%96%B9%E6%B3%95/</link>
      <pubDate>Fri, 26 Jun 2026 13:10:41 +0800</pubDate><author>hushan@kmoon.fun (Kmoon)</author>
      <guid>https://blog.kmoon.fun/query%E5%85%A8%E6%96%B0%E7%9A%84-http-%E8%AF%B7%E6%B1%82%E6%96%B9%E6%B3%95/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://kreya.app/blog/new-http-query-method-explained/&#34;&gt;The new HTTP QUERY method explained&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>不同 AI Coding Agent 对 Skills 的管理方式</title>
      <link>https://blog.kmoon.fun/%E4%B8%8D%E5%90%8C-ai-coding-agent-%E5%AF%B9-skills-%E7%9A%84%E7%AE%A1%E7%90%86%E6%96%B9%E5%BC%8F/</link>
      <pubDate>Mon, 22 Jun 2026 19:57:10 +0800</pubDate><author>hushan@kmoon.fun (Kmoon)</author>
      <guid>https://blog.kmoon.fun/%E4%B8%8D%E5%90%8C-ai-coding-agent-%E5%AF%B9-skills-%E7%9A%84%E7%AE%A1%E7%90%86%E6%96%B9%E5%BC%8F/</guid>
      <description>&lt;p&gt;前两天我在做一个工具，想统计一下自己每天到底调用了多少次 Claude Code 的 skill。&lt;/p&gt;&#xA;&lt;p&gt;听起来很简单对吧，就是做个上报，记录一下什么时候用了什么 skill，用了几次。但我刚开始写代码就懵了。&lt;/p&gt;&#xA;&lt;p&gt;Claude Code 的 skill 是本地 Markdown 文件，Cursor IDE 的 skill 藏在设置里的 JSON 配置，Codex CLI 的 skill 是 YAML 格式。我要适配所有 Coding Agent，却发现它们管理 skill 的方式完全是八套不同的逻辑。&lt;/p&gt;&#xA;&lt;p&gt;不是哥们，我就想记个日志，怎么各家差别能这么大？？？&lt;/p&gt;&#xA;&lt;p&gt;这件事让我意识到，如果你想做一个通用的 skill 调用上报工具，首先得搞清楚每家 Agent 在三个维度上的差异。&lt;/p&gt;&#xA;&lt;p&gt;存放路径在哪，怎么加载，以及最关键的，Hook 模型是什么样的。&lt;/p&gt;&#xA;&lt;p&gt;咱们一个个拆开了聊。&lt;/p&gt;&#xA;&lt;p&gt;先从 CLI 工具说起。&lt;/p&gt;&#xA;&lt;p&gt;Claude Code 是最直白的。它就放在项目目录下的 &lt;code&gt;.claude/skills/&lt;/code&gt; 文件夹里，每个 skill 是一个子文件夹，里面必须有一个 &lt;code&gt;skill.md&lt;/code&gt; 文件。&lt;/p&gt;&#xA;&lt;p&gt;这种设计的好处是，skill 就是项目代码的一部分。你把项目仓库 clone 下来，skill 跟着一起下来了，完全不需要额外安装。而且因为就在文件系统里，你想做监控很容易，用文件系统监听或者直接读目录结构都行。&lt;/p&gt;&#xA;&lt;p&gt;我跟你说，我做上报工具的时候，Claude Code 这块儿是最省心的。直接遍历 &lt;code&gt;.claude/skills/&lt;/code&gt; 目录，解析每个子文件夹里的 &lt;code&gt;skill.md&lt;/code&gt;，提取 frontmatter 里的 name 和 description，一个 &lt;code&gt;os.walk&lt;/code&gt; 就搞定了。&lt;/p&gt;&#xA;&lt;p&gt;Codex CLI 是 OpenAI 出的官方 Coding Agent，它的 skill 系统叫 Commands，配置放在 &lt;code&gt;.codex/commands/&lt;/code&gt; 目录下，每个 command 是一个 YAML 文件。&lt;/p&gt;&#xA;&lt;p&gt;Codex 也支持全局和项目级配置，全局的在 &lt;code&gt;~/.codex/commands/&lt;/code&gt;。Codex 的设计介于 Claude Code 和 Cursor 之间，它用 YAML 而不是 Markdown，保留了一些可读性，同时保持了结构化的优势。每个 command 可以定义参数、描述、还有可选的 hooks，比如在代码提交前自动执行。&lt;/p&gt;&#xA;&lt;p&gt;然后是 Trae CLI，也叫 CoCo，字节出的命令行工具。&lt;/p&gt;&#xA;&lt;p&gt;CoCo 的 skill 配置放在 &lt;code&gt;.coco/skills/&lt;/code&gt; 目录，格式是 TOML。TOML 比 JSON 友好，比 YAML 简单，CoCo 选择这个格式是有考量的。而且 CoCo 支持 skill 市场，你可以从远程仓库拉取别人分享的 skill，本地只是一个缓存。&lt;/p&gt;&#xA;&lt;p&gt;这种设计给上报带来了复杂性，因为 skill 可能来自远程，本地文件只是一个副本，版本可能随时变化。&lt;/p&gt;&#xA;&lt;p&gt;接下来是 Gemini CLI，Google 的命令行工具。&lt;/p&gt;&#xA;&lt;p&gt;Gemini CLI 的 skill 系统叫 Code Actions，配置方式很特别，它不是本地文件，而是 Google Cloud 上的资源。你在本地项目里看不到任何 skill 配置文件，所有的 skill 都定义在 Cloud Console 里，通过项目 ID 关联。&lt;/p&gt;&#xA;&lt;p&gt;这种云原生的设计意味着，做上报工具的时候，你必须调用 Google Cloud API 才能获取 skill 列表，本地文件系统帮不上忙。&lt;/p&gt;&#xA;&lt;p&gt;好，CLI 说完了，聊聊 IDE。&lt;/p&gt;&#xA;&lt;p&gt;Cursor IDE 也有 skill 系统，虽然它也在推 MCP，但 Cursor 本身有一套原生的 skill 配置。它的 skill 分为两类，一类是全局 skill，一类是项目级 skill。&lt;/p&gt;&#xA;&lt;p&gt;全局 skill 的配置存在 &lt;code&gt;~/.cursor/settings.json&lt;/code&gt; 里。项目级 skill 配置在项目根目录的 &lt;code&gt;.cursor/skills.json&lt;/code&gt; 里。&lt;/p&gt;&#xA;&lt;p&gt;注意，Cursor 的 skill 配置是纯 JSON，不像 Claude Code 是 Markdown。每个 skill 有一个 name、description、prompt，还可以定义什么时候触发。这种结构化的好处是解析简单，坏处是可读性差，写 prompt 的时候不能换行、不能用 Markdown 格式，一长串文本挤在 JSON 字符串里，看着头大。&lt;/p&gt;&#xA;&lt;p&gt;Trae IDE 是字节出的桌面端，它的 skill 系统叫智能体，配置放在 &lt;code&gt;.trae/agents/&lt;/code&gt; 目录下。每个智能体是一个 JSON 文件，但 Trae 的创新点是支持多轮对话模板，你可以定义一个完整的对话流程，而不只是一个静态 prompt。&lt;/p&gt;&#xA;&lt;p&gt;这种设计让 skill 更像是一个剧本，AI 按照预设的步骤和用户交互。对于我的上报工具来说，这意味着我需要解析更复杂的结构，因为每个 skill 可能包含多个步骤的触发点。&lt;/p&gt;&#xA;&lt;p&gt;说到 OpenCode，开源社区孵化的 Coding Agent。&lt;/p&gt;&#xA;&lt;p&gt;OpenCode 的 skill 系统是最激进的，它完全照搬了 Claude Code 的设计，放在 &lt;code&gt;.opencode/skills/&lt;/code&gt; 目录，也是 Markdown 格式。但它加了一个功能叫 skill 继承，一个项目可以继承另一个项目的 skill，形成层级结构。&lt;/p&gt;&#xA;&lt;p&gt;这意味着 skill 的来源不只是当前项目，可能还有父项目、全局配置。做上报的时候，你得递归解析整个继承链，才能知道最终有哪些 skill 可用。&lt;/p&gt;&#xA;&lt;p&gt;然后是 OpenClaw，飞书出的 Coding Agent。&lt;/p&gt;&#xA;&lt;p&gt;OpenClaw 的 skill 配置放在 &lt;code&gt;.openclaw/skills/&lt;/code&gt; 目录，格式是 JSON。但 OpenClaw 的特殊之处在于它和飞书生态深度绑定，skill 可以通过飞书多维表格来管理，本地文件只是同步下来的缓存。&lt;/p&gt;&#xA;&lt;p&gt;这种设计对上报工具很不友好，因为 skill 的真相在云端，本地文件可能过期，也可能被手动修改后还没同步。&lt;/p&gt;&#xA;&lt;p&gt;说到这儿，你可能会发现一个问题。&lt;/p&gt;&#xA;&lt;p&gt;CLI 和 IDE 在 skill 存放路径上有个本质区别。CLI 工具基本都是项目级配置，skill 文件就在项目里，跟着代码走。IDE 则既有全局配置又有项目级配置，而且往往把配置藏在用户目录或者云端。&lt;/p&gt;&#xA;&lt;p&gt;这个区别对上报工具的影响很大。CLI 工具的上报可以基于文件系统监听，IDE 的上报则需要同时监控多个位置，还要处理合并逻辑。&lt;/p&gt;&#xA;&lt;p&gt;好，路径聊完了，说说加载方式。&lt;/p&gt;&#xA;&lt;p&gt;这块儿才是真正考验适配难度的地方。&lt;/p&gt;&#xA;&lt;p&gt;Claude Code 的加载方式是静态扫描加热重载。启动的时候，它会递归扫描 &lt;code&gt;.claude/skills/&lt;/code&gt; 目录，读取每个 &lt;code&gt;skill.md&lt;/code&gt; 文件，解析 YAML frontmatter 和正文内容，然后把这些 skill 注册到内存里。而且支持热重载，你修改了 skill 文件，保存后立刻生效，不需要重启。&lt;/p&gt;&#xA;&lt;p&gt;对于我的上报工具来说，这意味着我需要监听文件系统变化，持续更新 skill 列表。&lt;/p&gt;&#xA;&lt;p&gt;Codex CLI 的加载也是静态扫描加热重载，和 Claude Code 类似。但它多了一个条件加载，支持在 command 里定义 when 条件，比如当文件是 Python 时才显示这个 command。这意味着即使扫描到了 skill，也不一定对所有文件都可用。&lt;/p&gt;&#xA;&lt;p&gt;CoCo 的加载需要区分本地 skill 和远程 skill。本地的是静态扫描，远程的需要调用 API 获取列表，还要处理版本缓存。如果网络不好，CoCo 会回退到本地缓存，这时候上报的数据可能不准确。&lt;/p&gt;&#xA;&lt;p&gt;Gemini CLI 的加载完全依赖网络，启动时调用 Google Cloud API 获取项目的 Code Actions。如果网络不通，Gemini 就完全没有 skill 可用。这种设计对上报工具来说，意味着必须处理各种网络异常。&lt;/p&gt;&#xA;&lt;p&gt;说到 IDE 的加载方式。&lt;/p&gt;&#xA;&lt;p&gt;Cursor IDE 的加载方式是静态扫描，但它不支持热重载。你修改了 &lt;code&gt;.cursor/skills.json&lt;/code&gt;，需要重启 Cursor 才能生效。而且全局 skill 和项目级 skill 是合并加载的，如果有同名 skill，项目级的会覆盖全局的。&lt;/p&gt;&#xA;&lt;p&gt;这种合并逻辑给上报带来了麻烦，因为你得知道最终生效的是哪个 skill。我的方案是启动时读取全局配置，然后读取项目配置，自己做合并逻辑，和 Cursor 保持一致。&lt;/p&gt;&#xA;&lt;p&gt;Trae IDE 的加载方式更复杂，因为它支持动态智能体。除了启动时扫描 &lt;code&gt;.trae/agents/&lt;/code&gt;，Trae 还可以在对话过程中动态加载新的智能体，基于上下文判断用户需要什么能力。这种动态加载给上报带来了挑战，因为 skill 列表不是固定的。&lt;/p&gt;&#xA;&lt;p&gt;OpenCode 的加载最复杂，因为要处理 skill 继承链。它需要先加载全局 skill，然后加载父项目的 skill，最后加载当前项目的 skill，每一层都可能覆盖上一层的同名 skill。这个过程是递归的，如果项目嵌套很深，加载时间会很长。&lt;/p&gt;&#xA;&lt;p&gt;OpenClaw 的加载需要同步飞书云端。启动时会检查本地缓存和云端的版本，如果有更新就下载。这意味着 skill 列表可能随时变化，上报工具需要持续监听同步事件。&lt;/p&gt;&#xA;&lt;p&gt;说到加载，必须提一嘴 skill 的概念。&lt;/p&gt;&#xA;&lt;p&gt;Claude Code 支持子 skill，你可以在一个 skill 文件夹里放多个子文件夹，每个子文件夹是一个子 skill。比如 &lt;code&gt;adk-sdd/&lt;/code&gt; 下面有 &lt;code&gt;adk-sdd-plan/&lt;/code&gt;、&lt;code&gt;adk-sdd-implement/&lt;/code&gt; 等。它们之间可以互相调用，形成一个完整的工具链。&lt;/p&gt;&#xA;&lt;p&gt;这种层级结构给上报带来了额外复杂度，因为你得递归扫描子目录，而且子 skill 的调用关系也得记录。&lt;/p&gt;&#xA;&lt;p&gt;OpenCode 也支持类似的层级结构，而且它还有 skill 继承，复杂度更高。其他 Agent 像 Cursor、Trae、Codex、CoCo 都不支持子 skill，是扁平结构。&lt;/p&gt;&#xA;&lt;p&gt;好，路径和加载都说完了，最后聊聊 Hook 模型。&lt;/p&gt;&#xA;&lt;p&gt;这是各家差异最大的部分，也是我做上报工具时最纠结的地方。&lt;/p&gt;&#xA;&lt;p&gt;Claude Code 的 Hook 模型，我总结为被动触发加语义匹配。你不需要显式调用 skill，Claude Code 会根据对话内容自动判断，把 skill 的 description 和 user message 做语义匹配，超过阈值就注入 prompt。&lt;/p&gt;&#xA;&lt;p&gt;这种模型的优点是交互自然，缺点是可控性差。做上报的时候，Claude Code 没有官方 Hook 点，我只能通过解析 &lt;code&gt;.claude/logs/&lt;/code&gt; 目录下的日志来间接推断 skill 是否被激活。&lt;/p&gt;&#xA;&lt;p&gt;Codex CLI 的 Hook 模型是命令式加事件式结合。Commands 可以显式调用，也可以通过定义的 hooks 在特定事件触发，比如 onSave、onCommit。做上报的时候，你需要同时监控用户命令和编辑器事件。&lt;/p&gt;&#xA;&lt;p&gt;CoCo 的 Hook 模型是关键词触发加场景识别，除了匹配关键词，CoCo 还会分析当前编辑场景，比如你在写测试，它就自动推荐测试相关的 skill。这种模型需要上报工具理解场景是怎么定义的。&lt;/p&gt;&#xA;&lt;p&gt;Gemini CLI 的 Hook 模型完全是黑盒，因为 skill 在云端，触发逻辑也由服务端控制。本地只能看到最终的 AI 回复，看不到中间过程。做上报基本不可能，除非 Google 开放 API。&lt;/p&gt;&#xA;&lt;p&gt;说到 IDE 的 Hook 模型。&lt;/p&gt;&#xA;&lt;p&gt;Cursor IDE 的 Hook 模型是混合触发，可以通过 &lt;code&gt;/skill-name&lt;/code&gt; 显式调用，也可以让 AI 自动判断。显式调用容易监控，自动判断就需要解析日志。&lt;/p&gt;&#xA;&lt;p&gt;Trae IDE 的 Hook 模型是剧本驱动，因为支持多轮对话模板，一个 skill 可能在对话的不同阶段多次触发。比如第一步询问需求，第二步生成代码，第三步解释代码。每个步骤都是一个 Hook 点，上报工具需要追踪整个对话流程。&lt;/p&gt;&#xA;&lt;p&gt;OpenCode 的 Hook 模型和 Claude Code 类似，也是被动触发。但由于有 skill 继承，触发的时候可能同时激活了父项目和子项目的 skill，上报工具需要记录这个层级关系。&lt;/p&gt;&#xA;&lt;p&gt;OpenClaw 的 Hook 模型是意图识别加工作流，它会先识别用户意图，然后触发对应的工作流。工作流可能包含多个 skill 的串联调用，上报的时候你需要记录整个调用链。&lt;/p&gt;&#xA;&lt;p&gt;说了这么多，我来帮你理一理这八家 Agent 的差异。&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Agent&lt;/th&gt;&#xA;          &lt;th&gt;类型&lt;/th&gt;&#xA;          &lt;th&gt;路径&lt;/th&gt;&#xA;          &lt;th&gt;格式&lt;/th&gt;&#xA;          &lt;th&gt;加载方式&lt;/th&gt;&#xA;          &lt;th&gt;Hook 模型&lt;/th&gt;&#xA;          &lt;th&gt;上报难度&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Claude Code&lt;/td&gt;&#xA;          &lt;td&gt;CLI&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;.claude/skills/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Markdown&lt;/td&gt;&#xA;          &lt;td&gt;静态扫描加热重载&lt;/td&gt;&#xA;          &lt;td&gt;被动触发&lt;/td&gt;&#xA;          &lt;td&gt;中等&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Codex CLI&lt;/td&gt;&#xA;          &lt;td&gt;CLI&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;.codex/commands/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;YAML&lt;/td&gt;&#xA;          &lt;td&gt;静态加热重载加条件&lt;/td&gt;&#xA;          &lt;td&gt;命令加事件&lt;/td&gt;&#xA;          &lt;td&gt;中等&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;CoCo&lt;/td&gt;&#xA;          &lt;td&gt;CLI&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;.coco/skills/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;TOML&lt;/td&gt;&#xA;          &lt;td&gt;静态加远程&lt;/td&gt;&#xA;          &lt;td&gt;关键词加场景&lt;/td&gt;&#xA;          &lt;td&gt;高&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Gemini CLI&lt;/td&gt;&#xA;          &lt;td&gt;CLI&lt;/td&gt;&#xA;          &lt;td&gt;Cloud Console&lt;/td&gt;&#xA;          &lt;td&gt;-&lt;/td&gt;&#xA;          &lt;td&gt;API 获取&lt;/td&gt;&#xA;          &lt;td&gt;黑盒&lt;/td&gt;&#xA;          &lt;td&gt;不可能&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Cursor IDE&lt;/td&gt;&#xA;          &lt;td&gt;IDE&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;~/.cursor/settings.json&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;JSON&lt;/td&gt;&#xA;          &lt;td&gt;静态扫描&lt;/td&gt;&#xA;          &lt;td&gt;混合触发&lt;/td&gt;&#xA;          &lt;td&gt;中等偏高&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Trae IDE&lt;/td&gt;&#xA;          &lt;td&gt;IDE&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;.trae/agents/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;JSON&lt;/td&gt;&#xA;          &lt;td&gt;静态加动态&lt;/td&gt;&#xA;          &lt;td&gt;剧本驱动&lt;/td&gt;&#xA;          &lt;td&gt;高&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;OpenCode&lt;/td&gt;&#xA;          &lt;td&gt;IDE&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;.opencode/skills/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Markdown&lt;/td&gt;&#xA;          &lt;td&gt;继承链加载&lt;/td&gt;&#xA;          &lt;td&gt;被动触发&lt;/td&gt;&#xA;          &lt;td&gt;中等偏高&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;OpenClaw&lt;/td&gt;&#xA;          &lt;td&gt;IDE&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;.openclaw/skills/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;JSON&lt;/td&gt;&#xA;          &lt;td&gt;云端同步&lt;/td&gt;&#xA;          &lt;td&gt;意图加工作流&lt;/td&gt;&#xA;          &lt;td&gt;很高&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;从这张表你能看出什么？&lt;/p&gt;&#xA;&lt;p&gt;首先，CLI 和 IDE 泾渭分明。CLI 工具普遍使用项目级配置，格式多样，Markdown、YAML、TOML 都有。IDE 则倾向于 JSON 配置，而且普遍有全局加项目级的双层结构。&lt;/p&gt;&#xA;&lt;p&gt;其次，云原生是趋势。CoCo、Gemini CLI、OpenClaw 都不同程度地依赖云端，本地只是一个视图或缓存。这对上报工具提出了新要求，不能只盯着文件系统，还得调 API、处理网络异常。&lt;/p&gt;&#xA;&lt;p&gt;最后，Hook 模型越来越复杂。从最简单的显式调用，到被动触发，再到剧本驱动、意图识别，Agent 越来越智能，但也越来越不透明。上报工具能拿到的信息越来越少，很多都是黑盒。&lt;/p&gt;&#xA;&lt;p&gt;说真的，写到这儿，我突然意识到一个问题。&lt;/p&gt;&#xA;&lt;p&gt;为什么各家在 skill 管理上差异这么大？&lt;/p&gt;&#xA;&lt;p&gt;背后其实是产品哲学的不同，CLI 和 IDE 的目标用户不一样，设计思路自然也不同。&lt;/p&gt;&#xA;&lt;p&gt;Claude Code 和 OpenCode 相信文件即真理。skill 就应该像代码一样，放在版本控制里，任何人都能看、能改、能复现。这种哲学决定了本地优先、透明可见的设计。&lt;/p&gt;&#xA;&lt;p&gt;Codex CLI 相信配置的标准化。YAML 比 Markdown 结构化，比 JSON 可读性好，是工程化团队的首选。&lt;/p&gt;&#xA;&lt;p&gt;CoCo 相信生态的力量。skill 不应该绑定在本地，而应该可以分享、可以订阅、可以版本管理。这种设计牺牲了离线可用性，换来了社区活力。&lt;/p&gt;&#xA;&lt;p&gt;Gemini CLI 相信云服务的整合。skill 是 Google Cloud 生态的一部分，用户不需要关心配置，只需要用。&lt;/p&gt;&#xA;&lt;p&gt;Cursor IDE 和 Trae IDE 相信配置的灵活性。JSON 虽然难写，但好解析，适合图形化配置界面。而且全局加项目级的双层结构，满足了个人习惯和团队协作的不同需求。&lt;/p&gt;&#xA;&lt;p&gt;OpenClaw 相信飞书生态的协同。skill 可以通过多维表格管理，非技术人员也能参与配置。&lt;/p&gt;&#xA;&lt;p&gt;这八种哲学没有对错，只是取舍不同。&lt;/p&gt;&#xA;&lt;p&gt;但对于我这种想做通用上报工具的人来说，这就很痛苦了。因为没有标准，每家都有自己的玩法，你得为每家写一套适配代码。&lt;/p&gt;&#xA;&lt;p&gt;我有时候觉得，AI Coding Agent 的发展，就像早期的浏览器大战。Netscape 和 IE 各有各的 DOM API，开发者苦不堪言。直到 W3C 标准化之后才好转。&lt;/p&gt;&#xA;&lt;p&gt;skill 管理目前还没有统一标准，Markdown、JSON、YAML、TOML 混用，触发模型也五花八门。&lt;/p&gt;&#xA;&lt;p&gt;短期内，这种碎片化还会继续。&lt;/p&gt;&#xA;&lt;p&gt;写到这儿，我突然想起我做上报工具的初衷。&lt;/p&gt;&#xA;&lt;p&gt;我其实只是想知道自己每天用了多少次 skill，哪些 skill 最有用，哪些可能可以删掉。一个简单的数据分析需求，却因为底层差异变得异常复杂。&lt;/p&gt;&#xA;&lt;p&gt;但换个角度想，这种复杂度本身，就是 AI 工具现状的真实写照。&lt;/p&gt;&#xA;&lt;p&gt;我们还在早期，还在探索，还在争吵什么是最好的方案。这种混乱是创新的代价，也是进化的必经之路。&lt;/p&gt;&#xA;&lt;p&gt;或许几年后回头看，今天的这些差异都会显得幼稚。但在当下，我们必须面对它们，理解它们，在碎片中寻找规律。&lt;/p&gt;&#xA;&lt;p&gt;这就是我这几天做适配的最大感受。&lt;/p&gt;&#xA;&lt;p&gt;无论你用 Claude Code、Cursor、Trae 还是 Codex，skill 管理的本质问题是一样的，怎么让 AI 学会新能力，怎么让它在合适的时机调用这些能力，怎么让这一切对开发者透明、可控。&lt;/p&gt;&#xA;&lt;p&gt;每家产品都有自己的答案，而我在这些答案之间，试图搭一座桥。&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>一文讲清 RAG 效果评测 101</title>
      <link>https://blog.kmoon.fun/%E4%B8%80%E6%96%87%E8%AE%B2%E6%B8%85-rag-%E6%95%88%E6%9E%9C%E8%AF%84%E6%B5%8B-101/</link>
      <pubDate>Sat, 20 Jun 2026 20:26:53 +0800</pubDate><author>hushan@kmoon.fun (Kmoon)</author>
      <guid>https://blog.kmoon.fun/%E4%B8%80%E6%96%87%E8%AE%B2%E6%B8%85-rag-%E6%95%88%E6%9E%9C%E8%AF%84%E6%B5%8B-101/</guid>
      <description>&lt;h2 id=&#34;评估阶段&#34;&gt;评估阶段&lt;/h2&gt;&#xA;&lt;p&gt;针对 RAG 的评估主要分为两个阶段的评估：检索阶段、生成阶段。其中检索阶段需要评估召回的文档的相关性，生成阶段需要评估模型生成的内容的正确性。&lt;/p&gt;&#xA;&lt;h3 id=&#34;检索阶段&#34;&gt;检索阶段&lt;/h3&gt;&#xA;&lt;h3 id=&#34;生成阶段&#34;&gt;生成阶段&lt;/h3&gt;&#xA;&lt;h2 id=&#34;评估指标&#34;&gt;评估指标&lt;/h2&gt;&#xA;&lt;h2 id=&#34;评估框架&#34;&gt;评估框架&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/vibrantlabsai/ragas&#34;&gt;RAGAS&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/Marker-Inc-Korea/AutoRAG&#34;&gt;AutoRAG&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.zdoc.app/zh/confident-ai/deepeval&#34;&gt;DeepEval&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>luckin CLI：实现在终端里点杯瑞幸咖啡</title>
      <link>https://blog.kmoon.fun/luckin-cli%E5%AE%9E%E7%8E%B0%E5%9C%A8%E7%BB%88%E7%AB%AF%E9%87%8C%E7%82%B9%E6%9D%AF%E7%91%9E%E5%B9%B8%E5%92%96%E5%95%A1/</link>
      <pubDate>Thu, 18 Jun 2026 11:28:59 +0800</pubDate><author>hushan@kmoon.fun (Kmoon)</author>
      <guid>https://blog.kmoon.fun/luckin-cli%E5%AE%9E%E7%8E%B0%E5%9C%A8%E7%BB%88%E7%AB%AF%E9%87%8C%E7%82%B9%E6%9D%AF%E7%91%9E%E5%B9%B8%E5%92%96%E5%95%A1/</guid>
      <description>&lt;p&gt;上周末加班，今天调休，相当于提前放端午假了哈哈。&lt;/p&gt;&#xA;&lt;p&gt;瑞幸咖啡太能整活了，听说出了瑞幸咖啡 CLI 服务，可以在终端调用 CLI 服务，让下单更加便捷。刚好前几天女朋友跟我说瑞幸出了一个新品，“弗朗明戈-跳跳冰茶”，说里面有跳跳躺，喝起来很神奇。我们今天就来尝尝看，顺便看看这个 CLI 是整活还是真能用！&lt;/p&gt;&#xA;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T05-07-49-696Z.png&#34; width=200/&gt;&#xA;&lt;p&gt;首先，访问&lt;a href=&#34;https://open.lkcoffee.com/cli&#34;&gt;瑞幸咖啡AI开放平台&lt;/a&gt;，可以看到网站做的还是很简洁科技风的。&lt;/p&gt;&#xA;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T03-31-46-639Z.png&#34; width=500/&gt;&#xA;&lt;p&gt;我们按照官网教程，安装 CLI 工具：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;curl -fsSL https://open.lkcoffee.com/install &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; bash&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T03-36-16-259Z.png&#34; width=500/&gt;&#xA;&lt;p&gt;我们可以看到已经成功安装了 luckin 0.0.1 CLI 工具。接下来我们在终端输入 luckin 回车：&lt;/p&gt;&#xA;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T03-38-18-773Z.png&#34; width=500/&gt;&#xA;&lt;p&gt;需要先登录授权一下，这里可以看到是跳转到浏览器官网进行授权登录，然后创建一个 Token。&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;https://cdn.kmoon.fun/2026/2026-06-18T03-39-51-212Z.png&#34; width=300/&gt;&#xA;&lt;img src=&#34;https://cdn.kmoon.fun/2026/2026-06-18T03-40-18-960Z.png&#34; width=300/&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;然后我们返回终端，可以看到已经登录成功了，并且可以看到支持哪些斜杠命令：&lt;/p&gt;&#xA;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T03-41-02-898Z.png&#34; width=500/&gt;&#xA;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T03-47-39-742Z.png&#34; width=500/&gt;&#xA;&lt;p&gt;我们先查看一下附近门店，使用 /store 命令，这里突然离谱了起来，这个命令居然需要我自己输入经纬度？？？&lt;/p&gt;&#xA;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T03-49-24-902Z.png&#34; width=500/&gt;&#xA;&lt;p&gt;算了，我们还是在 Claude Code 里面使用它的 my-coffee Skill 吧。希望后续可以打通定位服务，自己定位用户所在位置。&lt;/p&gt;&#xA;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T03-54-20-618Z.png&#34; width=500/&gt;&#xA;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T04-38-06-550Z.png&#34; width=500/&gt;&#xA;&lt;p&gt;我们将官网创建的 Token 手动复制过来，并告诉它在附近帮我点一杯新品冰茶：&lt;/p&gt;&#xA;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T04-41-08-607Z.png&#34; width=500/&gt;&#xA;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T04-54-50-192Z.png&#34; width=500/&gt;&#xA;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T04-56-28-201Z.png&#34; width=500/&gt;&#xA;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T04-57-40-940Z.png&#34; width=500/&gt;&#xA;&lt;p&gt;这里需要我们扫码支付一下，支付完成后会帮我们查询订单状态和取餐码。这里我们可以发现目前只支持自取，暂时不支持外卖配送。不过我相信不久的将来，当美团 CLI、淘宝闪购 CLI 出现的时候，是完全可以支持外卖配送的。&lt;/p&gt;&#xA;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T04-59-52-774Z.png&#34; width=500/&gt;&#xA;&lt;p&gt;好啦，现在我们可以准备去门店取餐了。&lt;/p&gt;&#xA;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T05-03-13-746Z.png&#34; width=500/&gt;&#xA;&lt;p&gt;打开手机的小程序，我们可以看到订单制作完成，等待取餐中：&lt;/p&gt;&#xA;&lt;img src= &#34;https://cdn.kmoon.fun/2026/2026-06-18T05-06-18-362Z.png&#34; width=300/&gt;&#xA;&lt;p&gt;也是成功喝到新品了，一直滋滋滋，跟小时候吃的跳跳糖一样。&lt;/p&gt;&#xA;&lt;img src=&#34;https://cdn.kmoon.fun/2026/2026-06-18T05-24-51-962Z.png&#34; alt=&#34;&#34; width=300/&gt;&#xA;&lt;h2 id=&#34;写在最后&#34;&gt;写在最后&lt;/h2&gt;&#xA;&lt;p&gt;这次折腾这么久，最后也确实成功完成了在终端里下单一杯咖啡。瑞幸这次不是简单整活，而是提供了完整的一套 MCP + CLI + Skills，同时也提供了工具文档，这体现了瑞幸选择拥抱 AI 这条路上迈出了第一步。&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Google 开源 Agent OKF 新标准</title>
      <link>https://blog.kmoon.fun/google-%E5%BC%80%E6%BA%90-agent-okf-%E6%96%B0%E6%A0%87%E5%87%86/</link>
      <pubDate>Wed, 17 Jun 2026 09:34:20 +0800</pubDate><author>hushan@kmoon.fun (Kmoon)</author>
      <guid>https://blog.kmoon.fun/google-%E5%BC%80%E6%BA%90-agent-okf-%E6%96%B0%E6%A0%87%E5%87%86/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;Reference:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing/&#34;&gt;Introducing the Open Knowledge Format&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://mp.weixin.qq.com/s/nhF1cy_lIQukq_niVFvRCA&#34;&gt;AI 寒武纪：谷歌突然开源Agent OKF新标准！&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf&#34;&gt;GitHub：knowledge-catalog&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Open Knowledge Format，简称 OKF，6 月 12 日发布，&lt;a href=&#34;https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf&#34;&gt;GitHub 仓库&lt;/a&gt;三天冲到 3300+ stars。它要做的事情很简单：给 AI 知识库定一个通用的、可移植的格式规范。&lt;/p&gt;&#xA;&lt;h2 id=&#34;为什么需要统一格式&#34;&gt;为什么需要统一格式&lt;/h2&gt;&#xA;&lt;p&gt;我们平时给 AI 的文档大部分都是内部知识，来源方方面面：数据库表字段含义、接口定义规范、业务功能模块、平台操作手册。这些文档没有统一格式，不同部门关注的重点也不同。这就导致每次新开发一个 Agent，都得从头注入、清洗、梳理一遍知识库。知识没有流通，锁在创建它的那个平台里。&lt;/p&gt;&#xA;&lt;p&gt;更麻烦的是，每个人搭建知识库的方式都不同。有的全部放在一起靠目录区分模块，有的分模块放在不同知识库；有的用长串文件名标记属性，有的更习惯用 frontmatter。对人类来说这无所谓，自己写的自己找得到。但 Agent 需要的是一份格式稳定的&amp;quot;说明书&amp;quot;，不是每次都重新学习你的目录组织习惯。&lt;/p&gt;&#xA;&lt;h2 id=&#34;okf-是什么&#34;&gt;OKF 是什么&lt;/h2&gt;&#xA;&lt;p&gt;核心设计很简单：一个 OKF bundle 就是一个 Markdown 文件目录，每个文件代表一个概念（数据表、数据集、指标、操作手册、API 等），文件路径就是这个概念的唯一标识。&lt;/p&gt;&#xA;&lt;p&gt;目录结构长这样：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sales/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── index.md&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── datasets/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── orders_db.md&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── tables/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── orders.md&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── customers.md&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── metrics/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    └── weekly_active_users.md&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;每个文件分两部分：顶部一小块 YAML frontmatter 存结构化字段，下面是 Markdown 正文，写什么完全自由。&lt;/p&gt;&#xA;&lt;p&gt;一个完整的概念文件：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;---&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;type: BigQuery Table&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;title: Orders&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;description: One row per completed customer order.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;resource: https://console.cloud.google.com/bigquery?p=acme&amp;amp;d=sales&amp;amp;t=orders&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tags: [sales, revenue]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;timestamp: 2026-05-28T14:30:00Z&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;---&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gh&#34;&gt;# Schema&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;| Column        | Type      | Description                              |&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;|---------------|-----------|------------------------------------------|&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;| &lt;span class=&#34;sb&#34;&gt;`order_id`&lt;/span&gt;    | STRING    | Globally unique order identifier.        |&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;| &lt;span class=&#34;sb&#34;&gt;`customer_id`&lt;/span&gt; | STRING    | FK to [&lt;span class=&#34;nt&#34;&gt;customers&lt;/span&gt;](&lt;span class=&#34;na&#34;&gt;/tables/customers.md&lt;/span&gt;). |&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gh&#34;&gt;# Joins&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Joined with [&lt;span class=&#34;nt&#34;&gt;customers&lt;/span&gt;](&lt;span class=&#34;na&#34;&gt;/tables/customers.md&lt;/span&gt;) on &lt;span class=&#34;sb&#34;&gt;`customer_id`&lt;/span&gt;.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;概念之间用普通 Markdown 链接互引，整个目录变成一张关系图，比文件系统的父子层级丰富得多。&lt;/p&gt;&#xA;&lt;p&gt;合规条件就三条：每个非保留文件必须有可解析的 YAML frontmatter；frontmatter 必须包含非空的 &lt;code&gt;type&lt;/code&gt; 字段；保留文件名（&lt;code&gt;index.md&lt;/code&gt;、&lt;code&gt;log.md&lt;/code&gt;）遵循规定结构。其他一切随意。&lt;/p&gt;&#xA;&lt;p&gt;而且消费者被要求尽量宽容：不能因为缺少可选字段、遇到未知 type、多出额外 frontmatter key，甚至链接断了就拒绝解析。这设计哲学很有意思——宁可多读点废话，也别因为格式细节把合法内容挡在门外。&lt;/p&gt;&#xA;&lt;p&gt;整个&lt;a href=&#34;https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md&#34;&gt;规范文件&lt;/a&gt;才 451 行、14.7 KB。Google 自己说的：Read the spec, it&amp;rsquo;s short! 这话倒没骗人。&lt;/p&gt;&#xA;&lt;h2 id=&#34;karpathy-的影子&#34;&gt;Karpathy 的影子&lt;/h2&gt;&#xA;&lt;p&gt;OKF 不是凭空出来的。Karpathy 今年 4 月发了 &lt;a href=&#34;https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f&#34;&gt;LLM Wiki gist&lt;/a&gt;，两个月拿到 5000+ stars，核心思路就是&amp;quot;每个概念一个 Markdown 文件，LLM 负责维护，互链成图&amp;quot;。OKF 本质上是在标准化这个模式：把 Karpathy 的个人实践固化成一份开放规范，让不同团队、不同工具产出的知识库可以互相识别和消费。&lt;/p&gt;&#xA;&lt;p&gt;我之前分析 &lt;a href=&#34;https://blog.kmoon.fun/blog/rag-vs-llm-wiki/&#34;&gt;RAG vs LLM Wiki 的取舍&lt;/a&gt;时说过，编译好的 wiki 有错误滚雪球、写入成本膨胀、分布式一致性这些问题。OKF 解决的是另一个层面的问题：如果大家都觉得 wiki 模式是对的，那格式得统一，不然你的 wiki 我的 wiki 各玩各的，还是没法流通。&lt;/p&gt;&#xA;&lt;p&gt;Karpathy 估算过，维护得当的 Markdown 知识库相比直接灌原始文档，能减少高达 95% 的 token 消耗。OKF 想做的就是让这个&amp;quot;维护得当&amp;quot;有一个可遵循的底线。&lt;/p&gt;&#xA;&lt;h2 id=&#34;ai-知识的四个层次&#34;&gt;AI 知识的四个层次&lt;/h2&gt;&#xA;&lt;p&gt;参考文章里提了一个我觉得很有意思的分层框架，把 AI 获取知识的方式拆成四层：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;sitemap.xml&lt;/strong&gt; 告诉爬虫哪些 URL 存在&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;llms.txt&lt;/strong&gt; 告诉 agent 哪些页面最值得读&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;EntityMap&lt;/strong&gt; 声明实体和它们之间的关系&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;OKF&lt;/strong&gt; 把内容本身递到 agent 手里，每个页面是一个干净的概念，互链成图&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这四层不是替代关系，是从索引到内容的递进。sitemap 回答&amp;quot;有什么&amp;quot;，llms.txt 回答&amp;quot;看什么&amp;quot;，EntityMap 回答&amp;quot;谁和谁有关&amp;quot;，OKF 回答&amp;quot;具体是什么&amp;quot;。OKF 补的是最后一公里，不是告诉你去哪找，而是直接把知识递给你。&lt;/p&gt;&#xA;&lt;h2 id=&#34;三个设计原则&#34;&gt;三个设计原则&lt;/h2&gt;&#xA;&lt;h3 id=&#34;极简主义&#34;&gt;极简主义&lt;/h3&gt;&#xA;&lt;p&gt;OKF 对每个文件只要求一个字段：&lt;code&gt;type&lt;/code&gt;。其他一切都留给内容创建者。存在哪些类型、包含哪些字段、正文写哪些部分，规范都不管。它定义的是互操作性接口，不是内容模型。&lt;/p&gt;&#xA;&lt;p&gt;说实话，&lt;code&gt;type&lt;/code&gt; 是唯一必填字段，意味着你可以把任何东西塞进去，只要标上 type。灵活，但也意味着两个 OKF bundle 可能长得完全不一样。&amp;ldquo;格式统一&amp;quot;只统一了最外层的壳，里面的内容组织还是各写各的。&lt;/p&gt;&#xA;&lt;h3 id=&#34;生产者消费者独立性&#34;&gt;生产者/消费者独立性&lt;/h3&gt;&#xA;&lt;p&gt;OKF 把知识编写者和使用者清晰分离。人手写的文件，AI agent 可以读。元数据流水线生成的 bundle，可视化工具可以浏览。一个 LLM 生成的 bundle，另一个 LLM 可以查询。格式是契约，两端工具可以独立替换。&lt;/p&gt;&#xA;&lt;h3 id=&#34;格式本身不是平台&#34;&gt;格式本身，不是平台&lt;/h3&gt;&#xA;&lt;p&gt;OKF 不绑定任何云服务、数据库、模型厂商或 agent 框架。读写不需要专有账号或 SDK。&lt;/p&gt;&#xA;&lt;p&gt;不过有意思的是，发布 OKF 的同一天，Google Cloud 也更新了 Knowledge Catalog，让它原生支持导入 OKF bundle。Knowledge Catalog 管的是 OKF 故意不碰的部分：存储、服务、查询、权限控制。开放格式，付费服务层。这策略眼不眼熟？&lt;/p&gt;&#xA;&lt;h2 id=&#34;3-个参考实现&#34;&gt;3 个参考实现&lt;/h2&gt;&#xA;&lt;p&gt;Google 随规范一起发布了 3 个参考实现：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;enrichment agent&lt;/strong&gt;：工作在 BigQuery 数据集上的数据增强 Agent。先针对每个表和视图写 OKF 概念文件草稿，再跑一遍 LLM 爬取权威文档，补充 schema、引用和关联路径。基于 Google ADK 构建，Gemini 做后端模型。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;static HTML visualizer&lt;/strong&gt;：把任何 OKF bundle 转成单个自包含 HTML 文件里的交互式图形视图。无需后端，数据不会离开页面。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;3 个示例 bundle&lt;/strong&gt;：GA4 电商数据集、Stack Overflow、比特币公开数据集。都是用上面的 enrichment agent 生成的，放在 repo 里作为格式合规的示例。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;说点实在的&#34;&gt;说点实在的&lt;/h2&gt;&#xA;&lt;p&gt;OKF 的极简设计方向是对的。Markdown 是人机共读的最佳折中，用文件路径做概念 ID 很聪明，YAML frontmatter 兼顾了结构化查询和人类可读。规范本身短到 451 行，降低了采纳门槛。&lt;/p&gt;&#xA;&lt;p&gt;但社区也有不同声音。有人直接说&amp;quot;这不就是一个带 YAML frontmatter 的文件夹吗？&amp;ldquo;更尖锐的质疑是：如果目标消费者是 AI，为什么选 Markdown 而不是更结构化的 JSON 或 Protobuf？Markdown 嵌套表格渲染不好，复杂 schema 表达力有限。&lt;/p&gt;&#xA;&lt;p&gt;我觉得关键在于 OKF 不替代 RAG 和 MCP，它们是互补的。RAG 处理大规模动态文档档案，OKF 处理稳定的、精选的组织知识。MCP 管的是 agent 怎么连工具和实时数据，是&amp;quot;插座&amp;rdquo;；OKF 管的是 agent 对数据源知道什么，是&amp;quot;流过插座的知识&amp;rdquo;。一个 MCP server 完全可以把 OKF bundle 作为知识源暴露出来。三者不在一个层面上竞争。&lt;/p&gt;&#xA;&lt;p&gt;真正的顾虑是：OKF 还只是 v0.1。三天 3300 stars 说明社区有需求，但格式规范的真正考验不在发布而在采纳。需要多少生态工具跟上，多少团队真的用 OKF 格式出包，才能形成正循环？标准这东西，用的人多了才是标准，发布了没人用就只是一份文档。&lt;/p&gt;&#xA;&lt;h2 id=&#34;写在最后&#34;&gt;写在最后&lt;/h2&gt;&#xA;&lt;p&gt;OKF 定义的是知识的流通格式，不是知识的替代品。格式能打通孤岛，但填进去的内容还得靠人。&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>为我的 Chaos Pool 开发一个“卖报童”机器人</title>
      <link>https://blog.kmoon.fun/%E4%B8%BA%E6%88%91%E7%9A%84-chaos-pool-%E5%BC%80%E5%8F%91%E4%B8%80%E4%B8%AA%E5%8D%96%E6%8A%A5%E7%AB%A5%E6%9C%BA%E5%99%A8%E4%BA%BA/</link>
      <pubDate>Tue, 16 Jun 2026 13:14:34 +0800</pubDate><author>hushan@kmoon.fun (Kmoon)</author>
      <guid>https://blog.kmoon.fun/%E4%B8%BA%E6%88%91%E7%9A%84-chaos-pool-%E5%BC%80%E5%8F%91%E4%B8%80%E4%B8%AA%E5%8D%96%E6%8A%A5%E7%AB%A5%E6%9C%BA%E5%99%A8%E4%BA%BA/</guid>
      <description>&lt;h2 id=&#34;背景&#34;&gt;背景&lt;/h2&gt;&#xA;&lt;p&gt;我特别想喜欢飞书的 Link Preview 功能，就是在飞书群里发送一篇文章、一个 Github 仓库链接等等，会自动生成一个预览小卡片。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;  &#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-9cb89026&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片：link preview 效果&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-9cb89026&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T05-22-50-379Z.png&#34;&#xA;            alt=&#34;link preview 效果&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-9cb89026&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T05-22-50-379Z.png&#34;&#xA;            alt=&#34;link preview 效果&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;figcaption&gt;link preview 效果&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;这就很好地解决了我平时看到优秀的文章、知识、项目的时候，收藏了一堆链接，但是到后面看着一串 URL 无从下手，不知道每个链接是什么东西的问题。&lt;/p&gt;&#xA;&lt;p&gt;为了管理从四面八方看到的有趣内容，我建了一个飞书群，命名为“Chaos Pool”，中文译为“混沌池”，本意是希望我每天从互联网搜刮来的信息，先扔进这个混沌池子，督促我有时间一定要吸收学习掉，然后将“混沌”转为“稳定”。&lt;/p&gt;&#xA;&lt;p&gt;为此我专门用豆包为群聊生成了一个群头像，我最后选择了第一个：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;  &#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-571da84c&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片：豆包&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-571da84c&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T05-20-41-586Z.png&#34;&#xA;            alt=&#34;豆包&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-571da84c&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T05-20-41-586Z.png&#34;&#xA;            alt=&#34;豆包&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;figcaption&gt;豆包&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;需要一个卖报童&#34;&gt;需要一个“卖报童”&lt;/h2&gt;&#xA;&lt;p&gt;后面一段时间我都是这样的流程：平时逛 Github、微信公众号、V2EX、阮一峰网络周刊等等，遇到感兴趣、有意思、我能用上的东西，就长按复制链接、打开飞书、进入chaos pool群聊、粘贴链接、回车发送。这一套是不是看着还挺行云流水。&lt;/p&gt;&#xA;&lt;p&gt;后来我慢慢发现，这样其实还是有点麻烦，中间很多环节都可以简化或者省略掉，我期望最终形态是：我复制链接、自动调起一个发送框、回车发送，至少在移动端这样才是我希望的流程。&lt;/p&gt;&#xA;&lt;p&gt;而且还有一个问题，我经常看的社区、周刊、月刊，基本上都是定时发布，我每次还需要自己去对应的网站查看，我需要一个人（机器人）到点自动帮我推送到群里，有点像 RSS 订阅一样。&lt;/p&gt;&#xA;&lt;p&gt;因此，我需要在飞书群里添加一个机器人，来完成上面这些任务，我给他起名为“卖报童”，这个名字也是通俗易懂。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;  &#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-a133e919&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片：添加飞书机器人&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-a133e919&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T05-22-15-325Z.png&#34;&#xA;            alt=&#34;添加飞书机器人&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-a133e919&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T05-22-15-325Z.png&#34;&#xA;            alt=&#34;添加飞书机器人&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;figcaption&gt;添加飞书机器人&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;添加完飞书机器人后，我们得到了一个名为 webhook 的 https 链接，怎么用呢？&lt;/p&gt;&#xA;&lt;h2 id=&#34;webhook-是什么怎么用&#34;&gt;webhook 是什么？怎么用？&lt;/h2&gt;&#xA;&lt;p&gt;webhook 是什么？怎么用？我们点击查阅一下&lt;a href=&#34;https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot?lang=zh-CN&#34;&gt;官方使用指南&lt;/a&gt;。看完发现没啥，可以理解为一个发送消息的接口，通过 webhook 请求可以在飞书群里发送消息。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-8a587a8d&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-8a587a8d&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T05-37-54-159Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-8a587a8d&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T05-37-54-159Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;那我们先简单调试一下，测试调用自定义机器人的 webhook 地址，向所在群组发送消息。：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;curl -X POST -H &lt;span class=&#34;s2&#34;&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    -d &lt;span class=&#34;s1&#34;&gt;&amp;#39;{&amp;#34;msg_type&amp;#34;:&amp;#34;text&amp;#34;,&amp;#34;content&amp;#34;:{&amp;#34;text&amp;#34;:&amp;#34;啦啦啦！啦啦啦！我是卖报的小行家。&amp;#34;}}&amp;#39;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    https://open.feishu.cn/open-apis/bot/v2/hook/****&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们可以看到群里收到了卖报童发来的卖报歌，：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-ee59ee56&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-ee59ee56&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T05-46-53-370Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-ee59ee56&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T05-46-53-370Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &#xA;&lt;/figure&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-18d24d2a&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-18d24d2a&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T05-47-06-161Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-18d24d2a&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T05-47-06-161Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;这里遇到一个问题，通过机器人发送的链接没有生成预览卡片，我自己手动发送的明明可以生成卡片呀。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-de57acbf&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-de57acbf&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T05-51-21-752Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-de57acbf&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T05-51-21-752Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;了解后发现“自定义机器人”不支持链接预览功能，需要创建一个“应用机器人”：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-37306963&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-37306963&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T11-04-40-775Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-37306963&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-16T11-04-40-775Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>RAG vs LLM Wiki：“无状态”或成护城河?</title>
      <link>https://blog.kmoon.fun/rag-vs-llm-wiki%E6%97%A0%E7%8A%B6%E6%80%81%E6%88%96%E6%88%90%E6%8A%A4%E5%9F%8E%E6%B2%B3/</link>
      <pubDate>Sat, 13 Jun 2026 15:21:16 +0800</pubDate><author>hushan@kmoon.fun (Kmoon)</author>
      <guid>https://blog.kmoon.fun/rag-vs-llm-wiki%E6%97%A0%E7%8A%B6%E6%80%81%E6%88%96%E6%88%90%E6%8A%A4%E5%9F%8E%E6%B2%B3/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;Reference：&lt;a href=&#34;https://mp.weixin.qq.com/s?__biz=MzY4OTMxODkyNQ==&amp;amp;mid=2247483768&amp;amp;idx=1&amp;amp;sn=2185014ab823f2e2d25a6c20841385bb&amp;amp;chksm=f3d83c1ac4afb50c24e3a67630a9199353352017cbc65154d5bcc947ec5695d98e3714d151be&amp;amp;cur_album_id=4531621795329884171&#34;&gt;&amp;ldquo;无状态&amp;quot;是 RAG 的护栏&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Karpathy 最近提了一个很有意思的方案叫 &lt;a href=&#34;https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f&#34;&gt;LLM Wiki&lt;/a&gt; —— 与其每次查询都重新检索一堆原始资料，不如让 LLM 先把所有文档读一遍，整理成一个结构化的知识库。以后有新文档进来，LLM 自动找出受影响的页面然后更新。查询的时候直接读 wiki，不用再翻原始材料了。&lt;/p&gt;&#xA;&lt;p&gt;说实话我第一反应是：这不就是我理想中的知识管理吗？干净、有序、能自己生长。比 RAG 那种每次都从头翻文档的&amp;quot;笨办法&amp;quot;优雅太多了。&lt;/p&gt;&#xA;&lt;p&gt;但仔细想想，这套方案的代价其实挺大的。而且有意思的是，这些代价恰恰是 RAG 那个&amp;quot;笨办法&amp;quot;天然避开的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;错误会滚雪球&#34;&gt;错误会滚雪球&lt;/h2&gt;&#xA;&lt;p&gt;RAG 拿给你的是原始文档的片段，你可以自己判断靠不靠谱。LLM Wiki 拿给你的是 LLM 写出来的东西——它理解错了、漏了关键信息、过度压缩导致失真，这些偏差会直接变成 wiki 里的&amp;quot;事实&amp;rdquo;。&lt;/p&gt;&#xA;&lt;p&gt;更要命的是，错误会繁殖。LLM 处理第 15 篇论文的时候，参考了第 3 篇的摘要，而那个摘要本身就有偏差。基于偏差做了交叉引用，错误没被修正反而被放大了。后面所有查询都在这个有问题的基底上回答，lint 检查也是——lint 本身也是 LLM 跑的，它在有偏差的 wiki 上做检查，能查出什么？&lt;/p&gt;&#xA;&lt;p&gt;这让我想到一个词：&lt;strong&gt;有状态的错误传播&lt;/strong&gt;。一旦错误进入系统，它不是静止的，它会沿着依赖链扩散，而且你很难定位是哪个环节出了问题。&lt;/p&gt;&#xA;&lt;p&gt;RAG 之所以没有这个问题，恰恰因为它每次都从源头重新来——上一次的错不会留给下一次。我们一直说 RAG&amp;quot;无状态&amp;quot;是局限，但换个角度看，这是道护栏。&lt;/p&gt;&#xA;&lt;h2 id=&#34;写入成本膨胀&#34;&gt;写入成本膨胀&lt;/h2&gt;&#xA;&lt;p&gt;Karpathy 估算一篇新文档可能涉及 10-15 个 wiki 页面。听起来不多，但跑一下流程就知道了：LLM 先读完整篇新文档，再翻 index.md 找相关页面，然后读这十几页的内容判断怎么改，最后逐个重写。一篇文档的摄入就是几万到十几万 token。&lt;/p&gt;&#xA;&lt;p&gt;更关键的是，这个成本不是恒定的。wiki 页面越来越多、越来越长，摄入第 101 篇时 LLM 要读的上下文比第 1 篇大得多。而 RAG 的摄入成本基本是恒定的——切分、embedding，完事，知识库再大也不会越来越贵。&lt;/p&gt;&#xA;&lt;p&gt;两种模式的成本节奏也完全不同：RAG 是查询时花钱，按次付费；LLM Wiki 是写入时花钱，而且账单随规模往上走。对于查询量不大但资料持续涌入的场景，这个成本结构很致命。&lt;/p&gt;&#xA;&lt;h2 id=&#34;分布式一致性的老问题&#34;&gt;分布式一致性的老问题&lt;/h2&gt;&#xA;&lt;p&gt;当 wiki 到了 200 个页面，一个新事实进来，LLM 怎么确保找全了所有该更新的地方？没法确保。更新了 mixture-of-experts.md 忘了改 training-cost-comparison.md，两个页面矛盾了，你大概率要碰巧都读到才能发现。&lt;/p&gt;&#xA;&lt;p&gt;这本质上是分布式系统的一致性问题——只不过节点换成了 Markdown 文件。&lt;a href=&#34;https://baike.sogou.com/v175447357.htm&#34;&gt;CAP 定理&lt;/a&gt; 告诉我们这类问题没有银弹，交给 LLM 来扛，说实话我不太放心。&lt;/p&gt;&#xA;&lt;p&gt;审计也是。RAG 能清楚告诉你答案来自哪几个 chunk，鼠标一点就能核对。LLM Wiki 呢？一个结论在第 5、12、18、23 次摄入时被改过，每次都是 LLM 当时理解的产物，原始信息在多次&amp;quot;传话&amp;quot;后溯源链早就断了。除非你给 wiki 上 git 版本控制并且每次 diff 审查——但真这么干，省下来的精力又贴回去了。&lt;/p&gt;&#xA;&lt;p&gt;还有冷启动。开头最痛苦——你不知道该定什么结构，LLM 也不知道。如果方向错了，前几十篇的产物可能得推倒重来。后续理解深入了发现分类方式有问题要大规模重组，这跟代码重构一样疼，但这次是让 LLM 来重构，你对结果没有完全的把握。&lt;/p&gt;&#xA;&lt;h2 id=&#34;适用场景较窄&#34;&gt;适用场景较窄&lt;/h2&gt;&#xA;&lt;p&gt;LLM Wiki 最适合的其实是 Karpathy 自己的场景——个人研究笔记，几十到一两百篇材料，容错率高，错了也没什么大不了。结构化、知识密度高的内容（论文、技术报告）也还行。&lt;/p&gt;&#xA;&lt;p&gt;但现实中大量场景不太适合：会议纪要和对话记录信息密度低、上下文咬合紧，拆成独立页面很勉强；代码仓库变化太快 wiki 追不上；法律合规文档一个字都不能动，摘要就有遗漏风险；实时数据根本没有&amp;quot;编译&amp;quot;的时间窗口。&lt;/p&gt;&#xA;&lt;h2 id=&#34;写在最后&#34;&gt;写在最后&lt;/h2&gt;&#xA;&lt;p&gt;RAG 的&amp;quot;无状态&amp;quot;不是缺陷，是护城河。它保证了每一轮问答都能回溯到原始材料，不会把 LLM 的理解偏差一层层叠加上去。&lt;/p&gt;&#xA;&lt;p&gt;但这不代表只能二选一。更务实的路线是把 LLM Wiki 定位成&lt;strong&gt;缓存层和知识导航&lt;/strong&gt;——用它建立高层知识地图，帮你快速定位该看的范围，真正作答时还是回到原始文档做最终核验。&lt;/p&gt;&#xA;&lt;p&gt;编译是为了提速和导航，不是为了取代信源。&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>代码定义结构，权重存储知识</title>
      <link>https://blog.kmoon.fun/%E4%BB%A3%E7%A0%81%E5%AE%9A%E4%B9%89%E7%BB%93%E6%9E%84%E6%9D%83%E9%87%8D%E5%AD%98%E5%82%A8%E7%9F%A5%E8%AF%86/</link>
      <pubDate>Fri, 12 Jun 2026 09:47:25 +0800</pubDate><author>hushan@kmoon.fun (Kmoon)</author>
      <guid>https://blog.kmoon.fun/%E4%BB%A3%E7%A0%81%E5%AE%9A%E4%B9%89%E7%BB%93%E6%9E%84%E6%9D%83%E9%87%8D%E5%AD%98%E5%82%A8%E7%9F%A5%E8%AF%86/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;Reference: &lt;a href=&#34;https://mp.weixin.qq.com/s/TM9lv6b-9AH8O9ZiApgTBA&#34;&gt;代码定义结构，权重存储知识：一个例子看懂大模型如何推理&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;ollama-本地部署模型&#34;&gt;Ollama 本地部署模型&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://ollama.com/&#34;&gt;Ollama&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;先下载好 Ollama&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-c7bad051&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-c7bad051&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-13T06-25-22-668Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-c7bad051&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-13T06-25-22-668Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;然后选择一个模型本地部署，这里选择 llama3&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-47346ff6&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-47346ff6&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-13T06-36-08-617Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-47346ff6&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-13T06-36-08-617Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;一行命令部署好，测试一下&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-e36ca731&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-e36ca731&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-13T06-37-02-311Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-e36ca731&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-13T06-37-02-311Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Ollama 本地保存模型的目录结构，其中 blobs 是真正占空间的权重文件，manifests 是模型配置/索引（很小），这里就有一个奇怪的现象：模型的代码只有几十到几百 KB，权重文件却有几个 GB，甚至几十GB。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.ollama/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── models/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── blobs/       # 实际权重文件（GGUF 格式，GB 级）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── manifests/   # 模型元数据（JSON，KB 级）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;因为 Ollama blobs 里存的 GGUF 是量化后的权重二进制文件，全部浮点数参数都存在这里；manifests 只是模型描述、层数、上下文窗口、量化精度等配置，不含知识。&lt;/p&gt;&#xA;&lt;p&gt;我们可以看到 llama3:latest 模型权重有 4.7 G。为什么差距这么大，我们跟模型交互对话时，模型代码和模型权重是如何配合的？模型代码大小和权重大小到底谁对模型更重要？&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-fba2b4bd&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-fba2b4bd&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-13T06-46-13-059Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-fba2b4bd&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-13T06-46-13-059Z.png&#34;&#xA;            alt=&#34;&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;最小模型&#34;&gt;最小模型&lt;/h2&gt;&#xA;&lt;p&gt;我们从一个只有 3 个参数的小模型开始，一步步走完整个推理过程。&lt;/p&gt;&#xA;&lt;p&gt;假设我要做一个判断天气的小模型。根据温度和湿度判断是否适合出门，输入：温度 temp、湿度 humidity，输出：适合出门的分数 y。其中w1、w2 分别是温度和湿度对应的权重，b 是偏置。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;WeatherModel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;fm&#34;&gt;__init__&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;w1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;w2&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;b&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;forward&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;temp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;humidity&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;temp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;w1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;humidity&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;w2&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;b&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;模型训练的过程就是逐渐确定w1、w2、b这三个参数的过程。我们收集了很多历史天气数据中的温度、湿度的值，并打上是否适合出门的标记，这个就是模型训练过程中的训练数据。训练的过程，就是不断调整 w1、w2、b，让模型输出越来越接近真实结果。&lt;/p&gt;&#xA;&lt;p&gt;经过大量的迭代，最后得到：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;w1 = 0.7&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;w2 = -0.3&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;b  = 5.0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这三个数字就是模型权重，训练结束后模型保留的只有这三个数字，这些数字其实是历史经验的压缩结果。即权重 = 从训练数据中提炼出来的经验。对于我们使用的大模型，道理完全一样，只不过区别在于大模型的参数数量扩大了几十亿倍。千亿参数大模型只是把无数层类似 w * x + b 的矩阵运算堆叠、加上注意力机制，底层计算逻辑本质仍是权重与输入的线性运算，只是结构更复杂。&lt;/p&gt;&#xA;&lt;p&gt;后面我们得到了一组新的温度值、湿度值，送入模型进行计算，得出适合出门的分数，如果分数低于我们设定的阈值，就判断为不适合出门。整个过程中：代码决定怎么算、权重决定用什么数字算，二者缺一不可。&lt;/p&gt;&#xA;&lt;h2 id=&#34;结构-vs-权重&#34;&gt;结构 vs 权重&lt;/h2&gt;&#xA;&lt;p&gt;模型结构 = 大脑，模型权重 = 记忆。或者说模型结构 = 神经系统，模型权重 = 学到的知识。&lt;/p&gt;&#xA;&lt;p&gt;一个模型结构可以搭配多个权重，比如英文模型和中文模型就可以搭配不同的预训练权重。&lt;/p&gt;&#xA;&lt;p&gt;同一套 Llama3 Transformer 代码结构，加载英文通用权重 = 英文对话模型；加载代码微调权重 = 代码助手；加载医疗微调权重 = 医疗问答模型。&lt;/p&gt;&#xA;&lt;p&gt;因此真正决定模型能力的，往往不是结构文件，而是权重文件，因为权重文件代表模型学到了多少知识。&lt;/p&gt;&#xA;&lt;h2 id=&#34;模型推理过程&#34;&gt;模型推理过程&lt;/h2&gt;&#xA;&lt;p&gt;模型加载完成后，会把权重全部放进显存，然后开始推理。CPU 负责指挥，GPU 负责干活，CPU 负责告诉 GPU 执行第 1 层、执行第 2 层、&amp;hellip;，GPU 负责真正计算矩阵乘法。推理时，权重常驻显存。输入数据不断流过这些权重矩阵。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;CPU：处理文本分词、token 预处理、调度计算流程、控制网络层执行顺序；&lt;/li&gt;&#xA;&lt;li&gt;GPU：承载全部权重矩阵，并行执行海量矩阵乘，是推理算力核心；&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;写在最后&#34;&gt;写在最后&lt;/h2&gt;&#xA;&lt;p&gt;真正庞大的从不是复杂的算法代码，而是训练过程中沉淀下来的亿万数值。&lt;/p&gt;&#xA;&lt;p&gt;我们说大模型 “大”，不在于实现代码有多冗长，而在于它承载了海量学习得来的知识。&lt;/p&gt;&#xA;&lt;p&gt;代码搭建出神经网络的完整结构，相当于塑造大脑的生理框架；&lt;/p&gt;&#xA;&lt;p&gt;权重存储全部训练学到的信息，相当于大脑里积累的记忆；&lt;/p&gt;&#xA;&lt;p&gt;所谓推理，本质就是将输入文本转化为向量，不断穿过每一层承载知识的权重矩阵，最终输出对应的结果。&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Agent = Model &#43; Harness</title>
      <link>https://blog.kmoon.fun/agent-model--harness/</link>
      <pubDate>Tue, 09 Jun 2026 19:36:02 +0800</pubDate><author>hushan@kmoon.fun (Kmoon)</author>
      <guid>https://blog.kmoon.fun/agent-model--harness/</guid>
      <description>&lt;h2 id=&#34;程序--数据结构--算法&#34;&gt;程序 = 数据结构 + 算法&lt;/h2&gt;&#xA;</description>
    </item>
    <item>
      <title>zhlgd-cli：尝试做一个 CLI</title>
      <link>https://blog.kmoon.fun/zhlgd-cli%E5%B0%9D%E8%AF%95%E5%81%9A%E4%B8%80%E4%B8%AA-cli/</link>
      <pubDate>Sun, 07 Jun 2026 17:01:02 +0800</pubDate><author>hushan@kmoon.fun (Kmoon)</author>
      <guid>https://blog.kmoon.fun/zhlgd-cli%E5%B0%9D%E8%AF%95%E5%81%9A%E4%B8%80%E4%B8%AA-cli/</guid>
      <description>&lt;p&gt;一直觉得在命令行里敲命令然后回车，有一种莫名的爽感，特别是当黑框里返回自己想要的东西的时候。&lt;/p&gt;&#xA;&lt;p&gt;AI 这两年发展很快，CLI（Command Line Interface）也被重新推到了舞台中央。平时我们常用的 GUI，对人类当然很友好，但对 Agent 来说未必是最高效的工作方式。能直接发命令、拿结果、继续串联下一步，这种交互反而更适合 AI 干活。&lt;/p&gt;&#xA;&lt;p&gt;目前互联网上最火的 CLI 之一，应该就是字节跳动开源的 &lt;a href=&#34;https://github.com/larksuite/cli/blob/main/README.zh.md&#34;&gt;飞书 CLI（lark-cli）&lt;/a&gt;。它能让人类和 Agent 都在终端里操作飞书，比如收发消息、创建文档、多维表格、查询日历和会议等等。&lt;/p&gt;&#xA;&lt;p&gt;CLI 对 Agent 来说，本质上也是一种工具调用，而且很多时候还是更顺手的一种工具。所以，为了紧跟 AI 发展，我决定尝试自己写一个 CLI，看看这东西到底是怎么落地的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;先想需求&#34;&gt;先想需求&lt;/h2&gt;&#xA;&lt;p&gt;不管干什么，总得结合实际需求，毕竟没实际用途，做着也没劲。仔细想了想，我日常能接触到的场景也就来自两方面：学校、公司。公司这边肯定没法下手，压根不需要我；字节这边每天都会有层出不穷的工具，各种花色任你选择，说实话真有点“军备竞赛”那味道了。&lt;/p&gt;&#xA;&lt;p&gt;所以我把思路转移到学校这边。学校这边能做的感觉挺多，毕竟学校的网站、各种平台都比较老旧，有些交互也确实不够顺手。突然想到智慧理工大，武理一个大家日常都会用到的大 OA 平台，而且最近还刚改版，那就干脆拿它来练手，试试把它 CLI 化。&lt;/p&gt;&#xA;&lt;h2 id=&#34;开发流程&#34;&gt;开发流程&lt;/h2&gt;&#xA;&lt;p&gt;第一步肯定是先了解一下 CLI 的开发流程。我现在除了知道应该要用 Node.js 开发，其他几乎是零基础。那就老老实实先上网搜。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;  &#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-17594071&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片：搜索 Node.js CLI&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-17594071&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-08T12-32-53-351Z.png&#34;&#xA;            alt=&#34;搜索 Node.js CLI&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-17594071&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-08T12-32-53-351Z.png&#34;&#xA;            alt=&#34;搜索 Node.js CLI&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;figcaption&gt;搜索 Node.js CLI&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;找到了一篇 2019 年的知乎文章：&lt;a href=&#34;https://zhuanlan.zhihu.com/p/38730825&#34;&gt;用Node.js开发一个Command Line Interface (CLI)&lt;/a&gt;。本来我看到这个时间，第一反应是打算划走，但换个思路一想，这么久远的文章还能被我搜到，说不定反而是一篇优质内容，而且标题也非常直白，所以我还是决定仔细看一看。&lt;/p&gt;&#xA;&lt;p&gt;看完之后，大概就知道整个流程是什么样了。和我预想得差不多，确实是用 Node.js 开发，难怪平时网上安装 CLI 的时候都会让我先确保本地有 Node 环境，而且很多工具都是直接用 &lt;code&gt;npx&lt;/code&gt; 起。&lt;/p&gt;&#xA;&lt;p&gt;开发流程大概可以分成下面几步：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;创建 npm 模块&lt;/li&gt;&#xA;&lt;li&gt;增加 bin 入口文件&lt;/li&gt;&#xA;&lt;li&gt;全局安装&lt;/li&gt;&#xA;&lt;li&gt;发布&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;这么一看，整个路径其实还挺清晰的。先把最小可用版本做出来，再往上加子命令就行。&lt;/p&gt;&#xA;&lt;h2 id=&#34;环境配置&#34;&gt;环境配置&lt;/h2&gt;&#xA;&lt;p&gt;先简单配置一下 &lt;a href=&#34;https://nodejs.org/zh-cn&#34;&gt;Node.js&lt;/a&gt; 环境。我平时一般用 &lt;a href=&#34;https://www.nvmnode.com/&#34;&gt;nvm&lt;/a&gt; 管理 Node 版本，毕竟我之前真的因为版本差异排查过很久 bug，这种坑踩过一次就够了。&lt;/p&gt;&#xA;&lt;p&gt;Node 环境配好之后先检查一下：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;  &#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-01659321&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片：本地 Node 环境检查结果&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-01659321&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-08T11-45-40-376Z.png&#34;&#xA;            alt=&#34;本地 Node 环境检查结果&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-01659321&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-08T11-45-40-376Z.png&#34;&#xA;            alt=&#34;本地 Node 环境检查结果&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;figcaption&gt;本地 Node 环境检查结果&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;创建 npm 模块，直接执行：&lt;code&gt;npm init&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;  &#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-fb739af8&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片：使用 npm init 初始化项目&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-fb739af8&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-08T12-36-11-738Z.png&#34;&#xA;            alt=&#34;使用 npm init 初始化项目&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-fb739af8&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-08T12-36-11-738Z.png&#34;&#xA;            alt=&#34;使用 npm init 初始化项目&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;figcaption&gt;使用 npm init 初始化项目&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;这样就初始化好了整个项目：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;zhlgd-cli&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;1.0.0&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;武汉理工大学智慧理工大命令行工具&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;homepage&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://github.com/kmoonn/zhlgd-cli#readme&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;bugs&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://github.com/kmoonn/zhlgd-cli/issues&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;repository&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;git&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;git+https://github.com/kmoonn/zhlgd-cli.git&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;license&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;MIT&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;author&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Kmoon&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;module&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;main&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;index.js&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;scripts&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;test&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;echo \&amp;#34;Error: no test specified\&amp;#34; &amp;amp;&amp;amp; exit 1&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;type&lt;/code&gt; 这里选择 &lt;code&gt;module&lt;/code&gt;，因为新版 ES 模块化用的是 &lt;code&gt;import/export&lt;/code&gt;，现在做 CLI 基本都会用到。&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;commonjs&lt;/code&gt; 则是老版 Node.js 模块化，写法是 &lt;code&gt;require()&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;接着在 &lt;code&gt;package.json&lt;/code&gt; 里增加 &lt;code&gt;bin&lt;/code&gt; 字段：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;bin&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;zhlgd&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;./bin/index.js&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里也顺便记一下：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;zhlgd-cli&lt;/code&gt; 是项目名 / 包名&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;zhlgd&lt;/code&gt; 才是最终在终端里执行的命令&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;在项目根目录新建一个 &lt;code&gt;bin&lt;/code&gt; 文件夹，并在里面新建一个 &lt;code&gt;index.js&lt;/code&gt; 文件。&lt;/p&gt;&#xA;&lt;p&gt;文件结构长这样：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;zhlgd-cli/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── bin/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── index.js&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── package.json&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;编写 &lt;code&gt;bin/index.js&lt;/code&gt; 代码。&lt;/p&gt;&#xA;&lt;p&gt;第一行必须写：&lt;code&gt;#!/usr/bin/env node&lt;/code&gt;，意思是告诉系统用 Node 来运行这个脚本。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;ch&#34;&gt;#!/usr/bin/env node&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 你的命令行逻辑开始&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;✅ 智慧理工大命令行工具启动成功！&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;👉 命令：zhlgd&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后全局安装，把命令链接到本地环境。&lt;/p&gt;&#xA;&lt;p&gt;在项目根目录执行：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm link&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;和下面图片一样，基本就说明成功了。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;  &#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-6c8a91bc&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片：npm link 成功后的终端输出&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-6c8a91bc&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-08T11-45-00-454Z.png&#34;&#xA;            alt=&#34;npm link 成功后的终端输出&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-6c8a91bc&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-08T11-45-00-454Z.png&#34;&#xA;            alt=&#34;npm link 成功后的终端输出&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;figcaption&gt;npm link 成功后的终端输出&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;这个阶段主要是本地开发调试方便。我后面把它正式发到了 npm 上，所以现在除了 &lt;code&gt;npm link&lt;/code&gt; 这种本地联调方式之外，也可以直接下载安装：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm i zhlgd-cli&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;能走到这一步，我自己其实还挺开心的。因为它说明这个项目已经不只是我本地电脑上的一个小玩具，而是真的变成了一个别人也能装下来体验的 CLI。&lt;/p&gt;&#xA;&lt;p&gt;然后测试一下我们的命令。新开一个终端输入 &lt;code&gt;zhlgd&lt;/code&gt;，就可以直接看到输出：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;  &#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-3b0c4f1c&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片：首次运行 zhlgd 命令的输出&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-3b0c4f1c&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-08T11-44-22-324Z.png&#34;&#xA;            alt=&#34;首次运行 zhlgd 命令的输出&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-3b0c4f1c&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-08T11-44-22-324Z.png&#34;&#xA;            alt=&#34;首次运行 zhlgd 命令的输出&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;figcaption&gt;首次运行 zhlgd 命令的输出&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;到这一步，其实最小可用 CLI 就已经搭起来了。后面要做的，就是继续往里面塞真正有用的子命令。&lt;/p&gt;&#xA;&lt;h2 id=&#34;zhlgd-help&#34;&gt;zhlgd help&lt;/h2&gt;&#xA;&lt;p&gt;接下来先完成第一个最简单的命令，也是几乎每个 CLI 都会有的命令：&lt;code&gt;help&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;这里认识一个新东西，官方很常见的 CLI 开发框架：&lt;a href=&#34;https://github.com/tj/commander.js&#34;&gt;commander&lt;/a&gt;。&lt;/p&gt;&#xA;&lt;p&gt;先安装一下依赖：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm install commander&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;写着写着我发现，&lt;code&gt;help&lt;/code&gt; 其实根本不用自己手搓，框架已经给你做好了。&lt;/p&gt;&#xA;&lt;p&gt;下面是使用 commander 改造后的入口文件 &lt;code&gt;index.js&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;ch&#34;&gt;#!/usr/bin/env node&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 导入 commander 库，用于处理命令行&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Command&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;commander&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;program&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 配置 CLI 基础信息&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;program&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;zhlgd&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;version&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;1.0.0&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;-v, --version&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;description&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;一个基于 Node.js 开发的智慧理工大 CLI 工具&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 解析命令行输入的参数&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;program&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;parse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;process&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;argv&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这一步虽然简单，但我感觉挺关键。因为从这里开始，这个项目才不只是一个“能打印一句话的脚本”，而是开始往真正的 CLI 工具靠了。&lt;/p&gt;&#xA;&lt;p&gt;运行一下看看效果：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;  &#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-d0bc9da6&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片：zhlgd help 命令输出&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-d0bc9da6&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-08T12-31-14-420Z.png&#34;&#xA;            alt=&#34;zhlgd help 命令输出&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-d0bc9da6&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-08T12-31-14-420Z.png&#34;&#xA;            alt=&#34;zhlgd help 命令输出&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;figcaption&gt;zhlgd help 命令输出&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;zhlgd-login&#34;&gt;zhlgd login&lt;/h2&gt;&#xA;&lt;p&gt;下面开始啃一个硬骨头：&lt;strong&gt;登录&lt;/strong&gt;。毕竟只有先登录，后面的事情才做得下去。&lt;/p&gt;&#xA;&lt;p&gt;智慧理工大的登录是单点登录，大概流程如下：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;用户输入账号、密码 → 点击登录&lt;/li&gt;&#xA;&lt;li&gt;前端校验：账号/密码不能为空&lt;/li&gt;&#xA;&lt;li&gt;记住密码：写入 Cookie（加密存储）&lt;/li&gt;&#xA;&lt;li&gt;向后端请求 &lt;strong&gt;RSA 公钥&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;使用公钥 &lt;strong&gt;加密账号、密码&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;把加密后的账号密码塞进表单隐藏域&lt;/li&gt;&#xA;&lt;li&gt;自动提交登录表单 → 后端验证 → 登录成功 / 失败&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;每次登录时，前端都会先请求后端获取 RSA 加密公钥：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 获取 key&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ajax&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;rsa?skipWechat=true&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;dataType&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;json&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;success&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;encrypt&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;JSEncrypt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;encrypt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;setPublicKey&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;publicKey&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;#ul&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;encrypt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;encrypt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;u&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;#pl&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;encrypt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;encrypt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;#loginForm&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;submit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;网页里这一步看起来很自然，因为浏览器已经帮你把表单提交、跳转、Cookie 处理这些事情都包掉了。但放到 CLI 里，这些步骤就得自己一层层补出来。&lt;/p&gt;&#xA;&lt;p&gt;也就是说，在 CLI 里，登录这件事至少要做下面几步：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;请求 RSA 公钥&lt;/li&gt;&#xA;&lt;li&gt;用公钥加密账号和密码&lt;/li&gt;&#xA;&lt;li&gt;提交登录请求，拿到 ST ticket&lt;/li&gt;&#xA;&lt;li&gt;根据 ticket 继续跟重定向，换取完整 Cookie&lt;/li&gt;&#xA;&lt;li&gt;把 Cookie 保存到本地，后续命令统一复用&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;对用户名和密码加密之后，再提交登录请求获取 ST ticket，接着顺着重定向把完整 Cookie 换回来，并保存到本地，后续都拿这份 Cookie 作为鉴权使用：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 5. 用 ST 换取完整 Cookie&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;st&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;extractTicket&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;loginResp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;headers&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;location&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;followRedirects&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;SERVICE&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;?ticket=&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;st&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;cookieJar&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;commonHeaders&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 6. 保存&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;saveCookies&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cookieJar&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里我自己的体感是，&lt;strong&gt;登录这一关是整个 CLI 最麻烦的部分&lt;/strong&gt;。因为一旦 Cookie 这套机制没打通，后面所有需要鉴权的接口都跑不起来；但只要这一关打通了，后续子命令反而会顺很多，本质上就变成“带上鉴权去请求接口，再把结果整理一下输出”。&lt;/p&gt;&#xA;&lt;p&gt;下面演示一下登录过程。这里为了安全，密码输入做了隐藏处理：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;  &#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-4b7e8383&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片：zhlgd login 命令的隐藏密码输入效果&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-4b7e8383&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-14T02-16-43-242Z.png&#34;&#xA;            alt=&#34;zhlgd login 命令的隐藏密码输入效果&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-4b7e8383&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-14T02-16-43-242Z.png&#34;&#xA;            alt=&#34;zhlgd login 命令的隐藏密码输入效果&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;figcaption&gt;zhlgd login 命令的隐藏密码输入效果&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;到这里，&lt;code&gt;login&lt;/code&gt; 命令就算是跑通了。现在这个项目也已经正式发到了 npm 上，直接执行下面这条命令就可以安装：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm i zhlgd-cli&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;  &#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-6ae3548a&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片：npm&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-6ae3548a&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-14T03-20-20-659Z.png&#34;&#xA;            alt=&#34;npm&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-6ae3548a&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-14T03-20-20-659Z.png&#34;&#xA;            alt=&#34;npm&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;figcaption&gt;npm&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;所以如果你只是想直接上手体验，而不是自己从头本地联调，走 npm 安装会更方便。具体实现细节如果感兴趣，也可以直接看源码：&lt;a href=&#34;https://github.com/kmoonn/zhlgd-cli&#34;&gt;zhlgd-cli GitHub 仓库&lt;/a&gt;。&lt;/p&gt;&#xA;&lt;h2 id=&#34;zhlgd-msg--news&#34;&gt;zhlgd msg &amp;amp; news&lt;/h2&gt;&#xA;&lt;p&gt;登录搞定之后，我先做了两个最基础、也最容易验证成果的功能：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;zhlgd msg&lt;/code&gt;：查看我的消息&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;zhlgd news&lt;/code&gt;：查看校园新闻&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;之所以先做这两个，是因为它们很适合拿来验证整条链路是不是已经通了：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;登录是否真的拿到了可用 Cookie&lt;/li&gt;&#xA;&lt;li&gt;Cookie 是否已经被正确保存&lt;/li&gt;&#xA;&lt;li&gt;后续子命令是否能稳定复用这套鉴权能力&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;实现上其实没有想象中那么玄学，核心就是把两个接口分别转成 JavaScript 请求：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;apiRequest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;BASE_URL&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;/tp_up_new/up/newhome/getFpMsgList`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;MSG_TYPE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;3&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;apiRequest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;BASE_URL&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;/tp_up_new/up/newhome/getNewsNotice`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;channel&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;13962&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;真正关键的地方，还是请求时要把登录成功后保存下来的 Cookie 带上：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;/**&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * 带鉴权的 API 请求，未登录时自动提示&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; */&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;apiRequest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;isLoggedIn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;⚠️  未登录，请先执行 zhlgd login&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;process&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;exit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;cookies&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;loadCookies&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;resp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;axios&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;headers&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;API_HEADERS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;cookie&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;cookieHeader&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cookies&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;s1&#34;&gt;&amp;#39;User-Agent&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;UA&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;resp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;所以说，前面把 &lt;code&gt;login&lt;/code&gt; 打通之后，后面很多功能的开发思路就都统一了：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;找到对应接口&lt;/li&gt;&#xA;&lt;li&gt;搞清楚请求参数&lt;/li&gt;&#xA;&lt;li&gt;带上登录态发请求&lt;/li&gt;&#xA;&lt;li&gt;把返回结果整理成终端里更适合看的样子&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;下面测试一下实际效果：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;  &#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-25b81c94&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片：zhlgd msg 命令输出效果&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-25b81c94&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-14T02-22-55-918Z.png&#34;&#xA;            alt=&#34;zhlgd msg 命令输出效果&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-25b81c94&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-14T02-22-55-918Z.png&#34;&#xA;            alt=&#34;zhlgd msg 命令输出效果&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;figcaption&gt;zhlgd msg 命令输出效果&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;  &#xA;&#xA;&lt;figure&#xA;    class=&#34;image-caption image-zoom-container&#34;&#xA;&gt;&#xA;    &#xA;    &lt;input&#xA;        type=&#34;checkbox&#34;&#xA;        id=&#34;img-64a69e0d&#34;&#xA;        class=&#34;image-zoom-toggle&#34;&#xA;        aria-label=&#34;放大图片：zhlgd news 命令输出效果&#34;&#xA;    /&gt;&#xA;    &lt;label&#xA;        for=&#34;img-64a69e0d&#34;&#xA;        class=&#34;image-zoom-label&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-14T02-23-11-367Z.png&#34;&#xA;            alt=&#34;zhlgd news 命令输出效果&#34;&#xA;            loading=&#34;lazy&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;label&#xA;        for=&#34;img-64a69e0d&#34;&#xA;        class=&#34;image-zoom-overlay&#34;&#xA;    &gt;&#xA;        &lt;img&#xA;            src=&#34;https://cdn.kmoon.fun/2026/2026-06-14T02-23-11-367Z.png&#34;&#xA;            alt=&#34;zhlgd news 命令输出效果&#34;&#xA;            class=&#34;zoomable-image&#34;&#xA;        /&gt;&#xA;    &lt;/label&gt;&#xA;    &#xA;    &lt;figcaption&gt;zhlgd news 命令输出效果&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;看到这里，我自己其实还挺有成就感的。因为它已经不再只是一个能打印几行字的 demo，而是真的具备了“登录 -&amp;gt; 带鉴权请求 -&amp;gt; 输出结果”这一整套能力。&lt;/p&gt;&#xA;&lt;h2 id=&#34;写在最后&#34;&gt;写在最后&lt;/h2&gt;&#xA;&lt;p&gt;这次折腾 &lt;code&gt;zhlgd-cli&lt;/code&gt;，对我来说最有意思的地方，不是把 &lt;code&gt;help&lt;/code&gt; 命令跑起来，而是第一次比较完整地把“网页里浏览器偷偷帮你做的事情”，一点点搬到了 CLI 里。&lt;/p&gt;&#xA;&lt;p&gt;目前这个 CLI 至少已经把最难的登录流程打通了，也做出了 &lt;code&gt;msg&lt;/code&gt; 和 &lt;code&gt;news&lt;/code&gt; 这两个能实际用起来的子命令。后面如果继续往下做，其实就可以顺着这条路继续扩，比如把更多查询类功能搬进终端，甚至再往前走一步，给 Agent 直接调用。&lt;/p&gt;&#xA;&lt;p&gt;总之，这次尝试让我更直观地感受到一件事：CLI 这种东西，看起来很“黑框”，但一旦场景选对了，真的会很顺手。尤其是在 AI 时代，它不只是给人用的工具，很多时候也天然适合给 Agent 干活。&lt;/p&gt;&#xA;</description>
    </item>
  </channel>
</rss>
