| name | tabdoc-operator |
| description | 文档操作——创建、编辑、检索、整理叙事性长文档、 报告、需求 spec、会议纪要、知识沉淀。用户要写 / 改 / 查长文档时使用;正文给出 `tabtin doc` CLI 的稳定操作流程。
|
| metadata | {"version":"0.14.6","tabtin":{"category":"doc","displayName":"TabDoc Operator","tags":["document","knowledge","search"],"autoActivateFor":["tabdoc"],"tools":["execute_command"]}} |
TabDoc Operator
任务涉及创建 / 编辑 / 检索 / 组织 TabDoc 文档时使用本 skill。
范式说明:tabdoc 操作走 tabtin doc CLI(通过 execute_command 调),不依赖 FC 工具。这跟其他已启用的内置 App 一致。
如果你以前看过的版本里有 tabdoc_create_document / tabdoc_update_document 等 FC 工具——它们已全部退役,你不会在工具清单里看到。
当前网页生成 TabDoc是通用网页导入:先由 Browser Operator 锁定当前 Tab 的 <locked-tab-id>,执行 tabtin browser print --include all --tab-id <locked-tab-id> --save <path.md>,再用本 skill 建文档。来源站点不改变这条路线。
只有用户明确要求迁移飞书云盘、飞书知识库或飞书资产时,才用 skills_read("app:tabtin-integrations-lite-pack/feishu-import-to-org") 与 tabtin feishu *;仅凭 URL 或域名不能改走飞书专用通道。
当前网页导入契约(Hard Contract · 必读)
- 接手 Browser Operator 通过当前 Tab 产出的
--include all Markdown 与资源清单;不能用默认 print 的纯正文产物,因为它会剥掉图片、链接和表格。
- 正文里的公开长期 URL 可以保留。
blob:、登录态图片、短期签名 URL 等资源必须先用 tabtin browser resource download --tab-id <locked-tab-id> --url <url> 从同一锁定 Tab 下载,再用 tabtin oss upload 得到稳定 URL,替换 Markdown 中对应的图片 / 资源引用后创建文档;文档已创建时可用 doc insert-image 补入本地图片。
- 用
tabtin doc create --title <title> --markdown @<path.md> 建文档;若需走 Markdown 草稿转换,也必须把 doc import markdown 的结果继续写入 create / save-content,不能把草稿当成最终文档。
- 创建后读取目标文档,按源页面与采集结果核对标题 / 正文,以及图片、链接、表格的数量和关键内容。任何资源下载、上传、替换或渲染失败都要列为缺失项;不得在仍有缺失或失败时声称完整导入成功。
正文标题契约(Hard Contract · 必读)
TabDoc 的 title 就是整篇文章标题,content 不是一份需要自带标题的独立 Markdown 文件。Agent 新建或整篇重写正文时:
tabtin doc create --title "<标题>" 负责整篇文章标题;上传的 Markdown content 不得再以任何文章级 # <标题> 开头。
- 正文直接从导语开始;章节从
## 开始。不要为了“Markdown 完整性”补一个全文大标题。
- 更新既有文档时同样遵守:title 留在元数据,content 首块不是 H1。
- CLI 只做结构兜底:create 的 content 若以 H1 开头,发送前移除首个 H1;
save-content 显式传 --title 时同样移除首个 H1,没传 --title 则拒绝这类写入。
完成标准:生成草稿后检查首个非空块;它不能是 H1。
输出契约(Hard Contract · 必读)
任何 chat 回复里出现 doc 标题(list / search / create / read / 提到某文档),都必须写成 markdown link——否则用户得手动去侧栏找,体验断裂。canonical 形态唯一:
[<title>](tabtin://resource/document/<id>?hint=tabdoc)
| 字段 | 取值 | 不能写成 |
|---|
| path 第 1 段 | document | ❌ doc tabdoc documents |
hint | tabdoc | ❌ document tabdocs doc |
list / search 结果回复样板(直接抄):
找到 3 篇文档:
| 标题 | 创建 | 版本 |
|------|------|------|
| [厦门旅游](tabtin://resource/document/8a21f144-46d2-4f8c-ae08-519a6fce9605?hint=tabdoc) | 5/28 12:39 | v3 |
| [杭州旅游](tabtin://resource/document/a926ea31-9902-48c1-970e-d0f6a8fa4ae5?hint=tabdoc) | 5/28 02:58 | v4 |
| [东北旅游](tabtin://resource/document/5f4d3938-ea46-4bd3-8b8b-22bba7a6a69c?hint=tabdoc) | 5/27 13:24 | v3 |
search 带 snippet 的样板:
找到 2 条匹配 "项目进展":
- [Q3 周报](tabtin://resource/document/d7f34d67-2c1a-4b6e-9f30-8e5a1c7d4b21?hint=tabdoc) — *…本周项目进展顺利,三个里程碑按期…*
- [产品规划](tabtin://resource/document/ad070d7b-58e3-4f92-b1c6-3d9a72e05f48?hint=tabdoc) — *…下半年核心项目进展将聚焦在…*
<id> 必须写完整——从 CLI 输出里原样复制整个 id,一个字符都不能省。snippet 正文可以用 … 截断,链接里的 id 绝对不行:截断的 id(如 02eda024-5f11-…)会原样进用户界面的产物卡片,点击后端直接报「document_id 不是合法 UUID」。
禁止形态(用户点不动):
**厦门旅游** ❌ 纯加粗,无链接
| 厦门旅游 | ❌ 表格里纯字符串
**厦门旅游** 🟢 ❌ 装饰性 emoji 替代不了 link
[厦门旅游](tabtin://resource/doc/<id>?hint=document) ❌ type/hint 双 typo
[厦门旅游](tabtin://resource/document/<id>?hint=document) ❌ hint 错(应为 tabdoc)
[厦门旅游](tabtin://resource/document/02eda024-…?hint=tabdoc) ❌ id 被截断(必须完整复制)
tabtin://resource/document/02eda024-5f11-4d4a-85c2-… ❌ 裸链接 + 截断 id,双重违约
字段反例对照表 + Parser 兜底别名说明见 Pattern 2 文末「回复模板」段;本契约一切场景适用,别名是兜底,不是写法许可。
输出协议与 jq 路径约定
--format json 输出的 stdout 是完整 envelope:
{"ok": true, "data": {"<真实业务数据>": "..."}, "meta": {"...": "..."}}
所以 jq 路径要分清两种情形:
- 外部管道
| jq 拿到的是完整 envelope,所有路径都要 .data. 前缀:
tabtin doc read doc_xxx --format json | jq '.data.document.latest_version'
- CLI 内置
--jq 自动 unwrap 一层 envelope,直接写 .foo 不加 .data.:
tabtin doc read doc_xxx --jq '.document.latest_version'
下面所有 Pattern 的 jq 示例都遵循此约定。如果你跑出来 jq 拿不到值,99% 是这两种路径混了——
重新检查你用的是 | jq 还是 --jq。
正文字段命名速查
TabDoc 读写字段名不是同一套,直接调 REST 或写 jq 时按场景区分:
- 创建请求:
initial_content_markdown / initial_content_pm_json / initial_content_plaintext;CLI doc create --markdown 会替你映射。
- 保存请求:
content_markdown / content_pm_json / content_plaintext;CLI doc save-content --markdown 会替你映射。
- 读取响应:
doc read 返回的 content 里用 description_markdown / description_json / description_plaintext。
- 分块读取:
doc chunks 的二进制块字段是 blob_b64,不是 blob。
CLI 命令清单
| 命令 | 用途 |
|---|
tabtin doc list | 列出文档(支持 --page --page-size,与后端分页契约一致) |
| `tabtin doc create --title [--markdown <文本
| @文件 |
| `tabtin doc move --parent-item-id <context_item_id> | --root` |
tabtin doc search --query <keywords> | 全文搜索(有搜索词时必须用它,别用 list 冒充 search) |
tabtin doc search-blocks <id> --query <keywords> | 在单篇文档内搜索正文命中的具体 block,返回可直接给 read-block / update-block 使用的 block-id |
tabtin doc read <id> | 读取当前或指定云端 TabDoc 的完整内容 + 元数据(含 latest_version / updated_at);上下文给出 current_doc_id 时直接把 id 传给它 |
tabtin doc chunks <id> [--start <n>] [--limit <n>] | 超大文档按块分页读取(每块含 chunk_index / plaintext_preview / blob_b64)——比一次 read 全文省 token |
| `tabtin doc export --export-format markdown | html |
tabtin doc delete <id> | 归档(软删除,第一级) |
tabtin doc list-blocks <id> | 列文档顶层 block 大纲(id / type / level / preview / index)——比 read 省 token |
tabtin doc update <id> --title / --status / --parent-id / --icon / --cover-image / --cover-position / --tags | 改元数据(不改正文)。--cover-position 是封面纵向焦点 0~1;--tags 整组替换;至少传一个字段。知识库树改挂用 doc move,不要用这里的 --parent-id |
| `tabtin doc save-content [--title ] --markdown <文本
| @文件路径 |
tabtin doc read-block <id> <block-id> | 读单个 block 的 markdown(省 token;先 list-blocks 拿 block-id) |
tabtin doc read-section <id> <heading-block-id> | 读整章:标题 + 其后正文直到下一个同级/更高级标题前(heading-block-id 由 list-blocks 给;比逐块 read-block 拼接省往返) |
tabtin doc update-block <id> <block-id> --markdown <...> | 精准替换单个 block(只动这一块、不碰其余)。block-id 由 list-blocks 给 |
|
长文可靠写入(Hard Rule)
- 仅当 Agent 为新建或整篇更新长 TabDoc 正文而新建临时 Markdown 草稿时,必须先用
write_file
写到相对工作区路径 .agent-drafts/<slug>.md,再执行
tabtin doc create --title "<title>" --markdown @.agent-drafts/<slug>.md --format json。
禁止把全文内联进 shell command,也禁止用 shell > 重定向或 heredoc 写草稿。
- create 只带可靠的最小参数;icon、cover、tags 等易错元数据在创建成功后用
tabtin doc update <document-id> ... 后置设置或修正,正文不重写。
知识库挂载例外:要挂到侧栏父资源时,create 当场传 --parent-item-id <context_item_id>
(写 ContextItem.parent)。不要用 --parent-id——那是 Document 内页树,侧栏看不到。
已有文档改挂用 tabtin doc move <id> --parent-item-id <ctx>|--root(勿用 doc update --parent-id)。
- create 仅在 CLI 明确返回参数/校验错误时,才可只修正短参数并复用同一份草稿文件重试;
不要重新生成或重写正文。网络超时、断连等结果未知时,不得直接重试 create:后端没有
幂等键,先运行
tabtin doc search --query "<title>" --format json 核对是否已创建;若无法唯一确认,
必须请求用户确认后再继续。
- metadata update 失败时,正文草稿与已创建正文不受影响。明确校验错误只修正元数据再重试 update;
如果 CLI 实际返回
409,才遵循既有通用处理:先 tabtin doc read <document-id> --format json
获取当前 latest_version,判断后带新的 --base-version <latest-version> 重试 update。这不是该创建后元数据流程的并发保证;全程不要重新生成或重写正文,也不要调用 save-content。
- 短文且所有参数已确定时,可走一步创建快捷路径;草稿用于当前任务的可恢复重试,任务成功后按
现有工作目录策略保留,不自动删除,也不要求用户清理。
Workflow Patterns
9 个工作流范式(创建写入 / 搜索摘要 / 省 token 阅读 / 重组删除 / 提取正文 / 版本管理 / 协作者管理 / 导入外部内容 / 文档分享)与 chat 回复模板见 references/workflow-patterns.md。
资源导航(按需读取)
references/workflow-patterns.md:当你要套用完整工作流模板、或需要可直接复用的 chat 回复模板时读取;日常命令调用优先看本文件主流程与命令清单,不默认加载整份参考文档。
并发保护(base-version)
save-content / update / doc version restore / doc version save 都接受 --base-version:
- 你读文档时(
tabtin doc read)拿到 latest_version
- 写回时把
latest_version 当作 --base-version 传进去
- 服务端发现版本变了(别人/别的进程同时改了文档)会返回
409 VERSION_CONFLICT
- 收到 409 → 重新
read → 决定是合并还是覆盖 → 重试
不传 --base-version 也能写成功,但失去并发保护。chat 里 LLM 单独操作时可省,多 Agent / 多用户场景必传。
写 markdown 给 TabDoc 时的常见雷区
TabDoc 后端用自研扫描式 markdown 解析器(不是标准库),有些 LLM 常出的写法会调用成功但文档烂掉——
后端不会报错,agent 完全无感。下面是经实测确认的高频盲区:
- 价格场景多个
$:总价 $5 加 $10 中间 "5 加 $" 会被吞为公式 latex。
正确写法:总价 \$5 加 \$10(反斜杠转义)或 总价 `$5` 加 `$10`(反引号包代码)。
- 块级公式 / 代码块未闭合:
$$ 和 ``` 必须成对,否则吞光后续所有内容。
写完检查一遍奇偶。
- Agent 写入时只使用
:::tabdata directive。Docusaurus/VuePress 的
:::note / :::warning / :::callout 全部会退化为带 ::: 的字面段落。需要警告框就用
> ⚠️ 警告内容 blockquote 替代。
- 公式语法:只支持
$...$ 行内 + $$...$$ 块;LaTeX \(...\) \[...\] 不识别。
:::tabdata attr 必须双引号::::tabdata{tableId="tbl-001"} 对,:::tabdata{tableId=tbl-001}
会在 CLI/API 硬失败(不再静默丢 tableId)。嵌入多维表请用一等命令
tabtin doc embed-table <doc-id> --table-id <table-id>,不要手写 directive。
普通 markdown 管道表(| a | b |)只生成 table block,不等于 tabdataBlock。
- 正文里手写 HTML 标签:
<div> <img> 等会保留为文本但渲染层会被 sanitize 吃掉。已有公开图片 URL 用 markdown
 语法;本地图片文件用下面的「图片插入」一条命令自动上传+拼块。要嵌入整块交互式 HTML(架构图/原型/可视化)不要往正文塞 <html>——用下面的「HTML 块」。
- 多行 Markdown 禁止在 shell 双引号里写字面
\n:zsh / PowerShell 双引号
不会把 \n 展开成真实换行,CLI 也不解码——有序列表会变成带 \n 字样的单行段落。
标题 + 列表等多行内容必须 write_file → --markdown @.agent-drafts/<slug>.md,或 --markdown -。
CLI 会对「无真实换行 + 结构向字面 \n」硬失败并提示改姿势。
- 含
$a / $x 的公式禁止放进 PowerShell/zsh 双引号:双引号会把未定义
$变量 展开为空,公式变成 :^2 - b^2...$ 这种残片,编辑器也无法 KaTeX 渲染。
公式 / 多行内容一律 write_file → --markdown @.agent-drafts/<slug>.md(或 stdin)。
不需要专用 doc insert-formula—— / 就是公式入口。
CLI 会对「只剩行尾单个 + LaTeX 残片」硬失败。
(CLI 在 doc save-content / doc create --markdown / doc import markdown 等写入口会在 stderr 打 warning
提示上述 1/2/3 类问题,但不阻塞调用——agent 看到 warning 自己决定要不要改 markdown 再重试。
:::tabdata 无引号/空 tableId 是硬失败,见 ;第 7 类字面 \n、第 8 类 shell $ 展开由 CLI 硬拦。)
图片插入(本地文件 → OSS → 正文)
什么时候用:你手上是一张本地图片文件(截图、生成的图表、下载的图片),想放进文档正文——标准 Markdown 图片,不是交互式内容。已有现成的公开图片 URL 时,直接在正文写 (雷区 6)即可,不需要这条命令,只有本地文件才需要先上传。
tabtin doc insert-image <document-id> --file ./chart.png --alt "销售趋势图"
tabtin doc insert-image <document-id> --file /tmp/screenshot.png --after <block-id>
雷区:
- 支持 png/jpg/jpeg/gif/webp/svg;上传路径白名单同
insert-html——只接 $HOME 或 /tmp 下的路径(symlink 会被拒),单文件 ≤100MB。
- 失败可重试不必重传——若上传成功但插块失败,错误
detail 里保留 file_id + 已拼好的 markdown + 一条 recovery_command,直接跑那条 doc insert-block 补写即可,不用重新上传。
- 生成的是标准 Markdown 图片(无沙箱 iframe);需要自定义交互 HTML(可点可拖的架构图/原型/可视化)用下面的「HTML 块」,不要拿图片凑合。
- 这条命令目前只覆盖插入;要替换已插入图片的图(≈
update-html 对图片的等价物)暂无一等命令,改法是 list-blocks 找到目标块、重新 insert-image 插一张新图、再 delete-block 删旧块。
HTML 块(交互式内容嵌入)
什么时候用:你产出了一份自包含的单文件 HTML——交互式架构图、脑暴板、原型、数据可视化(内嵌 CSS/JS、可点可拖)——想把它作为一个块放进文档。对标飞书:HTML 以文件附件上传,文档块只存引用,前端用沙箱 iframe 在线渲染。这跟"正文里写 <div>"(会被 sanitize 吃掉,见雷区 6)是两回事。
需要静态图用 ;需要数据表用 :::tabdata;只有需要自定义交互 HTML 才用 HTML 块。
插入:一条命令搞定(私有上传 OSS + 拼块)
tabtin doc insert-html <document-id> --file ./architecture.html --title "系统架构图" --height 600
tabtin doc insert-html <document-id> --file /tmp/dashboard.html --after <block-id>
编辑回路:读 → 授权下载 → 本地改 → 重传替换
HTML 块的正文不在文档里、在 OSS 上,所以"改 HTML"是授权下载→改→重传,不是改 markdown、也不是匿名 curl src:
tabtin doc read-block <document-id> <block-id> --jq .markdown
FILE_ID=$(tabtin doc read-block <document-id> <block-id> --jq .markdown | grep -oE 'fileId="[^"]+"' | head -1 | sed 's/fileId="//;s/"//')
curl -fsSL -H "Authorization: Bearer $TABTIN_TOKEN" \
"$TABTIN_API_BASE/api/tabdoc/documents/<document-id>/html-artifacts/$FILE_ID" \
-o /tmp/edit.html
tabtin doc update-html <document-id> <block-id> --file /tmp/edit.html
雷区(必读):
- 新 HTML 默认私有,权限跟随所属文档——成员按文档 viewer ACL 读;访客仅在文档开启分享后按 DocumentShare 规则读。不要假设
src 可匿名打开;历史公开直链(旧块非空 src)仍可能可达,但这不是新契约。
- iframe 是沙箱——渲染时没有宿主权限:拿不到 TabTin 的 cookie/登录态、调不了 TabTin API、跨不了同源。HTML 要能独立运行。
- 必须自包含单文件——CSS/JS 尽量内联进这一个
.html;外链资源必须是 https 且沙箱内可达(公网 CDN 可以,内网/需登录的不行)。
- 上传路径白名单——
--file 只接 $HOME 或 /tmp 下的路径(symlink 会被拒),单文件 ≤100MB。先把 HTML 写到 ~/ 或 /tmp/ 再传。
- 失败可重试不必重传——若"上传成功但插块/替换失败",错误
detail 里保留 file_id + 已拼好的 markdown + 一条 recovery_command,直接跑那条 doc insert-block / doc update-block 补写即可,不用重新上传。
用户给了 URL 不是 doc_id 怎么办
agent 拿到的 doc id 99% 是 chat 上下文里前一步返回的字面 id(如 doc_xxx)。但偶尔用户会贴
TabTin URL 进 chat:
https://www.example.com/docs/doc_xxx
tabtin://resource/document/doc_xxx?hint=tabdoc
CLI 不解析 URL,直接传会 404。提取方法:
DOC_ID=$(echo "$user_input" | grep -oE 'doc_[a-zA-Z0-9]+' | head -1)
tabtin doc read "$DOC_ID"
Rules
- 回复里凡出现文档标题,必须写成
[<title>](tabtin://resource/document/<id>?hint=tabdoc)——表格 / 列表 / 散文里都一样;纯文字 / 加粗 / **title** 都算违约(顶部「输出契约」段有 list / search 可抄样板)
- 链接里的
<id> 必须完整复制——禁止 … / ... / 手动截断;不带 label 的裸 tabtin:// 链接也禁止(截断 id 会进产物卡片且点击必失败)
- 用户说「找/搜/检索 XX 文档」必须走
tabtin doc search,不要降级为 doc list 客户端字符串过滤;禁止 rag_search 0 条后改 doc list 凑数
- 创建文档前先
tabtin doc search 看是否已有同名 / 同主题文档,避免重复
- 标题要简洁有意义,方便后续搜索
update 改元数据 vs save-content 改正文是两个端点——别混
- CLI 写正文使用
--markdown:仅当 Agent 为新建或整篇更新长 TabDoc 正文而新建临时 Markdown
草稿时,先用 write_file 写入相对工作区路径 .agent-drafts/<slug>.md,再以
--markdown @.agent-drafts/<slug>.md 创建;禁止把全文内联到 shell command,也禁止用 shell
> 重定向或 heredoc 写草稿。创建后的元数据修正走
doc update:校验错误先修元数据;如果 CLI 实际返回 409,才按既有通用处理重新 doc read 并带
新 --base-version 重试,不重写正文
- 不要在文档正文里存 API keys / 密码 / 凭据
- 只操作当前 Organization 内的文档(CLI 默认按当前组织上下文;
space_id 为遗留可选)
Safety
- 不在文档正文里存敏感凭据
- 尊重 Organization 边界——只操作当前用户有 viewer 权限以上的文档(CLI 会做权限校验)
历史变更
- 0.14.6 (2026-08-17):当前网页导入恢复通用路线——Browser 保真采集正文与资源,临时资源转存后用 TabDoc 既有创建 / 导入能力落库;只有显式飞书资产迁移才进入飞书专用通道。
- 0.14.5 (2026-08-17):飞书迁入路由补强——覆盖“当前浏览器已打开飞书文档、生成 TabDoc”这一隐式来源场景;禁止 browser/fetch 抽文本后走 Markdown 建文档,专用能力不可用时显式停下,不再有损降级。
- 0.14.4 (2026-08-07):按产品语义收敛正文契约——
title 是整篇文章标题,content 不再出现任何文章级首个 H1;移除标题相似度、标点和 emoji 判断,create/save-content 只按正文结构处理首个 H1。
- 0.14.3 (2026-08-07):快照回归修复——同名正文 H1 只多装饰性引号时仍去重;顶层
tabtin doc 示例改为 create --title --markdown 一步写入,save-content 示例显式携带 --title;正文以 H1 开头但无 title 上下文时拒绝写入,覆盖 Agent 未加载完整 Skill、只读取 relevant_cli 的路径。
- 0.14.2 (2026-08-07):正文标题契约——文档标题只放
title 元数据;Agent 新生成正文从导语开始、章节从 H2 开始,不再重复同名 H1。CLI create/save-content 在显式传 --title 时移除正文首个完全同名 H1,其他 H1 保留。
- 0.14.1 (2026-07-27):——移除
doc html-share get|set|off;HTML 块浏览改走文档级 doc share(DocumentShare)+ documentId/blockId。
- 0.14.0 (2026-07-26): (W4)——补
doc perm get|set(DocumentPermission 全量 replace;禁空 + 保留自身 admin)、doc shared-with-me、doc html-share get|set|off(已于 0.14.1 移除,改走文档级 share)。命令表由 doc_ai_help.go 重生。
- 0.13.0 (2026-07-26): (W2d)——补图片插入命令
doc insert-image(镜像 insert-html 编排:本地图片上传 OSS → 拼标准 Markdown  → 插块,两步走 Go CLI Execute 多请求编排,无新后端)。与 insert-html 的关键差异:图片是标准 CommonMark 语法(非自定义 directive),不固定 mime_type(服务端按扩展名 png/jpg/jpeg/gif/webp/svg 自动识别)。命令清单加 1 行 + 新增「图片插入(本地文件 → OSS → 正文)」段 + references/workflow-patterns.md Pattern 11 同步。上传成功但插块失败时错误 detail 保留 file_id + 可重跑 doc insert-block 命令。
- 0.12.0 (2026-07-26):——TabDoc HTML 权限收束:新上传强制
is_public=false、块持久化 src=""、读取走 GET /api/tabdoc/documents/<id>/html-artifacts/<fileId>(分享端走 share 端点)。Skill / Pattern 10 / AIHelp 同步去掉「OSS 公开对象 / 匿名 curl src」旧契约;历史非空 src 仅兼容回退。
- 0.11.0 (2026-07-08):——补 HTML 嵌入块命令
doc insert-html / 。:公开直链契约已由 0.12.0 / 废止。