mybooks-tools-builder
v0.1.7
Published
Scaffold / build / validate CLI for MyBooks Toolbox external tools (mytool). MyBooks is a personal e-books management system base on calibre.
Readme
Tools Builder (mytool)
MyBooks Toolbox 外部工具的脚手架 / 构建 / 校验 CLI。
设计背景:MyBooks(MyBooks 仓库)的 Toolbox 支持以"外部工具"的形式动态安装工具, 不需要改动核心仓库代码就能新增功能。
工具作者也可以完全不用这个脚手架——只要产物符合"一个 manifest.json + backend/ + frontend/"的约定即可(见下方"包结构")。
📖 完整 Core API / toolbox-bridge.js 参考文档:
poxenstudio.github.io/tools_builder
(本仓库 docs/index.html,随 GitHub Pages 发布,内容如何与 mybooks/mybooks 保持同步见
.claude/skills/sync-tool-api-docs/SKILL.md)。
安装
npm install -g mybooks-tools-builder
# 或不装到全局,直接 npx 用:
npx mybooks-tools-builder --help快速开始
mytool init tool_name \
--name "我的工具" \
--author "你的名字" \
--description "工具做什么" \
--repo-url "https://github.com/you/tool_name" \
--locales en,zh
cd tool_name
# 改 backend/tool.py 和 frontend/index.html,写你的业务逻辑
mytool validate . # 打包前先校验一遍
mytool build # 产出 dist/tool_name-0.1.0.zip,打印 sha256
mytool build -f 7z # 工具比较大时,产出 dist/tool_name-0.1.0.7z(体积更小)打包出来的 zip:
- 在一个开启了"开发者模式"(管理员在系统设置里打开
ENABLE_TOOLBOX_DEV_MODE)的 MyBooks 实例的/admin/toolbox页面里上传安装(POST /api/toolbox/install/upload); - 或者发送邮件提交给MyBooks审核发布(注明代码仓库地址,以及
build打印的 sha256)。
安装后需要重启MyBooks才会真正生效。之后管理员可以随时对它做禁用/启用(立即生效,不需要重启)或卸载。
命令
| 命令 | 作用 |
|---|---|
| mytool init <tool_id> | 生成一个新的工具项目骨架 |
| mytool validate <path> | 只做校验,不打包;path 可以是项目目录,也可以是已有的 zip |
| mytool build [dir] | 校验并打包成 dist/<tool_id>-<revision>.zip,打印 sha256;加 -f 7z 打包成 .7z(体积更大的工具可以用它减小包体积,需要本机装有 7z/7za) |
| mytool bump <major\|minor\|patch> [dir] | 按 semver 规则升级 manifest.json 里的 revision |
init 支持的选项:--name / --author / --description / --repo-url / --locales /
--outdir;不带这些选项时会在终端里交互式询问(非 TTY 环境下静默用空值/默认值,不会挂起)。
--locales(默认 en,zh)决定生成哪些 frontend/locales/<code>.json,见下方"多语言(i18n)"
一节。
包结构
tool_name/
├── manifest.json # 工具元数据,见下方字段说明
├── .toolbuilder.json # 构建配置:前端如果用自己的打包工具,在这里声明命令和产物目录
├── icon.png # 可选,工具图标
├── backend/
│ ├── __init__.py
│ └── tool.py # manifest.entry_backend 指向的模块,继承 BaseTool
└── frontend/
├── index.html # manifest.entry_frontend 指向的入口页面
└── ... # 自包含的静态资源,作者自选任意前端技术栈manifest.json 字段
| 字段 | 必填 | 说明 |
|---|---|---|
| tool_id | 是 | 唯一标识,只能是小写字母/数字/下划线 |
| name / description | 是 | 展示名称与描述 |
| revision | 是 | 语义化版本号 x.y.z |
| author | 是 | 作者 |
| repo_url | 是 | 源码仓库地址,供商店审核时核对产物与公开源码是否一致 |
| core_api_version | 是 | 依赖的最低 Core API 版本 |
| entry_backend | 是 | <module>.<ClassName>,指向 backend/ 下继承 BaseTool 的类 |
| entry_frontend | 否 | 前端入口文件(相对 frontend/),缺省则工具没有 UI |
| page | 否 | 落地页路径,缺省用 tool_id |
| api_routes | 否 | 自定义后端接口,[{"path": "...", "handler": "<module>.<ClassName>"}] |
| locales | 否 | 工具支持的语言代码列表,例如 ["en", "zh"];纯展示性质(商店/管理端用),不参与后端加载校验 |
| default_locale | 否 | 缺省语言,必须在 locales 里 |
.toolbuilder.json
{
"frontendBuildCommand": null,
"frontendOutputDir": null
}frontend/ 已经是纯静态文件(init 生成的默认模板就是)时两项都留空,build 直接打包
frontend/ 目录。如果你的前端用了自己的构建工具(Vite / Webpack 等),把构建命令和产物目录
填进去,build 会先跑这个命令,再从 frontendOutputDir 取产物打包,脚手架本身不强制
任何打包器。
backend/tool.py 能用到什么
backend/tool.py 里的类继承 BaseTool,运行时由 MyBooks 后端动态加载,可以通过
self.api 访问 Core API:
self.api.calibre—— 书库读写:search_books/get_metadata/set_metadata/import_file/merge_formats/add_format/delete_book...self.api.db—— 应用数据库:get_item_by_book_id/create_item/delete_item_by_book_id/get_readerself.api.tasks—— 后台任务:create_task/update_progress/complete_task/make_progress_callbackself.api.messages—— 站内消息:send_message/cleanup_messagesself.api.storage—— 工具专属数据目录 + 持久配置:get_work_dir/cleanup_work_dir/get_config/set_configself.api.settings—— 系统配置只读白名单:get(key, default),只有显式登记过的 key 才能读到真实值,其余一律返回default;完整名单见 mybooks/mybooks 仓库webserver/toolbox/core_api.py的SettingsAPI.ALLOWED_KEYSself.api.utils—— 通用文本/日期处理:strip/get_title_sort/guess_title_author_from_filename/parse_date,转发 mybooks/mybooks 仓库webserver/utils.py里的同名纯函数,行为完全一致
这份代码依赖真实的 MyBooks 运行环境(Calibre、SQLAlchemy session 等),无法脱离 MyBooks
单独跑,mytool 目前也不模拟这部分——实际验证逻辑要走开发者模式装进一个真实的
MyBooks 实例。
frontend/index.html 能用到什么
工具前端是一份自包含的静态站点,由宿主 MyBooks 用 <iframe> 加载,不要求用 Vue、不要求
和宿主的前端框架版本一致。运行时通过 <script src="/static/toolbox-bridge.js"> 获得
window.MyBooksToolBridge:
bridge.theme/bridge.locale—— 当前宿主的主题("light"/"dark")与语言,读取的 始终是最新值(宿主切换主题/语言不会重新加载 iframe,见下方"主题/语言运行时更新")bridge.onThemeChange(fn)/bridge.onLocaleChange(fn)—— 订阅主题/语言变化的推送,fn(newValue, oldValue),返回取消订阅函数;不订阅也没关系,bridge.theme/bridge.locale本身随时读到的都是最新值,只是不会主动收到"变了"的通知bridge.fetch(path, options)—— 请求/api/toolbox/tool/{tool_id}/{path},Cookie 自动带上,不需要额外处理鉴权bridge.notify(message, level)—— 请求宿主用它自己的提示组件展示一条消息
主题/语言运行时更新
宿主切换深浅色主题或界面语言时,不会重新加载工具的 iframe——只会通过 postMessage
把新值推给 toolbox-bridge.js,由它更新 bridge.theme/bridge.locale 并触发
onThemeChange/onLocaleChange 回调。工具首次加载时仍从 URL query 里同步拿到初始值
(不用等这套推送),此后的变化都走这条路径。
多语言(i18n)
init 默认生成一套开箱即用、跟框架无关的 i18n 胶水(frontend/lib/i18n.js +
frontend/locales/{manifest,en,zh}.json),边界是:翻译字串永远归工具自己所有,
toolbox-bridge.js 只提供"当前该用哪个语言 + 语言变了通知你",不托管任何字串。
<script src="/static/toolbox-bridge.js"></script>
<script src="lib/i18n.js"></script>
<script>
var i18n = window.MyBooksToolI18n.create();
i18n.ready.then(function () {
document.getElementById('status').textContent = i18n.t('status.ready');
});
</script>
<p id="status" data-i18n="status.loading">loading...</p>frontend/locales/manifest.json声明default与locales(由--locales生成);i18n.t(key, params)取当前语言字串(缺失回退到 default,再缺失原样返回 key,支持{param}插值);i18n.applyDom(root)把所有data-i18n="key"/data-i18n-attr元素 替换成译文;语言变化时自动重新加载对应字串表、重新applyDom(),并触发i18n.onChange(fn)(用了前端框架、需要重新渲染而不是直接改 DOM 的工具用这个)。- 完全是可选脚手架,不是强制约定——想用 vue-i18n / react-intl 之类,删掉这个文件,直接按
自己的技术栈接
bridge.locale/bridge.onLocaleChange即可。
共享视觉基础(light/dark)
init 还会生成 frontend/lib/theme.css——一份不依赖 Vuetify 的轻量 CSS:颜色变量
(--mb-color-bg / --mb-color-primary / --mb-color-error 等)+ 一批基础组件 class
(.mb-btn / .mb-input / .mb-card / .mb-pre / .mb-status--* / .mb-alert /
.mb-chip / .mb-list-item / .mb-progress-linear / .mb-spinner / .mb-divider /
.mb-dialog / .mb-field / .mb-file-input / .mb-row/.mb-col 等),覆盖到现有内置
工具(Vuetify 组件)里常见的元素类型,目的是让不同技术栈的工具之间、以及跟内置工具之间
观感更接近一些,不是强制约定、也不是像素级复刻。
<link rel="stylesheet" href="lib/theme.css" />
...
<button class="mb-btn">Run</button>深浅色切换复用 bridge.theme/bridge.onThemeChange 这条通道:页面给 <body> 设置
data-theme="light"|"dark"(模板已演示),theme.css 里对应的 CSS 变量在
body[data-theme="dark"] 下被覆盖,子元素通过变量继承自动换肤,不需要工具自己写两套样式。
不想用就整个删掉这个文件,不影响其它任何能力。
明确不做的事(当前版本)
- 没有
mytool dev:本地开发服务器 + mock 宿主环境(模拟toolbox-bridge.js和一个假的 MyBooks 后端)暂时没做。backend/tool.py的实际运行验证目前只能靠开发者模式 装进一个真实的 MyBooks 实例;frontend/index.html可以用任意静态文件服务器单独打开看 页面结构,但bridge.fetch(...)这部分同样需要真实后端。 - 没有
mytool publish:一键发布到 mybooks.top 商店的命令——商店侧的提交/审核 API 还没有定义,等这部分确定后再补。
与 MyBooks 核心仓库的版本对齐
package.json 的版本号与内置的 CORE_API_VERSION(src/core-api-version.js)对齐
mybooks/mybooks 仓库 webserver/toolbox/core_api.py 里的同名常量。核心仓库升级
CoreAPI/toolbox-bridge.js 接口时,这个仓库需要同步发新版本。
