评估范围:
apps/cli/src/route/**下的所有 Hono route handler。 评估目标:判断哪些 API 适合下沉到@smm/core-routes,被apps/cli与apps/ohos(Electron 主进程)共用。
回顾 packages/core-routes/src/ 现有实现,沉淀到该包内的 route 必须满足:
| 约束 | 现有参考 | 说明 |
|---|---|---|
| 框架中立 | src/http.ts、src/routes/*.ts 全部使用 node:http 的 IncomingMessage / ServerResponse |
不允许依赖 Hono(apps/cli 独有的 web 框架) |
| 运行时中立 | src/listFiles.ts 用 node:fs/promises,不用 Bun.file / Bun.write / Bun.$ |
apps/ohos 走 Electron + Node,CJS 打包后 Bun 全局不可用 |
| 零 apps/cli 业务依赖 | isFolderAvailable / listFiles / writeFile 仅依赖 @smm/core 和 zod |
不允许 import '@/utils/...', '../../lib/logger', '../tools/...', '../utils/...' 等 |
| 配置驱动 | CoreRoutesConfig = { allowlist, logger?, hello? } |
平台特定路径(userDataDir / logDir / tmpDir)通过 config 注入;logger 走 ctx.config.logger |
| 共享基础设施 vs CLI 业务状态 | createReverseProxyManager / createSocketIOManager 可持有跨平台基础设施生命周期 |
core-routes 可持有 Socket.IO 传输层、reverse proxy 等共享基础设施;MCP server、pty、shelljs、CLI 业务状态机(folderWatcher、rename/recognize 任务、AI agent 工具链)仍留在 apps/cli |
| HTTP route 单请求可重入 | doListFiles / doWriteFile 等无跨请求全局状态 |
createCommandExecutionLogWriter 等有副作用的 CLI 专用状态不进入 core-routes |
| 可独立单元测试 | *.test.ts 全部 vitest 跑在 Node 环境下 |
不得依赖 bun-only 的 bun:test / bun --watch |
构建链路参考:
packages/core-routes
├── src/index.ts ← ESM
├── dist/core-routes.cjs ← apps/ohos/electron 在 CJS 环境 require
└── tsconfig lib: ESNext, target: ESNext, noEmit
apps/ohos 的 main.js(Electron 主进程)调用 createCoreRoutesRequestHandler 与 createSocketIOManager,并自行提供 allowlist(userData / temp / homedir)和 hello 配置。也就是说,能放进 core-routes 的 API / 传输原语一定能被 ohos/electron 与 cli 同时复用。
| 可下沉到 core-routes | 不可下沉(留在 apps/cli) |
|---|---|
createSocketIOManager(httpServer, config) |
folderWatcher 及 fs.watch 逻辑 |
连接握手:hello / userAgent / clientId room |
rename/recognize 工具内的 broadcast 调用时机 |
broadcast / acknowledge / findSocketByClientId 传输原语 |
AI agent、ChatTask 的 ack 业务流 |
任何 import '@/utils/...' / '../tools/...' 的 CLI 业务 |
宿主职责(apps/cli / apps/ohos):创建 node:http.Server、在 HTTP handler 中对 /socket.io/ 早退、注入 logger / cors。
路径均以
apps/cli/src/route/为根。✅可迁移;⚠️需要重构/拆分后迁移;❌不建议迁移。
| apps/cli route | 现状 | core-routes 对应 |
|---|---|---|
ListFiles.ts |
薄壳,调用 doListFiles from @smm/core-routes |
✅ 已在 routes/listFilesRoute.ts |
WriteFile.ts |
薄壳,调用 doWriteFile from @smm/core-routes |
✅ 已在 routes/writeFileRoute.ts |
IsFolderAvailable.ts |
文件已删除,仅在 test 文件残留 *.test.ts |
✅ 已在 routes/isFolderAvailableRoute.ts |
execute.ts → POST /api/hello |
调用 doHello from @smm/core-routes |
✅ 已在 routes/helloRoute.ts(但 execute.ts 这个壳仍依赖 buildHelloOptions / proxyManager,详见 2.3.3) |
RenameFolder.ts |
薄壳,调用 doRenameFolder from @smm/core-routes |
✅ 已在 routes/renameFolderRoute.ts |
getEpisodes / listFilesInMediaFolder |
已抽到 core-routes | ✅ 已在 routes/getEpisodesRoute.ts / routes/listFilesInMediaFolderRoute.ts |
ReadFile.ts / DeleteFile.ts |
薄壳或已删除 | ✅ 已在 routes/readFileRoute.ts / routes/deleteFileRoute.ts |
- 职责:拉取
https://raw.gitcode.com/.../config.json并归一化为{ type, url, authorizationMethod }列表。 - 依赖审计:
- 纯
fetch+AbortController+setTimeout。 - 唯一的外部依赖是
loggerfrom'../../lib/logger'—— logger 已经走ctx.config.logger模式,可替换。 - 无
Bun.*、无shelljs、无 apps/cli 业务依赖。
- 纯
- 迁移动作:
- 提取
doDiscoverMediaDatabases()到packages/core-routes/src/discover.ts。 - 新增
routes/discoverRoute.ts,使用现有http.ts工具。 apps/cli/src/route/discover.ts改为薄壳调用doDiscoverMediaDatabases()并按 Hono 形式返回。
- 提取
- 职责:对一组允许的 URL(仅
github.com/gitcode.com)做HEAD请求并排序返回最快者。 - 依赖审计:
- 纯
fetch+performance.now()+AbortController。 - 零 apps/cli 依赖。零
Bun.*。 - 唯一可改进:用
ctx.config.logger替换console.log风格日志。
- 纯
- 迁移动作:抽
doSpeedtest(urls)到packages/core-routes/src/speedtest.ts,新建routes/speedtestRoute.ts。
- 职责:接收前端日志,转发到 Pino logger;带 10/s 速率限制。
- 依赖审计:
- 速率限制器(
RateLimiter类)是纯逻辑,不依赖任何平台。 switch (level) → logger.trace/debug/info/warn/error/fatal是 core-routesCoreRoutesLogger接口的天然超集 —— 但目前 core-routes 的CoreRoutesLogger不包含trace与fatal,需要扩展接口。- 没有任何 Bun / Hono / 业务依赖。
- 速率限制器(
- 迁移动作:
- 在
types.ts中把CoreRoutesLogger扩展为trace/fatal(或者新增一个FrontEndLogHandler注入点)。 doLog(body, config)+routes/logRoute.ts。- apps/cli 这边依然把 pino 适配成
CoreRoutesLogger即可。
- 在
- 职责:根据允许的路径读取文本文件。
- 依赖审计:
- 使用
Bun.file(platformPath).exists()/.text()——Bun.*不可移植到 ohos。 - 路径校验
validatePathIsInAllowlist已抽到 core-routes(allowlist.ts),apps/cli侧有path-validator.ts包了一层。 - 其余全是
path.resolve/Path.posix/Path.toPlatformPath等@smm/core工具。
- 使用
- 迁移动作:
- 改用
node:fs/promises.readFile。 doReadFile(body, config)抽到packages/core-routes/src/readFile.ts。readFileRequestSchema与 writeFile 对齐,调用validatePathIsInAllowlist(posixPath, config.allowlist)。- apps/cli 端
route/ReadFile.ts保留 Hono 壳。
- 改用
- 职责:读取图像文件并以
data:image/xxx;base64,...形式返回。 - 依赖审计:
Bun.file().arrayBuffer()—— 不可移植。- 其它逻辑(扩展名校验、MIME 表、Base64 编码)全是 pure。
- 迁移动作:
- 改用
node:fs/promises.readFile+Buffer.toString('base64')。 doReadImage(body)抽到packages/core-routes/src/readImage.ts。- 新建
routes/readImageRoute.ts。
- 改用
- 职责:把文件移动到系统回收站(headless 时直接删除)。
- 依赖审计:
node:fs/promises.stat—— ✅ 可移植。Path.toPlatformPath——@smm/core已共享。moveFileToTrashOrDeletefrom'../utils/files'—— apps/cli 专用,需要搬到 core 或抽象成回调。
- 迁移动作:
- 抽
doMoveFileToTrash(body)到packages/core-routes/src/moveFileToTrash.ts,核心需求:把moveFileToTrashOrDelete拆为「移到回收站」+「直接删除」两个 pure 函数并下沉到 core(或提供trash?: (path) => Promise<void>注入点)。 - ohos 端如果不需要“回收站”概念,可以在
config中提供只走unlink的实现。
- 抽
- 职责:永久删除 yt-dlp cookies 临时文件(限制路径前缀)。
- 依赖审计:
node:fs/promises.stat—— ✅。isManagedYtdlpCookiesPath(resolvedPath, userDataDir)from'@core/whitelistedCmd/ytdlpCookies'—— 该函数已在@smm/core,可共享。permanentlyDeleteFilefrom'../utils/files'—— apps/cli 专用,需要在 core-routes 重新实现(node:fs/promises.unlink)。- 路径前缀白名单依赖
getUserDataDirfrom'@/utils/config'—— 需要通过config注入。
- 迁移动作:
doDeleteFile(body, config):从config读allowlist(与 writeFile 同一套机制)。- 用
node:fs/promises.unlink实现 permanent delete。 - apps/cli 端
route/DeleteFile.ts变薄壳。
- 职责:根据 URL(http/https/file://)返回图片二进制。
- 依赖审计:
node:fs/promises.readFile+ 全局fetch—— ✅。- 路径读
allowRead(Path.posix(filePath))from'../utils/permission'—— apps/cli 专用,应当改成走config.allowlist+validatePathIsInAllowlist(与 writeFile/readFile 对齐)。 fileURLToPathfrom'url'是 Node 标准库。extnamefrom'path'是 Node 标准库。
- 迁移动作:
doDownloadImage(url, config)抽到packages/core-routes/src/downloadImage.ts。- 文件路径分支改用 allowlist 校验。
- apps/cli 端 Hono 壳可继续
c.req.query('url')→ 调doDownloadImage。
- 职责:把远端图片下载到本地指定路径。
- 依赖审计:
downloadImage(url, path)from'@/utils/downloadImage'—— apps/cli 专用工具;逻辑很简单(fetch+Bun.write),可重写为node:fs/promises.writeFile后下沉。- 目标路径允许列表:与 writeFile 一致。
- 迁移动作:
- 在 core-routes 新增
doDownloadImageToFile({ url, path }, config),走allowlist校验。 - 保持
POST /api/downloadImage协议不变,apps/cli 端薄壳。
- 在 core-routes 新增
- 职责:在 Windows 下用 PowerShell +
net use/net view列出本地盘与映射的网络盘。 - 依赖审计:
require('shelljs')—— CJS 风格,bun-only;ohos 走 Node + CJS bundling,需要改为import shelljs from 'shelljs'。- 命令调用 (
powershell -NoProfile -Command "..."/net use/net view) 仅在 Windows 下有意义。 - 解析逻辑(
_parseNetViewOutput/_parseNetUseOutput)是 pure,可下沉。 - 是否对 ohos 有意义:ohos 走 HarmonyOS 文件系统,列出 Windows 盘符没有意义 —— 结论:apps/cli 专属功能,不必迁到 core-routes。
- 建议:保留在
apps/cli。但应把require('shelljs')改成import形式(与 ohos bundler 兼容,避免 evaluate 时报错)。
- 职责:把
MediaMetadata序列化到{appDataDir}/metadata/<encoded>.json。 - 依赖审计:
mediaMetadataDir=path.join(getAppDataDir(), 'metadata')—— 依赖getAppDataDirfrom'@/utils/config'。findMediaMetadata/writeMediaMetadata/deleteMediaMetadataFile是apps/cli/src/utils/mediaMetadata.ts内的工具,强依赖getAppDataDir。Bun.file().exists()/Bun.file().unlink()/Bun.write()—— Bun-only。read.ts还调用listFiles(folderPath, true),又依赖更多apps/cli工具。
- 拆分思路:
- 把
mediaMetadataDir改成config.metadataDir(由调用方注入)。cli 端传getAppDataDir() + '/metadata',ohos 端传userData/metadata。 findMediaMetadata/writeMediaMetadata/deleteMediaMetadataFile用node:fs/promises重写后下沉。renameFilesInMediaMetadata已经@deprecated(注释明确说用POST /api/renameFiles),应直接删除而非迁移。
- 把
- 风险:
MediaMetadata涉及对 NFO / 媒体库的关键读写,要保证 ohos 在没有Bun的情况下也能工作。 - 建议:先迁
deleteMediaMetadata与writeMediaMetadata(逻辑简单),readMediaMetadata因为还需要listFiles与findMediaMetadata联动,建议作为第二批迁移。
- 状态:已完成。
doRenameFiles位于packages/core-routes/src/renameFiles.ts;POST /api/renameFiles已注册;apps/cli为薄壳;ohos 通过pnpm --filter @smm/core-routes build:ohos更新core-routes.js。 - 职责:批量重命名媒体文件(视频及关联字幕/封面等);可选地在同一请求内更新
MediaMetadata并通过 Socket.IO 广播变更。
POST /api/hello:已经迁到doHellofrom core-routes,但是 apps/cli 这边通过buildHelloOptions(proxyManager.url)注入数据。buildHelloOptions内部读version/getUserDataDir/getAppDataDir/getLogDir/getTmpDir/osLocale—— 全部是 apps/cli 资源。ohos 端在main.js里自己实现了buildHelloConfig(),所以 协议已统一,但配置入口未统一。- 建议:保留在 apps/cli,但增加
OHOS_HELLO_EXAMPLE文档(main.js 中已有实现可参考)。
- 建议:保留在 apps/cli,但增加
POST /api/execute:路由到GetSelectedMediaMetadata/system—— 业务调度,明显是 apps/cli 专属。ohos 端没有/api/execute。- 建议:保留在 apps/cli。
| Route | 关键阻碍 |
|---|---|
OpenFile.ts |
openFile / isDesktopEnv 来自 apps/cli/src/utils/os,ohos(移动设备)没有「桌面打开」语义。 |
OpenInFileManager.ts |
平台特定 cmd.exe /c start / xdg-open / open -R —— 同上,移动端无对应。 |
shutdown.ts |
强依赖 isLocalhostShutdownRequest / runGracefulShutdown / scheduleProcessExit —— apps/cli 进程生命周期。 |
Mcp.ts |
控制 apps/cli/src/mcp/mcpServerManager.ts 的 MCP HTTP server —— 协议与实现都仅 cli 使用。 |
ai.ts / AICheck.ts |
走 @ai-sdk/openai-compatible + Hono 流;MCP 端已有同名工具,重复实现;ohos 不需要。 |
Debug.ts |
内部广播/调用 tools/renameFilesInBatch / 删除 user config / 删除 media metadata cache —— dev 专用,不应该被 ohos 复用。 |
debug/*(9 个文件) |
全部薄壳,包装 agentTools.* 或 create*Tool —— agentTools 是 apps/cli 私有。 |
GetPendingPlans.ts / UpdatePlan.ts |
直接调 tools/recognizeMediaFilesTool / tools/renameFilesToolV2 —— 业务计划队列。 |
commandLog.ts / commandExecutionLog.ts / commandExecutionLogStatus.ts / commandExecutionRegistry.ts / commandExecutionStatus.ts |
围绕 getLogDir() 和 commands/<uuid>/main.log 体系,仅对 POST /api/executeCmd 有意义,没有 ohos 场景。 |
executeCmd.ts |
整个白名单命令执行 + NDJSON 流 + PTY,是 apps/cli 业务核心。ohos 没有也不该直接 spawn ffmpeg / yt-dlp。 |
tencentAsr/Transcribe.ts |
调 transcribeWithTencentAsrHttp —— 第三方 API 客户端 + 文件读取。ohos 走 HarmonyOS 的 ASR,不应共用。 |
discoverExecutables.ts |
调 resolveFfmpegPathInfo / resolveYtdlpPathInfo / resolveVideoCaptionerPathInfo / resolveQuickjsPathInfo —— apps/cli 工具发现,ohos 不需要。 |
DownloadImageToTempFile.ts |
已被 DownloadImage 取代,且无 route 注册。 |
每个候选都建议按 core-routes 现有
do<Name>(pure) +routes/<name>Route.ts(Node http handler)的两段式拆分。apps/cli 这边保留 Hono 薄壳。
| 优先级 | Route | 工作量 | 备注 |
|---|---|---|---|
| P0 | discover |
1h | 无重构,仅替换 logger 来源。 |
| P0 | speedtest |
1h | 同上。 |
| P0 | Log |
1~2h | 需要先扩展 CoreRoutesLogger 加上 trace / fatal。 |
| P1 | ReadFile |
2h | Bun.file → fs.readFile,加 allowlist 校验。 |
| P1 | ReadImage |
2h | 同上 + 调整 MIME 表。 |
| P1 | DeleteFile |
2h | 用 config.allowlist 替代 getUserDataDir。 |
| P1 | renameFiles |
Done | Migrated to packages/core-routes; ohos bundle rebuilt. |
| P2 | MoveFileToTrash |
3h | 需要把 moveFileToTrashOrDelete 拆出 pure 函数后下沉,或通过 config.trash 注入。 |
| P2 | DownloadImage |
3h | 路径分支改用 allowlist 校验。 |
| P2 | DownloadImageAsFile |
2h | 与 writeFile 共用 allowlist。 |
| P3 | mediaMetadata/{read,write,delete} |
4h | 先迁 delete/write;read 依赖 listFiles,待 2.2.4 落定后再迁。 |
| — | mediaMetadata/renameFilesInMediaMetadata |
30m | 删除(已 @deprecated)。 |
| — | DownloadImageToTempFile |
30m | 删除(未被注册)。 |
完成后,packages/core-routes 维护的 route 集合大致为:
POST /api/hello ✅
GET /api/listFiles ✅
POST /api/listFiles ✅
POST /api/writeFile ✅
POST /api/isFolderAvailable ✅
POST /api/renameFolder ✅
POST /api/getEpisodes ✅
POST /api/listFilesInMediaFolder ✅
POST /api/readFile ✅
POST /api/deleteFile ✅
POST /api/renameFiles ✅
GET /api/discover 🆕
POST /api/speedtest 🆕
POST /api/log 🆕
POST /api/readImage 🆕
POST /api/moveFileToTrash 🆕
GET /api/image 🆕
POST /api/downloadImage 🆕
POST /api/readMediaMetadata 🆕 (P3)
POST /api/writeMediaMetadata 🆕 (P3)
POST /api/deleteMediaMetadata🆕 (P3)
每次迁移应同时满足:
- 类型:
pnpm --filter @smm/core-routes typecheck与pnpm --filter cli typecheck通过。 - 单元测试:在
packages/core-routes内用 vitest 覆盖do<Name>()与<name>Routehandler(参考isFolderAvailable.test.ts)。 - 回归测试:
apps/cli原*.test.ts通过。 - ohos 端冒烟:重新跑
pnpm --filter ohos build:core-routes && copy-core-routes;在 ohos electron 主进程里对http://127.0.0.1:18081/api/<name>发送一个最小请求,确认能命中 handler。 - 回归:
apps/e2e中相关 spec(如涉及readFile/writeFile)通过。
- 当前
apps/cli一共 ~45 个 route 文件(含debug/*)。其中 ListFiles / WriteFile / IsFolderAvailable / hello / renameFolder / getEpisodes / listFilesInMediaFolder / readFile / deleteFile / renameFiles 等已在 core-routes。 POST /api/renameFiles已迁移至 core-routes(§2.3.3);ohos 复用core-routes.js即可提供 TvShow / Movie 重命名。- 另有 9 个(
discover/speedtest/Log/ReadImage/MoveFileToTrash/DownloadImage/DownloadImageAsFile等)无重大阻碍,按上述 P0~P2 计划落地。 - 3 个(
mediaMetadata/{read,write,delete})属于「可以拆但有依赖链」,建议与renameFiles一并处理;其中renameFilesInMediaMetadata已@deprecated,应当直接删除。 - 其余 25+ 个 route(
Open*/shutdown/Mcp/ai*/Debug*/commandLog*/executeCmd/tencentAsr/discoverExecutables/GetPendingPlans/UpdatePlan等)与 apps/cli 业务/平台生命周期深度耦合,不应下放到 core-routes;ohos 通过 core-routes 提供文件与 metadata 能力,不运行完整 cli 后端。