Live Microsoft Office automation via COM(会话式驱动)+ HTML-first 可编辑 PPTX 管线。 面向 Python 开发者与 AI Agent,独立产出美观、符合审美、言之有物的 Office 产物(Word / PPT / Excel)。
- 库名 / 命令:
pip install offipy、import offipy、CLI 命令offipy - 当前版本:0.12.0(当前稳定版;API 经进一步验证后再进入 1.0.0)
- 会话式常驻 server:跨调用保持 Office 窗口存活、文档 / 工作簿 / 演示文稿状态不丢
- 三套件原子操作:Word / Excel / PowerPoint 增删改 + 保存 / 导出 PDF
- Excel 另含格式能力——合并单元格、边框、条件格式(cell 规则 / 数据条 / 色阶)、冻结窗格、打印设置、行高 / 数字格式 / 自动列宽
- Word 另含版式能力——样式系统(文字 / 段落格式)、页面结构(页眉页脚 / 页码 / 页面设置 / 目录)、列表与表格(合并 / 边框 / 列宽 / 行高 / 自动调整)、文档辅助(查找替换 / 图片 / 分页)
- 实时文档会话语义:读 op 默认作用在用户当前激活的文档上(ActiveDocument / ActiveWorkbook / ActivePresentation);破坏性 op 需显式
doc_id或follow_active=True或expected_target绑定,杜绝改错文档;关窗后自动重建 - 断连自愈:用户关窗或 Office 退出后自动重建会话
- HTML-first 管线 + 设计系统:Claude 写 HTML 幻灯片 → 原生可编辑
.pptx→ 实况展示 + 视觉迭代;内置设计 token、3 套主题、11 种布局、审美审计、自动选型、反馈学习(见下方「设计系统」) - MCP server:把全部三套件操作暴露为 MCP 工具,Claude Desktop 等可直接驱动真实 Office
- 环境诊断:
offipy check一键检查 Python / 依赖 / Office 三件套 / 浏览器 / server 是否就绪(--json机器可读,失败退出码非 0) - server 进程管理:
offipy server status|stop|restart用/status真实握手 + PID 文件 / netstat 探测管理常驻进程 - Agent 只读回读:
word read_doc_text/ppt read_slide_summary(逐页标题/正文/备注)ppt read_slide_texts --slide_idx N(单页逐 shape 文本)/excel read_range把文档文本层读回(供 Agent 迭代),经 CLI / RPC / MCP 三路暴露 - PPTX 静态质量门禁:
offipy audit(见docs/audit.md)不开 PowerPoint、不依赖 Microsoft Office, 纯解析.pptx检查越界 / 贴边 / 重叠 / 文本溢出 / autofit 风险,输出 text / json / markdown / html 报告, 按严重度门槛阻断不合格产物(--fail-on);compare_pptx基线回归(见docs/audit-baseline.md)只阻断候选新增/恶化的问题;deck render_with_report让 HTML→PPTX 生成即门禁 - 艺术分析(offipy.art):纯标准库、确定性、只建议的视觉/排版质量分析(见
docs/art.md)——build_scene把幻灯片 抽象成 ArtScene,5 个维度规则(层级 / 构图 / 排版 / 颜色 / 媒体)评估,grade / confidence / evidence_coverage 三分离、证据不足降级不误报;analyze_deck双源(measurements + pptx 几何) 一次评估,deck.render_with_quality_report生成即质量参考 - 高层 API:
offipy.Excel() / Word() / Ppt()上下文管理器,库内直接驱动(见下方「Python API」)
- Windows + 已安装 Microsoft Office(Word / Excel / PowerPoint)
- Python ≥ 3.10(本仓库开发环境为 3.12)
import offipy在非 Windows 上可运行;调用 Office API 时抛UnsupportedPlatformError- 支持的平台 / Office / Python 组合矩阵见
docs/compatibility.md(Tested / Expected / Unsupported 三栏)
py -m pip install "offipy[all]" # 全部能力(office COM + deck 管线 + MCP)
py -m playwright install chromium # 转换器依赖 chromium 做 DOM 测量
offipy check --profile all # 一键检查环境就绪(Python/依赖/Office/浏览器/server)核心 import offipy 零额外依赖;按用途增量安装 extra:
offipy[office]:Windows COM 自动化(Word/Excel/PowerPoint)offipy[deck]:HTML→可编辑 PPTX deck 管线(python-pptx / lxml / fonttools / playwright / Pillow)offipy[mcp]:MCP server(offipy mcp,Claude Desktop 等接入)offipy[all]:以上全部
转换器本体已 vendored 进 wheel,装完即可用;deck 管线需额外跑 playwright install chromium。
offipy 是会话式驱动,不是一次性脚本:
- 首次调用自动在后台拉起常驻 server(
127.0.0.1:8890);之后所有操作打到同一进程。 - doc_id 是目标权威标识:
new_book/new_doc/new_pres与open_*返回doc_id, 会话内稳定、跨调用有效、不随改名变化。get_target查询当前激活目标身份:offipy excel get_target→{"app": "excel", "doc_id": "book1", "name": "Book1", "path": "..."}(无则null)。 - 读 op(
get_cell/read_range/read_doc_text/read_slide_summary/read_slide_texts/get_target…) 默认作用在用户当前激活的文档上(ActiveDocument / ActiveWorkbook / ActivePresentation), 实时解析,绝不用陈旧的内部缓存。没有文档时抛TargetNotFoundError,提示先new_*/open_*。 - 破坏性 op(写入 / 格式 / 保存 / 关闭等)默认拒绝执行,必须三选一:
- 显式
doc_id=<会话返回的标识>(CLI--doc-id); - 或
follow_active=True——显式声明「跟随当前活动文档」(CLI--follow-active); - 或
expected_target绑定(见下)。 三者都没有时抛InvalidArgumentError提示补目标——绝不静默改到当前激活的文档。
- 显式
expected_target绑定(CLI--expected-target '<json>'/ MCP 工具参数 / Remote 客户端, 三入口可用):{"doc_id": ...}/{"name": ...}/{"path": ...}可组合,做目标绑定: resolve-once——server 先按绑定键解析出目标 doc_id,再用解析结果执行操作;绑定失败抛TargetNotFoundError——杜绝「校验 A 执行 B」,防止切到别的文档后被误改(绕开按焦点路由)。activate(doc_id)把指定文档设为激活目标并同步真实 UI(ExcelWorkbook.Activate()、 WordDocument.Activate()、PPT 激活含该文档的窗口),同步失败回滚并抛ComOperationError;list_docs如实返回已登记句柄的{doc_id: {"name", "path", "active"}}(不隐式枚举未登记的)。quit excel等命令关掉应用;__exit__(Python API)不关 Office 窗口,窗口与文档跨调用保持存活。- 用户手动关窗后,下次调用自动重建会话(断连自愈)。
- server 只监听
127.0.0.1,不对外网开放。 - Bearer token 鉴权:启动时生成随机 token(环境变量
OFFIPY_SERVER_TOKEN优先,否则持久化到%LOCALAPPDATA%\offipy\token)。所有请求需Authorization: Bearer <token>,否则 401。 - op 白名单:只暴露各 app 类的公开方法;请求体上限 16MB;
Content-Type必须为 JSON;POST只接受/call与鉴权/shutdown,其余路径 404;响应体上限 64MB。 - 进程所有权(不杀策略):
server status只读——未运行只报「未在运行」,不隐式拉起;server stop优先走鉴权/shutdown优雅停机;token 失配(auth_fail)或端口被非 offipy 进程 占用且无法证明归属(mismatch)时一律不杀,只提示手动处理。 - 别把 token 泄漏给不信任的进程——拿到 token 即等于拿到你当前 Office 会话的读写权限。
- 详见
SECURITY.md。
# 首次调用自动在后台拉起常驻 server;之后所有操作打到同一进程
# 破坏性 op 需要一个目标:--doc-id <会话返回的标识> / --follow-active(跟随活动文档)/
# --expected-target '<json>'(doc_id/name/path 绑定)。下例用 --follow-active 跟随新建/打开的文档。
offipy excel new_book
offipy excel set_cell --sheet 1 --cell A1 --value 100 --follow-active
offipy excel format_cell --sheet 1 --cell A1 --bold true --size 14 --bg "#38BDF8" --follow-active
offipy excel merge_cells --sheet 1 --range_addr A1:B2 --follow-active
offipy excel set_border --sheet 1 --range_addr A1:D5 --side all --style continuous --weight thin --color "#D0D7DE" --follow-active
offipy excel add_conditional_format --sheet 1 --range_addr C2:C5 --rule cell --operator greater --value 0 --bg "#C6EFCE" --follow-active
offipy excel freeze_panes --sheet 1 --rows 1 --cols 0 --follow-active
offipy excel page_setup --sheet 1 --orientation landscape --fit_to_pages_wide 1 --follow-active
offipy excel set_number_format --sheet 1 --range_addr B2:B5 --fmt "#,##0" --follow-active
offipy excel autofit --sheet 1 --range_addr A1:D5 --rows false --follow-active
offipy word new_doc
offipy word write_line --text "你好,世界" --follow-active
offipy word format_text --paragraph 1 --bold true --size 18 --color "#2251FF" --follow-active
offipy word format_paragraph --paragraph 1 --alignment center --line_spacing double --follow-active
offipy word set_header_text --text "季度报告" --follow-active
offipy word add_page_number --alignment center --follow-active
offipy word page_setup --orientation landscape --paper a4 --top_margin 60 --follow-active
offipy word insert_toc --levels 3 --follow-active
offipy word add_list --lines "第一项" --lines "第二项" --style bullet --follow-active
offipy word merge_table_cells --table_idx 1 --start_row 1 --start_col 1 --end_row 1 --end_col 3 --follow-active
offipy word set_table_border --table_idx 1 --style single --color "#9AA5B1" --sides all --follow-active
offipy word set_table_col_width --table_idx 1 --col 1 --width 140 --follow-active
offipy word find_replace --find 季度 --replace 半年度 --replace_all true --follow-active
offipy word insert_image --path out/cover.png --width 360 --follow-active
offipy word insert_page_break --follow-active
offipy ppt new_pres
offipy ppt add_slide --layout 2 --follow-active
offipy ppt set_title --slide_idx 1 --text "标题" --follow-active
offipy check # 环境就绪诊断:Python/依赖/Office/浏览器/server(--json 机器可读)
offipy server status # 常驻 server 状态(/status 握手,只读不拉起);stop / restart 同理
offipy excel get_target # 查询当前激活目标身份 {app,doc_id,name,path}(无则 null)
offipy word read_doc_text # Agent 只读:全文档文本
offipy ppt read_slide_summary # Agent 只读:逐页 title/body/notes 摘要
offipy ppt read_slide_texts --slide_idx 1 # Agent 只读:单页逐 shape 文本(v0.10)
offipy excel read_range --sheet 1 --range_addr A1:B2 # Agent 只读:区域二维值
offipy quit excel
offipy audit deck.pptx # PPTX 静态质量审计(文本报告)
offipy audit deck.pptx --fail-on HIGH # 达 HIGH 就退出码 1(CI 门禁)
offipy audit deck.pptx --format html --out audit.html --slides-dir export/ # SVG 画布报告
offipy audit candidate.pptx --baseline baseline.pptx --fail-on-new MID # 基线回归:只阻断新增/恶化
offipy deck make --html deck.html --out deck.pptx --no-open \
--audit-mode strict --fail-on HIGH --audit-report deck.audit.json # HTML→PPTX 生成即门禁复杂参数用 --payload '<json>' 透传(覆盖同名 kwargs);重复 --key 会聚合成 list。
读 op(get_cell / read_range / read_doc_text / read_slide_summary / read_slide_texts / get_target)不需要目标参数。
from offipy import Excel, Word, Ppt
with Excel() as x: # 本地直连 COM(= offipy.direct.*),独立 doc_id/线程
doc_id = x.new_book()
x.set_cell(1, "A1", 100, doc_id=doc_id) # 破坏性 op 需显式 doc_id
x.save("out/report.xlsx", doc_id=doc_id)
with Ppt() as p:
pres_id = p.new_pres()
p.add_slide(2, doc_id=pres_id)
p.set_title(1, "标题", doc_id=pres_id)两种会话模型(P0-4):
Excel() / Word() / Ppt()(等价offipy.direct.*)——本地直连 COM, doc_id/线程/会话状态与 CLI/MCP 完全隔离。direct facade 绑定创建它的线程 (STA COM),非线程安全:同一实例须在创建线程内使用;跨线程各自建 facade 时用offipy.direct.com_apartment()包一层(线程各自 CoInitialize)。RemoteExcel() / RemoteWord() / RemotePpt()——经常驻 server 的远程会话, 与 CLI/MCP 共享同一会话(同 doc_id),适合 Agent 需要「CLI/Python/工具同 一个 Office 会话」的场景:
from offipy import RemoteExcel
with RemoteExcel() as x: # 默认连本地 8890(自动拉起 server)
x.new_book() # 与 `offipy excel list_docs` 看到同一个 doc_id
x.set_cell(1, "A1", 42, follow_active=True)未显式定义的 op 经 __getattr__ 代理到底层 app;offipy 异常(OffipyError 家族)原样透传。
返回契约:三条入口(Python API / HTTP RPC / MCP)同源但返回形状不同,如实对照见
docs/api.md:
- Python API 返回 App 方法原始值(
new_book→doc_id字符串、get_cell→单元值、get_target→dict …);void op 返回None;失败抛OffipyError领域异常。 - HTTP RPC
/call返回OperationResult(HTTP-only 契约):{ok, operation, resource_id, message, data}(附result兼容别名)。operation是"excel.set_cell"式全名;resource_id如excel:book:book1标识本次操作作用的文档 (doc_id是会话内稳定标识,不用用户可改的 name);data是操作结果(读 op 原值, void op 为null)。原始 COM 对象不外泄。 - MCP 工具 返回操作
data载荷(读 op 原值;void op 为"ok (<op>)")。
异常契约(error_code):失败统一为 OffipyError 子类,每个带 code:
InvalidArgumentError(invalid_argument)/ TargetNotFoundError(target_not_found)/
FileConflictError(file_conflict)/ ComOperationError(com_operation,保留 hresult)/
ProtocolError(protocol)。RPC 失败响应的 error_code 与异常一一对应,client 按表映射回
对应领域异常,三入口同源。
幂等重试(P0-2 方案 A):client.call/request 默认自动生成 request_id。若调用超时
(RemoteCallError)需要重试,务必复用同一 request_id——server 对同 request_id 同
payload 合并/回放缓存(响应带 cached: true),绝不重复执行;同 request_id 换了 payload
则返回 InvalidArgumentError(400)。
import uuid
from offipy import client
rid = str(uuid.uuid4()) # 调用方持有;超时后用同一 rid 重试,绝不双写
try:
client.call("excel", "set_cell", sheet=1, cell="A1", value=42, doc_id="book1", request_id=rid)
except client.RemoteCallError:
client.call("excel", "set_cell", sheet=1, cell="A1", value=42, doc_id="book1", request_id=rid)Claude 写 deck HTML 时,只需引用 design token(CSS 变量)并给每页打 data-layout,渲染时注入主题与布局,一份 HTML 换主题即换皮。示例见 examples/decks/design-system/deck.html。
<head>
<style data-theme="mckinsey"></style> <!-- 主题占位:render(theme=) 替换 -->
<style data-layouts></style> <!-- 布局占位:render(apply_layouts=) 替换 -->
</head>
<section class="slide hero" data-pptx-slide data-layout="hero-title">…</section>-
内置主题:
mckinsey(咨询蓝)/academic(学术极简)/dark-tech(深色科技)—— 一套 token 覆盖,design.theme_css()/design.inject_theme() -
命名布局库:
hero-title/split-2col/cards-3/big-number/quote-frame/timeline/comparison/chart-dominant/icons-row/portrait-feature/closer——layouts.inject_layouts()按引用注入 -
审美审计:转换产出的 measurement → 留白比例 / 字号层级数 / 每页色数 / 对比度 / 跨页一致性打分,
aesthetic.audit()输出带分报告供迭代 -
自动选型:
autopick.pick()从内容结构推荐主题 + 每页布局 + 理由(纯规则,可覆盖) -
内容工作流:把「言之有物」的骨架标准化——写一份 markdown 大纲 (
# 标题+ 每个## 段= 一页,-要点、正文、@layout:指令),offipy deck outline --input outline.md --out deck.html一键得到 HTML 骨架, 布局自动推断,再offipy deck make --theme <主题> --layouts注入主题与布局成稿。 示例见examples/outline/quarterly-review.md。offipy deck outline --input examples/outline/quarterly-review.md --out out/quarterly.html offipy deck make --html out/quarterly.html --theme mckinsey --layouts --out out/quarterly.pptx
-
原生图表:图表区打
data-chart="<类型>"(bar/line/pie),数据放容器data-chart-dataJSON 属性或页内<script type="application/json" data-chart-target="<选择器>">块,deck make --layouts后自动替换成 PowerPoint 原生可编辑图表(双击即可改数据),不再是贴图。 前提:图表所在页须引用 chart-dominant 布局(data-layout="chart-dominant"),容器才有可测的 surface 矩形。示例见examples/decks/charts/chart-demo.html。<div class="chart" data-chart="bar" data-chart-data='{"categories":["Q1","Q2"],"series":[{"name":"营收","values":[40,70]}]}'></div>
大纲里用
@chart: <类型>+@chart-data: <JSON>声明图表页,骨架自动落 chart-dominant 布局。 -
原生图标:图标容器打空
<svg data-icon="<集>:<名字>" viewBox=... width=.. height=..>,deck make --layouts后替换成 PowerPoint 原生 freeform 矢量图标(双击显示可编辑 锚点,非图片)。内置 Phosphor(ph:,256 viewBox,填充)+ Lucide(lu:,24 viewBox, 线形)双集(vendored 于src/offipy/assets/icons/,更新见scripts/fetch_icons.py); 图标颜色继承容器的color(HTML 里color: var(--accent)即上主题色;容器未设色时 缺省取当前主题的--accent)。Lucide 线形图标按 round 线帽/拐角渲染(对齐源 SVG 设计,非图片非贴图)。示例见examples/decks/icons/icons-demo.html。<svg class="icon" data-icon="ph:check-circle" viewBox="0 0 256 256" width="72" height="72"></svg>
大纲里用
@icons: <名字>[:标签]; ...声明图标行(默认ph:前缀),骨架自动落icons-row布局(超过 3 个图标自动换行,3 列/行)。示例见examples/outline/icons-demo.md。 -
反馈学习:审计后处置(fixed / accepted / ignored)记入
~/.offipy/feedback.jsonl,feedback.dimension_weights()折算审计权重,越修越严(P2 验证版)
from offipy import deck, aesthetic
pptx = deck.render("deck.html", theme="mckinsey", apply_layouts=True) # 注入主题+布局
report = aesthetic.audit("deck.html") # 读 measurement 打分
print(report.markdown())offipy.art 是纯标准库、确定性、只建议的视觉/排版质量分析:不调 AI、不开 Office、
不加载 python-pptx(import offipy 即有)。它把每页幻灯片抽象成「场景(ArtScene)」,
用 5 个维度规则(层级 / 构图 / 排版 / 颜色 / 媒体)评估,grade / confidence /
evidence_coverage 三分离——证据不足的维度降级 insufficient_evidence,绝不靠猜误报;
取舍留给调用方,不提供总分门禁。完整规则、证据源与边界见
docs/art.md。
from offipy import build_scene, analyze_scene, analyze_deck, render_markdown
# 双源:measurements(浏览器真实像素)为主、pptx 几何审计为副
scene = build_scene(
measurements="out/report_audit/_cache/measurements.json",
pptx="out/report.pptx",
)
report = analyze_scene(scene, profile="balanced")
print(render_markdown(report))
# 组合入口:一次调用同时几何审计 + 艺术分析
deck_report = analyze_deck(
pptx="out/report.pptx",
measurements="out/report_audit/_cache/measurements.json",
profile="consulting",
)
print(deck_report.art.slides[0].by_dimension("color").status) # assessed / insufficient_evidence- 双源合并:measurements 提供颜色 / 字号 / 文本证据,pptx 提供几何证据,按「文本强佐证 + 几何兜底」一对一匹配;未匹配元素保留 + warning,绝不静默丢弃
- 内置 profile:
balanced/consulting/academic/technology/event(offipy.profile_names()可查、get_profile(name)可读可扩展) - 生成即质量参考:
deck.render_with_quality_report(html, audit_mode=..., fail_on=..., profile=...)HTML→PPTX 生成后同时产出几何审计与艺术分析,返回QualityRenderResult(含art_report/deck_quality)——audit_mode="strict"达fail_on门槛才退出非 0 - 证据诚实:只传
pptx=时,依赖字号 / 颜色证据的维度自动降级(insufficient_evidence+art.evidence.limitedwarning),纯几何规则照常运行
offipy mcp 启动 MCP stdio server,把 Word / Excel / PowerPoint 全部操作暴露为 MCP 工具
(ppt_set_title、word_write_line、excel_set_cell 等)。工具调用与 offipy 命令等价,
窗口实时可见;读 op 作用在用户当前激活的文档上,破坏性 op 的调用参数含
expected_target / follow_active(见上「会话语义」)。
在 Claude Desktop 的 claude_desktop_config.json 添加。offipy 命令需在 PATH(pip 安装后自动加入);若用了专用 venv,command 换成该 venv 的 offipy.exe 绝对路径(如 <venv>\\Scripts\\offipy.exe):
{
"mcpServers": {
"offipy": {
"command": "offipy",
"args": ["mcp"]
}
}
}手动验证(无需 Office,仅握手):
offipy mcp # 阻塞运行,等待 stdio 客户端接入从源码开发安装(不装 PyPI 版本):
uv venv --python 3.12 .venv
uv pip install -e ".[all]" # 源码开发安装(全部能力)
uv run playwright install chromium # deck 管线需要uv sync --extra dev # 装 dev 依赖(ruff / mypy / pytest)
uv run ruff check . # lint
uv run ruff format --check . # 格式
uv run mypy src/offipy # 类型
uv run pytest tests -q # 测试(COM 集成测试无 Office 自动跳过)贡献规范与门禁见 CONTRIBUTING.md。
src/offipy/
core.py # COM 生命周期/会话管理 + active_doc/doc_alive 会话语义
exceptions.py # offipy 异常体系(OffipyError + 10 子类,策略 A 领域异常)
schema.py # operation schema 单一来源(OpSpec 表,server/CLI/MCP 三入口派生)
result.py # OperationResult 返回契约(HTTP-only:ok/operation/resource_id/message/data)
paths.py # 用户数据目录 / converter 数据目录 / ensure_writable 覆盖保护
server.py # 常驻会话 HTTP server(token 鉴权 + worker 队列 + /status + /shutdown)
cli.py # `offipy` 命令入口(复杂参数:重复 flag → list / --payload JSON)
api.py # 高层 API facade:Excel() / Word() / Ppt() 上下文管理器
audit/ # PPTX 静态质量审计与基线回归(models/extract/geometry/roles/rules/compare/render/pptx,纯解析无 COM)
art/ # 艺术分析(v0.12):场景建模/规则/双源合并/渲染/基线对比(纯 stdlib,只建议不阻断)
mcp_server.py # MCP stdio server(三套件操作 → MCP 工具)
excel.py / word.py / ppt.py # 三套件原子操作
client.py # server 的 HTTP 客户端(HTML 管线复用)*
envcheck.py # `offipy check` 环境就绪诊断(分组清单 + --json)*
deck.py # HTML → 可编辑 PPTX 管线(render/open_live/export_slides)*
design.py # 设计系统:token 模型 + 3 套内置主题 + .slide 基础样式 *
layouts.py # 命名布局库:11 种布局组件 + data-layout 注入 *
charts.py # 原生图表:图表声明解析 + 原生可编辑图表注入(bar/line/pie)*
icons.py # 原生图标:SVG 路径压平 + freeform 矢量图标注入(ph/lu 双集)*
assets/icons/ # vendored 图标资产(Phosphor fill + Lucide)+ manifest + LICENSE *
aesthetic.py # 审美审计:留白/字号层级/色数/对比度/一致性 → 打分报告 *
autopick.py # 自动选型:内容结构 → 推荐主题 + 每页布局 + 理由 *
feedback.py # 反馈学习:审计处置 → 维度权重(P2 验证版) *
outline.py # 内容工作流:markdown 大纲 → 逐页结构化内容 → HTML 骨架 *
_vendor/html_to_editable_pptx/ # vendored HTML→PPTX 转换器(外协代码,MIT)*
tests/ # pytest
docs/ # 协议(protocol.md)/ 返回契约(api.md)/ 弃用(deprecation.md)/ 兼容矩阵(compatibility.md)/ 发布手册(release.md)
examples/ # 可运行示例(decks / outline / excel / word)
* 由「HTML-first 可编辑 PPTX 管线」与「M1/M2/M3 内容工作流」计划新增。
MIT AND ISC(SPDX 表达式)——offipy 本体与 vendored 转换器 / Phosphor 图标为 MIT,
内嵌 Lucide 图标为 ISC。许可证原文随产物分发:根目录 LICENSE +
THIRD_PARTY_NOTICES.md,图标集各自的 LICENSE-*.txt 见
src/offipy/assets/icons/。
- Bug / 功能请求:到 GitHub Issues 提交,
附上
offipy check --json输出与最小复现。 - 预发布版本:TestPyPI 冒烟用
scripts/pypi_smoke.py --index https://test.pypi.org --version <预发布号>(从 TestPyPI JSON API 精确下载 wheel 并做双重 sha256 比对,见 CHANGELOG「预发布编号策略」)。 预发布版本仅供验证,稳定首发前__version__/ tag / CHANGELOG 顶层三者保持一致。