Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## 0.5.3

### Features

- **`--filter-title-block-id` 精确过滤**:新增 CLI 选项与 `ConvertOptions.filterTitleBlockId`,按 heading 块 id 严格匹配目标章节,彻底规避同名标题歧义;与 `--filter-title` 互斥
- **`get-titles` 子命令**:一键列出 docx/wiki 文档全部标题(1~9 级),支持 `yaml` / `yaml-tree` / `json` / `tree` / `text` 五种输出格式与 `--max-level` 限级;不支持 sheets
- **`--filter-title` / `--filter-title-block-id` 注入父级标题**:命中深层标题时,自动把所有更高层级的祖先标题(仅 heading 块本身)按文档顺序一并输出,保留章节层级上下文;顶级命中不注入,跳级不伪造中间层,旁支兄弟自动排除
- **标题元信息结构化**:导出 `HeadingInfo`(`blockId` / `level` / `text`),`--filter-title` 未命中时错误信息改为与 `get-titles` 同形的 yaml 清单,AI/脚本可直接据此重选 blockId,形成错误恢复闭环

### Fixed

- **表格 `row_span` / `col_span` 容错**:飞书接口可能返回 `undefined`,解析时兜底为 1,避免崩溃

### Changed

- **docx 表格输出改为 Markdown 管道格式**:原 `<table>` HTML 输出(包含 `rowspan` / `colspan`)改为标准 GFM 管道表格;合并单元格按「复制顶格值」策略展开,单元格内容自动转义 `|` 与换行;`Table` 与电子表格 `Sheet` 现使用同一 `renderMarkdownTable` 实现
- **`--agent` 取值规范化**:CLI `--agent` 与环境变量 `LARK_DOCX2MD_AGENT` 显式接受 `stdout` 或 `local`;不带值的 `--agent` 仍等价于 `stdout`,但环境变量原 `true` 形式已不再支持,需改写为 `LARK_DOCX2MD_AGENT=stdout`
- **请求限速放宽**:`getDocxBlocks` 分页间隔 100 → 50ms、图片下载间隔 600 → 300ms,大文档转换更快
- **代码组织拆分**:将 `getTitles` / `buildTitleTree` 从 `converter.ts` 抽离至独立模块 `src/get-titles.ts`,URL 解析逻辑迁移至 `src/url.ts`
- **SKILL 工作流升级**:`skills/lark-docx2md/SKILL.md` 指定标题场景改为「`get-titles` → 匹配 blockId → `dl --filter-title-block-id`」三步式,支持单标题、多级路径、同名同路径等多种消歧场景

## 0.5.2

### Fixed
Expand Down
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ npx -y lark-docx2md@latest download <url>
| `--app-id <id>` | 飞书应用 App ID | `LARK_DOCX2MD_APP_ID` | — |
| `--app-secret <secret>` | 飞书应用 App Secret | `LARK_DOCX2MD_APP_SECRET` | — |
| `-o, --output <dir>` | 输出目录 | `LARK_DOCX2MD_OUTPUT` | `./larkDocx2mdOutput` |
| `--agent [mode]` | Agent 模式:日志 ERROR。不传值(或 `=true`)为在线模式,Markdown 输出到 stdout;传 `local` 则落盘后输出引导 AI 读取的提示词 | `LARK_DOCX2MD_AGENT=true\|local` | `false` |
| `--agent [mode]` | Agent 模式:日志 ERROR。不传值(或 `=stdout`)为在线模式,Markdown 输出到 stdout;传 `local` 则落盘后输出引导 AI 读取的提示词 | `LARK_DOCX2MD_AGENT=stdout\|local` | `false` |
| `--image-mode <mode>` | 图片处理模式:`local`(下载到本地)或 `online`(24h 临时链接) | `LARK_DOCX2MD_IMAGE_MODE` | `local` |
| `--filter-title <title>` | 按标题过滤:仅转换匹配标题及其下级内容(匹配到同级或更高级标题时截止) | — | — |
| `--filter-title-block-id <id>` | 按 heading 块 id 精确过滤(无同名歧义),通常配合 `get-titles` 子命令获取;与 `--filter-title` 互斥 | — | — |
| `--wb-format <format>` | 画板输出格式:`base64`、`inline-svg`、`svg`、`yaml` | `LARK_DOCX2MD_WB_FORMAT` | `svg`(agent 下默认 `yaml`) |
| `--wb-bg <style>` | 画板 SVG 背景:`none`、`dot` 或颜色值如 `#fff` | `LARK_DOCX2MD_WB_BG` | `none` |
| `--wb-image-mode <mode>` | 画板图片模式:`online`、`base64` 或 `local` | `LARK_DOCX2MD_WB_IMAGE_MODE` | `local` |
Expand All @@ -54,7 +55,26 @@ npx -y lark-docx2md@latest download <url>
> - `--agent`(在线):强制 `--image-mode=online`、`--wb-image-mode=online`;`--wb-format` 默认 `yaml`,仅允许 `inline-svg` / `yaml`;转换完成后 Markdown 直接通过 stdout 输出。
> - `--agent local`:强制 `--image-mode=local`、`--wb-image-mode=local`(Markdown、图片、画板中的图片均落盘);`--wb-format` 默认 `yaml`,仅允许 `inline-svg` / `yaml`;stdout 输出引导 AI 读取文件的提示词(包含绝对路径)。
> - 非 agent 模式下 `--wb-format yaml` 时:`--wb-image-mode` 强制为 `online`。
> - `--filter-title`:按标题文本精确匹配(忽略前后空格),收集该标题及其所有子级块,遇到同级或更高级标题时停止。未匹配到则报错提示。
> - `--filter-title`:按标题文本精确匹配(忽略前后空格),收集该标题及其所有子级块,遇到同级或更高级标题时停止。同名标题取首个;未匹配时错误信息附全文标题 yaml 清单。
> - `--filter-title-block-id`:按 heading 块 id 严格相等匹配,适用于同名标题或脚本化场景;通常先用 `get-titles` 查出目标 `blockId` 再传入。与 `--filter-title` 互斥。
> - **命中深层标题时自动注入父级标题(仅 heading 块本身)**:两个过滤参数均会按文档顺序补齐包含路径上的顶层→该标题的所有祖先标题,以保留章节层级上下文;不会引入旁支兄弟或伪造跳级。

## 子命令:`get-titles`

列出 docx/wiki 文档全部标题(不支持 sheets),配合 `--filter-title-block-id` 使用可避开同名歧义。

```bash
npx -y lark-docx2md@latest get-titles --agent <url>
```

| 参数 | 说明 | 默认值 |
|-----------------------|------------------------------------------------------------------------------------------|--------|
| `<url>` | 飞书 wiki/docx URL | — |
| `--max-level <n>` | 仅输出 `level <= n` 的标题(1~9) | `9` |
| `--format <format>` | 输出格式:`yaml`(扁平) \| `yaml-tree`(嵌套) \| `json` \| `tree`(json 嵌套) \| `text`(缩进的 markdown 标题) | `yaml` |
| `--agent [mode]` | 同 `dl`,降低日志级别 | — |

输出包含 `blockId` / `level` / `text`,可被 AI 直接消费用于选择目标章节(嵌套关系由 `yaml-tree` / `tree` 格式天然表达)。

## 功能

Expand All @@ -81,7 +101,7 @@ npx -y lark-docx2md@latest download <url>
| Callout | 高亮块 | `>[!TIP]` + 子块 |
| Divider | 分割线 | `---` |
| Image | 图片 | `![图片](url)` |
| Table / TableCell | 表格 | `<table>` HTML(支持合并单元格) |
| Table / TableCell | 表格 | GFM 管道表格(合并单元格按值展开) |
| QuoteContainer | 引用容器 | `> 子块内容` |
| Grid / GridColumn | 分栏布局 | 展平为子块内容 |
| Sheet | 电子表格 | GFM 表格(合并单元格自动展开) |
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lark-docx2md",
"version": "0.5.2",
"version": "0.5.3",
"description": "Convert Lark/Feishu documents to Markdown",
"type": "module",
"main": "./dist/converter.js",
Expand Down
80 changes: 32 additions & 48 deletions skills/lark-docx2md/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,69 +5,53 @@ description: 读取飞书 Wiki 文档或电子表格(`https://*.feishu.cn/wiki

# lark-docx2md

将飞书文档或电子表格 URL 转换为本地 Markdown 文件,命令标准输出包含文件绝对路径的提示词。
将飞书文档或电子表格 URL 转换为本地 Markdown 文件。

## 必要输入
## 输入

- 飞书文档 URL,格式为 `https://*.feishu.cn/wiki/...` 或 `https://*.feishu.cn/sheets/...`(可带 `?sheet=xxx` 参数)
- **URL**(必填):`https://*.feishu.cn/{wiki,docx,docs,sheets}/...`,可带 `?sheet=xxx`。
- **标题**(可选,仅 `wiki/docx/docs` 适用;`sheets` 跳过):用户想要的某一章节。按以下规则识别,命中即视为指定:
1. 显式关键词「标题/章节/小节/部分/节选/只要/其中的」+ 名词短语。
2. 成对引号 `""` `''` `“”` `‘’` `「」` `《》` 包裹的短文本。
3. 介词结构:`X 中的 Y`、`X 里的 Y`、`X 那一段`、`关于 X 的内容`。
4. 多级路径:`A 下/中/里的 B`、`A > B`、`A/B`、`A → B` → 取为有序路径 `[A, B, ...]`。
5. 用户要求「完整/全部/整篇」或无法稳定抽取 → **不指定**。

## 可选输入:标题(用于 `--filter-title`)

当用户只想要文档中的某一节内容时,可通过 `--filter-title "<标题>"` 仅转换匹配该标题及其子级的部分(标题文本需与文档中的标题**完全一致**,仅做 trim 比较)。

### 识别「用户是否指定了标题」的启发式规则 (重要)

按如下顺序判断,命中任一条即视为用户指定了标题,并将命中的文本作为 `<title>`:

1. **显式指令**:用户消息中出现「标题」「章节」「小节」「部分」「节选」「只要/只读/仅要/仅读」「其中的」等关键词,并紧跟一个可识别的名词短语或引号文本。
- 例:`只要「接入指南」这一节` → title = `接入指南`
- 例:`把其中“常见问题”部分转成 md` → title = `常见问题`
2. **引号包裹**:用户消息中出现成对的引号(`""` / `''` / `“”` / `‘’` / `「」` / `《》`)包裹的短文本,且语义上指代文档中的某一节。
- 例:`读一下文档的「部署流程」` → title = `部署流程`
3. **介词结构**:形如 `URL 中的 X`、`文档里的 X`、`X 那一段`、`关于 X 的内容` 等结构,X 为短名词短语(一般 ≤ 30 字、不含句号/换行)。
4. **未命中即视为未指定**:若用户仅给出 URL、或要求「完整」「全部」「整篇」内容,或无法稳定抽取标题文本,则**不要**添加 `--filter-title`。

### 标题文本的规范化

- 去除首尾空白与包裹用的引号。
- **保留**原文大小写、标点、空格与中英文字符(过滤器按精确匹配比较)。
- 不要自行翻译、改写或补全标题。
- 命令行中用双引号包裹;若标题内含英文双引号,用 `\"` 转义。
抽到的标题文本:去首尾空白与包裹引号;保留原文大小写、标点、空格、中英文;不翻译/改写/补全。

## 工作流程

1. 验证 URL 包含 `/wiki/` 或 `/sheets/`。
2. 按上文规则判断用户是否指定了标题,并据此构造命令:
### sheets 或未指定标题

- **未指定标题**(默认):
URL 含 `/sheets/`,或 docx/wiki 下未识别出标题,直接:

```bash
npx -y lark-docx2md@latest dl --agent local "<url>"
```
```bash
npx -y lark-docx2md@latest dl --agent local "<url>"
```

- **指定了标题**:在上述命令基础上追加 `--filter-title "<title>"`:
### docx/wiki 且指定标题(三步)

```bash
npx -y lark-docx2md@latest dl --agent local --filter-title "<title>" "<url>"
```
1. 取标题表:

> **注意**:`--agent local` 参数在任何情况下都不可省略,缺少它会导致输出格式不适合 AI 处理。
```bash
npx -y lark-docx2md@latest get-titles --agent local "<url>"
```

3. 捕获标准输出,从提示词中解析出 Markdown 文件的绝对路径。
4. 读取该绝对路径对应的 Markdown 文件,获得文档完整内容。
2. 匹配标题并获取 `blockId`:
- 单标题:找 `text === <title>`。唯一直接用;多候选用上下文(父节点关键词)消歧。
- 同父级同名兄弟:按同父节点下 children 的出现顺序列出全部候选,附上下文提示回问用户选择。
- 无候选:报告并展示可用标题。

## 关联图片读取要求
3. 用 `blockId` 下载:

- Markdown 文件中的图片以本地相对路径引用(如 `static/xxx.png`),画板内的图片同样如此。
- 读取 Markdown 后,**必须**一并读取其中引用的所有图片文件内容,以确保对文档的理解包含图片信息。
```bash
npx -y lark-docx2md@latest dl --agent local --filter-title-block-id "<blockId>" "<url>"
```

## 异常处理
### 读取结果

- 如果 URL 格式无效,要求提供有效的 `https://*.feishu.cn/wiki/` 或 `https://*.feishu.cn/sheets/` 链接。
- 如果输出为空,报告所使用的命令并附上 stderr 关键信息。
从 `dl` 的 stdout 解析 Markdown 绝对路径并读取该文件;同时**必须**读取其中引用的所有图片(含画板内图片,路径形如 `static/xxx.png`)。

## 响应风格
## 约束

- 保持简洁、以执行为导向。
- 转换成功时附上准确的文件路径。
- 当用户输入不完整时,明确指出所做的假设。
- `dl` 必须带 `--agent local`;`get-titles` 必须带 `--agent`。
Loading
Loading