Community图像github.com

darkhipo/sharethought

将分层数据打包到 PNG 文件中。人类看到的图像是渲染后的 SVG 表面;SVG 源代码和一篇 Markdown 文档隐藏在文件内部——位于能存活于图像管线的 iTXt 块中,以及一个可 grep 的 IEND 之后的数据尾部。适用于创建、解包、编辑或重新打包 sharethought 文件——这些自包含的工件用于人机协作交流,其中人类阅读图像,LLM 阅读深层信息。

sharethought 是什么?

sharethought 是一种将结构化元数据嵌入到标准 PNG 图像文件中的技能,专为人机协作场景设计。它通过 PNG 的 iTXt 辅助块和在 IEND 块之后的尾部数据,存储 SVG 源代码和 Markdown 文档,使得人类可以看到渲染的图像,而 AI 代理(如 Claude Code、Cursor、Codex)可以程序化地解包读取深层的 SVVG 源代码和文档内容。该技能支持 pack、unpack、repack 和 verify 四种操作,并提供了一个基于 Python 标准库的参考实现 sharethought.py。主要的应用场景包括:创建包含图形和解释文档的自包含工件,在 AI 协作对话中传递结构化信息,以及通过 repack 功能在不解压缩的情况下更新文档内容。建议同时使用两种载体(chunks 和 trailer)来确保文件在各种传输管线中的存活能力。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/darkhipo/sharethought/tree/HEAD

Installed? Explore more 图像 skills: steipete/songsee, affaan-m/frontend-design, mattpocock/improve-codebase-architecture · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

sharethought 是做什么的?

sharethought 文件是一种有效的 PNG 文件,其在图像之外还携带了文本部分。它在任何地方都能像普通图像一样打开和渲染。图像背后携带了:用来渲染它的 SVG 源代码,以及一篇 Markdown 文档。携带数据前端的清单索引了所有内容,并将解包和重新打包的说明包含在文件自身之内。

这些部分可以通过两种载体之一传输:追加在 IEND 块之后(简单、仅适用于字节精确传输),或存放在 PNG 辅助块内(能存活于图像管线)。参见载体。

使用参考实现

本仓库中的 sharethought.py 是经过测试的、仅使用标准库的实现。建议优先使用它,而不是手动解析——每次重新实现都会引入新的潜在错误。

python3 sharethought.py pack   --png base.png --svg s.svg --doc d.md -o out.png --what "一行描述"
python3 sharethought.py unpack out.png [--out-dir DIR]
python3 sharethought.py repack --text out.png --set doc=new.md -o out2.png
python3 sharethought.py verify out.png
python3 -m unittest -q test_sharethought    # 测试套件

pack 在一次构建中同时写入两种载体(它们决不会出现不一致)。unpack 优先读取块,回退到尾部数据,并在两种载体同时存在但内容不一致时报错——pack 不会产生这种情况,所以不一致意味着外部篡改。verify 检查签名、IEND、两种载体、段名称的语法,以及清单与段之间的一致性。

格式(精确)

[ PNG 字节 ... IEND 块 ]
SHARETHOUGHT-v1\n
[ JSON 清单 — 一个 UTF-8 对象,参见模式 ]\n
<<<SECTION:name>>>\n
[ 段内容 — 默认为原始 UTF-8,如果 "enc":"base64" 则为 base64 编码 ]\n
<<<END:name>>>\n
... (更多段,相同的框架)

标记 SHARETHOUGHT-v1 紧跟在 PNG 的 IEND 块之后,并以换行符结束。(它前面的字节是 IEND 块的 CRC 尾部,不是换行符——所以应该按字节查找,而不是按行。)从标记到文件末尾的所有内容就是尾部数据。标准段包括:svg(图像源代码,image/svg+xml)和 doc(Markdown,text/markdown)。允许任意其他段——任何文本类型均可。

段名称必须匹配 [A-Za-z0-9_]+。 不符合此语法的名称在打包时会被拒绝,解包时会通过命名错误进行标记——不会静默地消失。

载体

相同的清单和段可以通过两种方式携带。根据传输方式选择。

A. 尾部数据 — 追加在 IEND 块之后(上述默认方式)。

  • 手动编写非常简单:cat trailer.txt >> file.png。无需 CRC、无需块框架、无需库。
  • 仅能存活于字节精确传输:git、zip、curl -O、直接文件复制。
  • 不能存活于任何会重新序列化或重新编码图像的处理管线。PNG→JPEG 转码会将其完全丢弃。许多聊天和云存储管线会进行的块级重写会保留每个 IDAT 字节,但丢弃 IEND 之后的所有内容。

B. 辅助块 — iTXt 位于 PNG 块流内部。

  • 存在于块列表内部,因此能存活于块级重写和保持 PNG 格式的图像管线。
  • 需要 CRC 和块框架,大的段需要压缩。
  • 不能存活于 PNG→JPEG 转码——除了像素之外,没有任何内容能幸存于这种转码。

建议:同时写入两种载体(pack 会这样做)。相同的负载,一次构建,不可能出现不一致。读取器会优先查找块,然后回退到尾部数据,因此文件既能在聊天管线上工作,也能在字节精确传输上工作。代价大约是额外复制一次负载。

块的布局:

[ IHDR ] [ IDAT ... ] [ iTXt sharethought:manifest ] [ iTXt sharethought:<name> ] ... [ IEND ]

每个段对应一个 iTXt:清单使用关键字 sharethought:manifest,每个段主体使用 sharethought:<name>(原始主体,无分隔符)。标准的 iTXt 框架——关键字 \0、压缩标志(1 字节,1 = zlib)、压缩方法(0)、语言标签 \0、翻译后的关键字 \0,然后是文本。

清单模式

{
  "v": 1,
  "what": "描述此工件的一行文本",
  "how": {
    "unpack": "如果存在名为 sharethought:* 的 iTXt 块(清单是 sharethought:manifest),则读取它们;否则从 SHARETHOUGHT-v1 标记读取到文件末尾的字节。解析 JSON 清单,然后解析每个段在其 <<<SECTION:name>>> 和 <<<END:name>>> 分隔符之间的内容。解码 base64 段。",
    "repack_text_only": "保留原始 PNG 字节直到标记处,然后用更新后的清单和段重写尾部数据。无需重新渲染。或者运行:python3 sharethought.py repack --text FILE --set doc=new.md",
    "repack_visual": "要更改图像:编辑 svg 段,将其渲染为 PNG(rsvg-convert -w 1200 in.svg > out.png,或 cairosvg,或 resvg,或通过 Playwright 使用 Chromium 等无头浏览器),然后再次打包。"
  },
  "sections": [
    { "name": "svg",  "type": "image/svg+xml", "enc": "utf8",    "summary": "图像的一行描述" },
    { "name": "doc",  "type": "text/markdown", "enc": "utf8",    "summary": "文档的一行描述" }
  ]
}

how 字段是必需的,并且必须包含有效的说明——该文件可以教会任何初次阅读者如何打开它。

解包

最佳方式:python3 sharethought.py unpack file.png。手动方式(尾部数据载体):

raw = open("file.png", "rb").read()
# 遍历 PNG 块直到真正的 IEND — 切勿使用 raw.index() 查找标记,
# 压缩的 IDAT 字节可能偶然包含该标记。
off = 8
while True:
    import struct
    ln, typ = struct.unpack(">I", raw[off:off+4])[0], raw[off+4:off+8]
    if typ == b"IEND": break
    off += 12 + ln
trailer = raw[off + 12:].decode("utf-8")
import json
manifest, _ = json.JSONDecoder().raw_decode(trailer[trailer.index("\n")+1:])
import re
for m in re.finditer(r"<<<SECTION:(\w+)>>>\n(.*?)\n<<<END:\1>>>", trailer, re.DOTALL):
    name, body = m.group(1), m.group(2)
    # 如果 enc 是 base64:import base64; body = base64.b64decode(body)

手动方式(块载体,优先检查):

import struct, zlib, json
raw = open("file.png", "rb").read()
off, chunks = 8, {}
while off + 8 <= len(raw):
    ln = struct.unpack(">I", raw[off:off+4])[0]
    typ = raw[off+4:off+8]
    data = raw[off+8:off+8+ln]
    if typ == b"IEND": break
    if typ == b"iTXt":
        key, _, rest = data.partition(b"\x00")
        flag = rest[0]
        rest = rest[2:]
        _, _, rest = rest.partition(b"\x00")   # 语言标签
        _, _, rest = rest.partition(b"\x00")   # 翻译后的关键字
        if flag == 1:
            rest = zlib.decompress(rest)
        chunks[key.decode()] = rest.decode("utf-8")
    off += 12 + ln
manifest = json.loads(chunks["sharethought:manifest"])
sections = {k[len("sharethought:"):]: v
            for k, v in chunks.items()
            if k.startswith("sharethought:") and k != "sharethought:manifest"}

仅使用 shell:tail -c +$(( $(grep -abo 'SHARETHOUGHT-v1' file.png | head -1 | cut -d: -f1) + 1 )) file.png 将尾部数据作为文本打印出来(仅在尾部数据未被传输丢弃时有效)。

创建

最佳方式:渲染 SVG,然后运行 python3 sharethought.py pack --png out.png --svg diagram.svg --doc notes.md -o artifact.png。手动步骤:

  1. 编写 SVG(面向人类的图片或图表)。
  2. 渲染为 PNG:rsvg-convert -w 1200 diagram.svg > out.png(或 cairosvg diagram.svg -o out.png,或 resvg diagram.svg out.png,或使用无头浏览器——通过 Playwright 使用 Chromium——这通常是唯一已安装的 SVG 渲染器)。
  3. 编写 Markdown 文档。
  4. 追加尾部数据(类似于 cat trailer.txt >> file.png),并且最好也写入 iTXt 块——pack 可以从一次构建中完成这两项。

输出文件可以像普通 PNG 一样渲染。验证:在任何图像查看器中打开它(显示图像),然后运行 strings artifact.png | grep SHARETHOUGHT(显示尾部数据存在),或运行 python3 sharethought.py verify artifact.png(检查所有内容,包括两种载体是否一致)。

重新打包

最佳方式:python3 sharethought.py repack --text artifact.png --set doc=new.md -o out.png——重写两种载体,无需渲染器。

仅文本编辑(文档更改,图像不变):重用原始 PNG 字节(SHARETHOUGHT-v1 之前的所有内容),使用编辑后的段和更新后的清单摘要写入新的尾部数据。如果文件也携带了块,也要重写它们——参考实现从同一个源重新构建两者,这是保持它们一致的唯一安全方法。

视觉编辑(svg 更改):将修改后的 SVG 渲染为新的 PNG,然后用新的 svg 段 + 文档 + 清单再次打包。任何 SVG 渲染器都可以使用,包括无头浏览器。

往返规则:重新打包的文件仍然是 sharethought 文件。相同的格式、相同的标记、更新后的清单。

分隔符安全性

段分隔符是单独成行的 <<<SECTION:name>>> 和 <<<END:name>>>。如果内容可能包含这样的行,则在清单中为该段设置 "enc":"base64" 并将主体编码为 base64——当主体包含其自身的结束分隔符或标记字符串时,pack 会自动执行此操作。

实际上哪种冲突会造成破坏:主体包含其他段的分隔符是无害的。主体包含其自身的 <<<END:name>>> 行会被非贪婪正则表达式截断——该行之后的所有内容都会成为孤儿。参考实现会检测到这种情况(孤儿内容和未平衡分隔符错误),而不是静默地截断。如有疑问,请使用 base64。

保持负载存活

负载不是 PNG 像素数据的一部分,因此任何重新编码图像的工具都可能静默地丢弃它,留下一个看起来完全正常但没有深层信息的 PNG 文件。这包括 Pillow 中的 Image.open(f).save(g)、图像编辑器和 CDN 优化器。应从原始字节重新打包,而不是重新保存渲染后的文件。

IEND 之后的尾部数据仅能存活于字节精确传输。如果文件必须通过图像管线——聊天附件、缩略图生成器、转码器——请使用块载体(pack 会这样做),并且以文件形式上传,而不是以内联图像形式上传,这样 PNG 不会被转码为 JPEG。

与人类协作

人类看到的是 PNG——简化的视觉层。所有深层信息(svg 源代码、文档、清单)对于普通查看是不可见的,但对你来说完全可用。当人类询问图像时,解包并读取文档以获取上下文。当人类想要更改图片时,编辑 svg 并重新打包视觉部分。当人类想要添加深层信息时,编辑文档并仅文本重新打包。文件就是整个交流——一个工件,两种模态,无需旁路通道。

相关技能