npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

easy-feishu-card

v0.4.5

Published

生成一张飞书卡片,你本不应该知道那么多。写一份 YAML,编译为标准飞书卡片 JSON(schema 2.0),可选发送到聊天。

Readme

easy-feishu-card

生成一张飞书卡片,你本不应该知道那么多。

用一份 YAML 描述飞书卡片,编译成标准飞书卡片 JSON(schema 2.0),可选发送到聊天。

飞书原生卡片 JSON 嵌套深、样板多、容易写错结构。本项目在它之上加了一层更高抽象、约束更强、可测试的 YAML 方言(DSL):你(或 LLM)只写 YAML,编译器兜住正确性——设默认值、做别名与字号映射、拦住非法结构。剩下的飞书细节,你本不应该知道那么多。

产品形态上它是「一份提示词 + 一个 CLI」:

  • 提示词 = prompt/card-dsl-spec.md,这门 YAML 方言的语言规范(组件、语法、硬性规则)。教会 LLM 怎么写。也从库导出为字符串 CARD_DSL_SPEC,可直接拼进 agent 的 system prompt。
  • CLI / 库 = 把 YAML 编译为飞书 JSON、发送、读回验证的确定性工具链。保证写出来的东西一定对、能发。

代码形态上它是「DSL + 转译器」:compile/ 是一个前端(parse → AST)/后端(AST → 飞书 JSON)解耦的 source-to-source 编译器。


安装

npm install        # Node >= 18(用到全局 fetch)

发送/验证需要环境变量:

| 变量 | 用途 | |---|---| | FEISHU_APP_ID | 应用 ID | | FEISHU_APP_SECRET | 应用密钥 | | FEISHU_CHAT_ID | 目标聊天(发送用) |

编译(compile)不需要发送类环境变量;但默认会联网访问 enumless 网关解析 emoji/icon(无网时那些标记按“解析不到”删除)。要纯离线编译请用库接口传 { resolve: false }


命令行用法

node bin/easy-feishu-card.mjs compile card.yaml            # YAML → 卡片 JSON(打印到 stdout;默认联网解析 emoji/icon)
node bin/easy-feishu-card.mjs compile card.yaml -o out.json # 写入文件
node bin/easy-feishu-card.mjs send    card.yaml            # 编译(+上传图片)→ 发送,打印 message_id
node bin/easy-feishu-card.mjs render  card.yaml            # 发送后自动读回对比,验证飞书完整接受
node bin/easy-feishu-card.mjs verify  <message_id> expected.json  # 读回某条消息 → 与期望 JSON 对比差异

render = send + 读回验证,是「飞书是否原样接受了我们发送的卡片」的往返检查。


作为库使用(JS / TS)

CLI 只是薄壳;核心能力都是可 import 的纯函数/类。从包入口导入:

import { compile, FeishuClient, resolveImages, resolveEmojis, resolveIcons, diffCards } from 'easy-feishu-card';

// 1) 编译:异步。默认联网把 :token: 表情、standard_icon 的 token 语义解析为真实
//    飞书 key/token(解析不到则删除)。保留飞书风格信封 { code, msg, data },失败不抛异常。
const { code, msg, data: card } = await compile({ content: yamlString });
if (code !== 0) throw new Error(msg);

// 2) 发送(需要 FEISHU_APP_ID / FEISHU_APP_SECRET)
const client = new FeishuClient();          // 凭证从 env 读,也可传 { appId, appSecret, token }
await resolveImages(card, client);          // 上传卡片内带 URL/本地路径的图片,换成 img_key(emoji/icon 已在 compile 阶段解析)
const messageId = await client.sendCard({ chatId: process.env.FEISHU_CHAT_ID, card });

// 3) 读回验证(可选)
const readback = await client.getRawCard(messageId);

需要纯离线、确定性输出(不联网、原样保留 :token: 与图标)时,传 { resolve: false }

const { data: card } = await compile({ content: yamlString, resolve: false });

也可用只含编译逻辑的子路径入口(同样支持 resolve 选项):

import { compile } from 'easy-feishu-card/compile';

TypeScript 用户有随包的 index.d.tscompile(...) 返回 Promise<CompileResult>dataCard

导出一览

| 导出 | 签名 | 说明 | |---|---|---| | compile | ({ content, resolve?, fetch? }) => Promise<{ code, msg, data }> | YAML 方言 → 飞书卡片 DSL。异步,默认联网语义解析 emoji/icon(resolve:false 则纯离线原样保留)。 | | FeishuClient | new (opts?) | 轻量飞书 IM 客户端:sendCard / getRawCard / uploadImage / token。 | | resolveImages | (card, client) => Promise<card> | 上传卡片内待解析的图片,原地替换为 img_key。仅发送路径需要。 | | resolveEmojis | (card, opts?) => Promise<card> | 把 richtext 里的 :token: 语义检索为真实飞书表情 key(英文词效果最佳),原地改写。低分/无匹配/离线则删除标记并 warn,结果写 .emoji-cache.jsoncompile 默认已调用;仅在 compile({resolve:false}) 后想手动解析时才需单独调。 | | resolveIcons | (card, opts?) => Promise<card> | 把 standard_icon 的 token 语义检索为真实图标 token。validate-then-replace:已存在的保留,未知的按最近匹配替换并尽量保留风格(outlined/filled/colorful)。低分/无匹配/离线则删除图标节点并 warn,结果写 .icon-cache.jsoncompile 默认已调用;仅在 compile({resolve:false}) 后想手动解析时才需单独调。 | | diffCards | (generated, readback) => { equal, diffs } | 语义对比生成卡片与读回卡片。 | | normalizeCard | (card) => card | 标准化(剥离编辑器默认值、归一 img_key),供往返对比。 |


编写 YAML

完整的组件、富文本语法、硬性规则见 prompt/card-dsl-spec.md(这门方言的语言规范,也从库导出为 CARD_DSL_SPEC)。设计取舍见 DESIGN.md。最小示例:

header:
  color: blue
  title: 周报 · 第25周
  subtitle: 2026-06-20 ~ 06-26
config:
  summary: 周报已生成
body:
  - richtext: "**本周核心指标**:完成 29 单"
  - hr:
  - linkbtn: {text: 查看详情, type: primary, url: https://feishu.cn/x}

开发

npm test          # 一致性测试(conformance/ 下 .yaml 输入配 .dsl.json 期望输出)
npm run regen     # 重新生成一致性 fixtures
node tests/smoke-exports.mjs   # 库导出冒烟测试

conformance/*.yamlconformance/*.dsl.json 锁死「同一份 YAML 永远编译出同一个 JSON」的语义契约。新增组件见 compile/blocks/NEW-COMPONENT.md

架构

YAML 方言
  └─ compile/yaml-source.mjs   前端:切分 header / config / body
  └─ compile/to-ast.mjs        前端:元素列表 → AST(与输入格式无关的中间表示)
  └─ compile/render.mjs        后端:AST → 飞书卡片 DSL
     + compile/blocks/*        各组件的字段映射
publish/   FeishuClient、图片上传解析(运行时 / IO)
verify/    读回对比(往返验证)
bin/       CLI(以上全部之上的薄壳)

渲染层与输入格式无关——理论上可替换前端(如另一种语法)复用同一后端。