diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index f2d85a5..38a8d12 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -4,13 +4,13 @@ on: pull_request: paths: - '.github/workflows/python-test.yml' - - 'video_subtitle/**' + - 'cli/video_subtitle/**' push: branches: - master paths: - '.github/workflows/python-test.yml' - - 'video_subtitle/**' + - 'cli/video_subtitle/**' permissions: contents: read @@ -36,19 +36,19 @@ jobs: shell: bash run: | set +e - python3 -m pip install -r video_subtitle/requirements.txt + python3 -m pip install -r cli/video_subtitle/requirements.txt install_status="$?" if [ "$install_status" -ne 0 ]; then echo "failed=true" >> "$GITHUB_OUTPUT" - echo "::warning::python3 -m pip install -r video_subtitle/requirements.txt failed with exit code $install_status. Python tests were skipped. This workflow is advisory and does not block release packaging." + echo "::warning::python3 -m pip install -r cli/video_subtitle/requirements.txt failed with exit code $install_status. Python tests were skipped. This workflow is advisory and does not block release packaging." exit 0 fi - python3 -m unittest video_subtitle/video_subtitle_test.py 2>&1 | tee python-test.log + python3 -m unittest cli/video_subtitle/video_subtitle_test.py 2>&1 | tee python-test.log status="${PIPESTATUS[0]}" if [ "$status" -ne 0 ]; then echo "failed=true" >> "$GITHUB_OUTPUT" - echo "::warning::python3 -m unittest video_subtitle/video_subtitle_test.py failed with exit code $status. This workflow is advisory and does not block release packaging." + echo "::warning::python3 -m unittest cli/video_subtitle/video_subtitle_test.py failed with exit code $status. This workflow is advisory and does not block release packaging." else echo "failed=false" >> "$GITHUB_OUTPUT" fi @@ -59,4 +59,4 @@ jobs: run: | echo '## Python test reminder' >> "$GITHUB_STEP_SUMMARY" echo '' >> "$GITHUB_STEP_SUMMARY" - echo '`python3 -m unittest video_subtitle/video_subtitle_test.py` failed. This workflow is advisory and does not block release packaging.' >> "$GITHUB_STEP_SUMMARY" + echo '`python3 -m unittest cli/video_subtitle/video_subtitle_test.py` failed. This workflow is advisory and does not block release packaging.' >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 71eb868..b06eefc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,13 +6,13 @@ on: - '.github/workflows/release.yml' - '.github/workflows/go-test.yml' - '.github/workflows/python-test.yml' - - 'docs/release.md' + - 'docs/**' - 'README.MD' - 'AGENTS.md' - 'go.mod' - 'go.sum' - '**/*.go' - - 'video_subtitle/**' + - 'cli/video_subtitle/**' - 'emby_plugins/video_subtitle/**' - 'sample/life_tools/**' push: @@ -54,9 +54,9 @@ jobs: - name: Compile video_subtitle Python files run: | python3 -m py_compile \ - video_subtitle/video_subtitle.py \ - video_subtitle/video_subtitle_test.py \ - video_subtitle/lib/*.py + cli/video_subtitle/video_subtitle.py \ + cli/video_subtitle/video_subtitle_test.py \ + cli/video_subtitle/lib/*.py - name: Run Emby plugin tests run: dotnet test emby_plugins/video_subtitle/LifeTools.Emby.VideoSubtitle.sln --configuration Release @@ -85,16 +85,15 @@ jobs: out="$tmp/$pkg" mkdir -p "$out/bin" "$out/sample/life_tools" "$out/docs" - GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags "-s -w" -o "$out/bin/renameV1" ./renameV1/... - GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags "-s -w" -o "$out/bin/check_keywords" ./save_work/... - GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags "-s -w" -o "$out/bin/retry_exec" ./retry_exec/... - GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags "-s -w" -o "$out/bin/codex_hook_notify" ./codex_hook_notify/... - GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags "-s -w" -o "$out/bin/file_share" ./file_share/... + GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags "-s -w" -o "$out/bin/renameV1" ./cli/renameV1/... + GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags "-s -w" -o "$out/bin/check_keywords" ./cli/check_keywords/... + GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags "-s -w" -o "$out/bin/retry_exec" ./cli/retry_exec/... + GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags "-s -w" -o "$out/bin/codex_hook_notify" ./cli/codex_hook_notify/... + GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags "-s -w" -o "$out/bin/file_share" ./cli/file_share/... cp README.MD install.sh "$out/" cp sample/life_tools/*.json "$out/sample/life_tools/" - cp docs/install.md docs/codex_hook_notify.md docs/file_share.md "$out/docs/" - cp docs/release.md "$out/docs/" + cp -R docs/. "$out/docs/" ( cd "$tmp" @@ -105,10 +104,10 @@ jobs: video_pkg="life_tools_video_subtitle_source_${tag}" video_out="$tmp/$video_pkg" mkdir -p "$video_out/video_subtitle" "$video_out/sample/life_tools" "$video_out/docs" - cp -R video_subtitle/*.py video_subtitle/lib video_subtitle/prompts video_subtitle/requirements.txt "$video_out/video_subtitle/" + cp -R cli/video_subtitle/*.py cli/video_subtitle/lib cli/video_subtitle/prompts cli/video_subtitle/requirements.txt "$video_out/video_subtitle/" cp sample/life_tools/video_subtitle.json "$video_out/sample/life_tools/" cp README.MD install.sh "$video_out/" - cp docs/video_subtitle.md docs/release.md "$video_out/docs/" + cp -R docs/. "$video_out/docs/" ( cd "$tmp" zip -qr "$dist/$video_pkg.zip" "$video_pkg" @@ -120,7 +119,7 @@ jobs: mkdir -p "$plugin_out" "$plugin_out/docs" "$plugin_out/emby_plugins/video_subtitle" cp emby_plugins/video_subtitle/src/LifeTools.Emby.VideoSubtitle.Emby/bin/Release/netstandard2.0/LifeTools.Emby.VideoSubtitle.Emby.dll "$plugin_out/" cp emby_plugins/video_subtitle/install.sh emby_plugins/video_subtitle/build.sh "$plugin_out/emby_plugins/video_subtitle/" - cp docs/emby_video_subtitle_plugin.md docs/video_subtitle.md docs/release.md "$plugin_out/docs/" + cp -R docs/. "$plugin_out/docs/" { echo '# Life Tools Emby Video Subtitle Plugin' echo diff --git a/.github/workflows/swift-mac-app.yml b/.github/workflows/swift-mac-app.yml index dab693c..e8dc73a 100644 --- a/.github/workflows/swift-mac-app.yml +++ b/.github/workflows/swift-mac-app.yml @@ -4,8 +4,8 @@ on: pull_request: paths: - '.github/workflows/swift-mac-app.yml' - - 'mac_app/interview_timer/**' - - 'docs/interview_timer.md' + - 'gui/interview_timer/**' + - 'docs/gui/interview_timer.md' - 'docs/release.md' - 'README.MD' - 'AGENTS.md' @@ -16,8 +16,8 @@ on: - 'v*' paths: - '.github/workflows/swift-mac-app.yml' - - 'mac_app/interview_timer/**' - - 'docs/interview_timer.md' + - 'gui/interview_timer/**' + - 'docs/gui/interview_timer.md' - 'docs/release.md' - 'README.MD' - 'AGENTS.md' @@ -39,23 +39,23 @@ jobs: xcodebuild -version - name: Run Swift tests - working-directory: mac_app/interview_timer + working-directory: gui/interview_timer run: swift test - name: Build executable - working-directory: mac_app/interview_timer + working-directory: gui/interview_timer run: swift build --product InterviewTimerApp - name: Build app bundle - working-directory: mac_app/interview_timer + working-directory: gui/interview_timer run: ./scripts/build_app.sh - name: Verify app bundle run: | set -euo pipefail - test -x mac_app/interview_timer/dist/InterviewTimer.app/Contents/MacOS/InterviewTimer - test -f mac_app/interview_timer/dist/InterviewTimer.app/Contents/Resources/AppIcon.icns - plutil -p mac_app/interview_timer/dist/InterviewTimer.app/Contents/Info.plist + test -x gui/interview_timer/dist/InterviewTimer.app/Contents/MacOS/InterviewTimer + test -f gui/interview_timer/dist/InterviewTimer.app/Contents/Resources/AppIcon.icns + plutil -p gui/interview_timer/dist/InterviewTimer.app/Contents/Info.plist release: name: Publish InterviewTimer release asset @@ -69,7 +69,7 @@ jobs: uses: actions/checkout@v4 - name: Build app bundle - working-directory: mac_app/interview_timer + working-directory: gui/interview_timer run: ./scripts/build_app.sh - name: Package unsigned app @@ -83,7 +83,7 @@ jobs: rm -rf "$dist_dir" mkdir -p "$dist_dir" ditto -c -k --keepParent \ - mac_app/interview_timer/dist/InterviewTimer.app \ + gui/interview_timer/dist/InterviewTimer.app \ "$dist_dir/${asset_name}.zip" ( diff --git a/.gitignore b/.gitignore index ea268fe..84a3b76 100644 --- a/.gitignore +++ b/.gitignore @@ -103,10 +103,10 @@ renameV1/tmp/ **/obj/ # SwiftPM and macOS app build artifacts -mac_app/**/.build/ -mac_app/**/.swiftpm/ -mac_app/**/dist/ -mac_app/**/*.xcodeproj/ -mac_app/**/*.xcworkspace/ +gui/**/.build/ +gui/**/.swiftpm/ +gui/**/dist/ +gui/**/*.xcodeproj/ +gui/**/*.xcworkspace/ *.app *.dSYM/ diff --git a/AGENTS.md b/AGENTS.md index dd23b26..c9253a9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,14 +12,19 @@ ## 仓库结构 -- `renameV1/`:批量重命名命令行工具,入口是 `package main`。 -- `save_work/`:敏感词检测工具,构建产物名是 `check_keywords`。 -- `video_subtitle/`:单视频自动生成中文字幕工具,详细说明见 `docs/video_subtitle.md`。 -- `emby_plugins/video_subtitle/`:Emby Server 插件,后端调用 `video_subtitle` 生成字幕,详细说明见 `docs/emby_video_subtitle_plugin.md`。 -- `mac_app/`:macOS 图形应用集合;当前包含 `mac_app/interview_timer/`,说明见 `docs/interview_timer.md`。 -- `docs/`:项目文档,`video_subtitle` 的使用和维护说明在 `docs/video_subtitle.md`。 +- `cli/renameV1/`:批量重命名命令行工具,入口是 `package main`。 +- `cli/check_keywords/`:敏感词检测工具,构建产物名是 `check_keywords`。 +- `cli/retry_exec/`:命令失败重试和失败通知工具。 +- `cli/codex_hook_notify/`:Codex lifecycle hook 飞书提醒工具。 +- `cli/file_share/`:临时 HTTP 文件分享工具。 +- `cli/video_subtitle/`:单视频自动生成中文字幕工具,详细说明见 `docs/cli/video_subtitle.md`。 +- `emby_plugins/video_subtitle/`:Emby Server 插件,后端调用 `video_subtitle` 生成字幕,详细说明见 `docs/plugins/emby_video_subtitle.md`。 +- `gui/interview_timer/`:macOS 面试悬浮计时 GUI 应用,说明见 `docs/gui/interview_timer.md`。 +- `docs/cli/`:CLI 工具文档。 +- `docs/plugins/`:插件程序文档。 +- `docs/gui/`:GUI 程序文档。 - `sample/life_tools/`:示例配置文件。 -- `renameV1/testData/`:`renameV1` 的测试数据,包含媒体文件、nfo、bif 等样例。 +- `cli/renameV1/testData/`:`renameV1` 的测试数据,包含媒体文件、nfo、bif 等样例。 - `output/`:构建产物目录,不要把它当源码维护。 没有 `Makefile`、`justfile` 或 `Taskfile.yml`。优先遵循 README 和现有脚本。 @@ -34,11 +39,11 @@ 该脚本会构建: -- `./output/renameV1` from `./renameV1/...` -- `./output/check_keywords` from `./save_work/...` -- `./output/retry_exec` from `./retry_exec/...` -- `./output/codex_hook_notify` from `./codex_hook_notify/...` -- `./output/file_share` from `./file_share/...` +- `./output/renameV1` from `./cli/renameV1/...` +- `./output/check_keywords` from `./cli/check_keywords/...` +- `./output/retry_exec` from `./cli/retry_exec/...` +- `./output/codex_hook_notify` from `./cli/codex_hook_notify/...` +- `./output/file_share` from `./cli/file_share/...` 测试优先使用: @@ -49,13 +54,13 @@ go test ./... 也可以按包缩小范围: ```bash -go test ./renameV1 -go test ./save_work +go test ./cli/renameV1 +go test ./cli/check_keywords ``` 涉及代码、配置、依赖或构建脚本的改动,交付前必须跑相应测试或构建。纯文档改动不需要假装跑 Go 测试,但要检查 diff。 -macOS App 不走根目录 `build.sh`。修改 `mac_app/interview_timer` 时在该目录下验证: +macOS App 不走根目录 `build.sh`。修改 `gui/interview_timer` 时在该目录下验证: ```bash swift test @@ -79,7 +84,7 @@ swift build --product InterviewTimerApp - `-rename` 会调用 `os.Rename` 执行真实重命名。 - 默认不要使用 `-skip_double_check`,除非测试环境可回滚。 -- 修改重命名逻辑前,优先用 `renameV1/testData/` 或临时副本验证。 +- 修改重命名逻辑前,优先用 `cli/renameV1/testData/` 或临时副本验证。 - 不要直接在真实 NAS、媒体库、下载目录上试新逻辑。 - 配置文件结构以 `ConfigRenameApp`、`ConfigRename`、`RenamePolicy` 为准。 - `fileNames` 里的 `skip` 是占位语义,用来跳过集数,不是普通文件名。 @@ -87,27 +92,27 @@ swift build --product InterviewTimerApp ## `check_keywords` 规则 -`save_work` 目录构建出的命令叫 `check_keywords`。 +`cli/check_keywords` 目录构建出的命令叫 `check_keywords`。 - 程序从 `stdin` 读取内容。 - `PrepareContents` 只保留以 `+` 开头的行,适配 `git diff` 新增内容检查。 - 默认配置路径是 `/etc/life_tools/check_keywords.json`。 - 示例配置在 `sample/life_tools/check_keywords.json`。 -- git hook 示例在 `save_work/git-hooks/pre-commit`。 +- git hook 示例在 `cli/check_keywords/git-hooks/pre-commit`。 不要把真实公司域名、内部仓库、密钥或敏感词写进仓库。示例只能保留假数据或公开无害字符串。 ## `video_subtitle` 规则 -`video_subtitle` 是 Python 工具,不走 Go 构建入口。完整使用方式、缓存结构和排障流程见 `docs/video_subtitle.md`。 +`video_subtitle` 是 Python 工具,不走 Go 构建入口。完整使用方式、缓存结构和排障流程见 `docs/cli/video_subtitle.md`。 - 真实配置默认在 `/etc/life_tools/video_subtitle.json`,示例配置在 `sample/life_tools/video_subtitle.json`。 - 不要把 TOS、ASR、LLM、TMDB 的真实 key、token、预签名 URL 写入仓库或日志。 -- 修改 ASR、split、translation、缓存、CLI 参数时,同步更新 `docs/video_subtitle.md` 和 README 中的简要入口。 +- 修改 ASR、split、translation、缓存、CLI 参数时,同步更新 `docs/cli/video_subtitle.md` 和 README 中的简要入口。 - 涉及 LLM 输出结构时,必须保留本地 JSON 校验和失败响应落盘,不能只信 prompt。 - 修改 split 逻辑时,优先用已有 `utterances.json` 或离线 fake LLM 测试,不要直接从真实视频重新烧 ASR。 -- 交付前至少运行:`PYTHONDONTWRITEBYTECODE=1 python3 -m unittest video_subtitle/video_subtitle_test.py`。 -- 如果改了 Python 文件,还要运行 `PYTHONDONTWRITEBYTECODE=1 python3 -m py_compile video_subtitle/video_subtitle.py video_subtitle/video_subtitle_test.py video_subtitle/lib/*.py`。 +- 交付前至少运行:`PYTHONDONTWRITEBYTECODE=1 python3 -m unittest cli/video_subtitle/video_subtitle_test.py`。 +- 如果改了 Python 文件,还要运行 `PYTHONDONTWRITEBYTECODE=1 python3 -m py_compile cli/video_subtitle/video_subtitle.py cli/video_subtitle/video_subtitle_test.py cli/video_subtitle/lib/*.py`。 ### `video_subtitle` 与 Emby 插件兼容性 @@ -153,7 +158,7 @@ emby_plugins/video_subtitle/install.sh --help ## Release Workflow 规则 `.github/workflows/release.yml` 负责 tag 发布,不是普通 CI。修改发布流程时要同时关注 Go、Python `video_subtitle` 和 Emby 插件三类产物。 -`.github/workflows/swift-mac-app.yml` 负责 `mac_app/interview_timer` 的 Swift 单测、编译和未签名 `.app` 发布。 +`.github/workflows/swift-mac-app.yml` 负责 `gui/interview_timer` 的 Swift 单测、编译和未签名 `.app` 发布。 - tag 触发规则保持 `v*`,避免普通分支 push 意外创建 Release;`pull_request` 只能做 dry-run,不能创建 Release。 - Go 测试放在 `.github/workflows/go-test.yml`,Python 单元测试放在 `.github/workflows/python-test.yml`,测试失败只能写 GitHub warning 和 summary,不能让 reminder workflow 或 release workflow 失败。 diff --git a/README.MD b/README.MD index 41f8ae8..3386085 100644 --- a/README.MD +++ b/README.MD @@ -1,8 +1,8 @@ -# 简介 +# life_tools -咸鱼程序员在工作之余(如果有的话)可能用到的小工具。 +个人日常使用的小工具集合,按应用类型组织源码:命令行程序放在 `cli/`,插件程序放在 `emby_plugins/`,图形界面程序放在 `gui/`。 -## 安装 +## 快速安装 支持 Linux 和 macOS。仓库根目录执行下面命令,默认安装稳定命令行工具:`renameV1`、`check_keywords`、`retry_exec`、`codex_hook_notify`、`video_subtitle`、`file_share`。 @@ -19,244 +19,82 @@ ./install.sh --tool file_share ``` -安装到自定义前缀和配置目录: +自定义安装目录和配置目录: ```bash ./install.sh --prefix "$HOME/.local" --config-dir "$HOME/.config/life_tools" ``` -`video_subtitle` 的 Python 依赖默认不会自动安装,需要脚本安装依赖时显式执行: +`video_subtitle` 的 Python 依赖默认不会自动安装,需要时显式开启: ```bash ./install.sh --tool video_subtitle --with-python-deps ``` -完整安装说明和 AI Agent 快速安装步骤见:[docs/install.md](docs/install.md)。 +完整安装说明见 [docs/install.md](docs/install.md)。 -macOS 图形应用不走默认安装脚本。`InterviewTimer` 放在 `mac_app/interview_timer/`,用于面试过程悬浮计时,构建和安装说明见:[docs/interview_timer.md](docs/interview_timer.md)。 +## 工具清单 -## 发布包 +| 分类 | 工具 | 源码目录 | 产物/入口 | 简述 | 详细文档 | +|---|---|---|---|---|---| +| CLI | `renameV1` | `cli/renameV1/` | `renameV1` | 按配置批量重命名媒体文件 | [docs/cli/renameV1.md](docs/cli/renameV1.md) | +| CLI | `check_keywords` | `cli/check_keywords/` | `check_keywords` | 检查 git diff 新增行中的敏感词或正则 | [docs/cli/check_keywords.md](docs/cli/check_keywords.md) | +| CLI | `retry_exec` | `cli/retry_exec/` | `retry_exec` | 给任意命令增加重试、退避和失败通知 | [docs/cli/retry_exec.md](docs/cli/retry_exec.md) | +| CLI | `codex_hook_notify` | `cli/codex_hook_notify/` | `codex_hook_notify` | 接收 Codex hook 并推送飞书提醒 | [docs/cli/codex_hook_notify.md](docs/cli/codex_hook_notify.md) | +| CLI | `file_share` | `cli/file_share/` | `file_share` | 临时启动只读 HTTP 文件分享服务 | [docs/cli/file_share.md](docs/cli/file_share.md) | +| CLI | `video_subtitle` | `cli/video_subtitle/` | `video_subtitle` | 为单个视频生成简体中文字幕 | [docs/cli/video_subtitle.md](docs/cli/video_subtitle.md) | +| Plugin | Emby 字幕插件 | `emby_plugins/video_subtitle/` | `LifeTools.Emby.VideoSubtitle.Emby.dll` | 在 Emby 后台调用 `video_subtitle` 生成字幕 | [docs/plugins/emby_video_subtitle.md](docs/plugins/emby_video_subtitle.md) | +| GUI | InterviewTimer | `gui/interview_timer/` | `InterviewTimer.app` | macOS 面试悬浮计时器 | [docs/gui/interview_timer.md](docs/gui/interview_timer.md) | -推送 `v*` tag 会通过 GitHub Actions 自动构建 GitHub Release 资产,包括 Linux/macOS Go 二进制包、`video_subtitle` 源码包、Emby 插件包和未签名的 `InterviewTimer.app` 包。发布流程和资产说明见:[docs/release.md](docs/release.md)。 +## 项目结构 -## 哪些工具? +| 路径 | 内容 | +|---|---| +| `cli/` | 命令行工具源码。Go CLI 和 Python CLI 都放在这里。 | +| `emby_plugins/` | 插件类应用源码。当前只有 Emby 字幕插件。 | +| `gui/` | GUI 应用源码。当前只有 InterviewTimer macOS App。 | +| `docs/cli/` | CLI 工具的设计、结构、流程和使用文档。 | +| `docs/plugins/` | 插件类应用的设计、结构、流程和部署文档。 | +| `docs/gui/` | GUI 应用的设计、结构、流程和安装文档。 | +| `sample/life_tools/` | 示例配置文件。 | +| `build.sh` | 构建 Go CLI 工具到 `output/`。 | +| `install.sh` | 安装 CLI 工具、示例配置和可选 hook。 | -## InterviewTimer +## 构建与验证 -`InterviewTimer` 是一个 macOS 面试悬浮计时 App。它用 JSON 模板描述电话面、编码面、系统设计面等不同流程,运行时显示当前环节剩余时间、整体剩余时间和提前/落后进度。 - -源码位置: - -```bash -mac_app/interview_timer -``` - -构建: - -```bash -cd mac_app/interview_timer -swift test -swift build --product InterviewTimerApp -./scripts/build_app.sh -open dist/InterviewTimer.app -``` - -完整背景、模板格式、使用方式和安装方式见:[docs/interview_timer.md](docs/interview_timer.md)。 - -## retry_exec - -一个简单的重试工具,可以套在 crontab 等地方,避免程序单次执行失败。 - -![使用方法](https://mcoder-image-storage.oss-cn-hangzhou.aliyuncs.com/image/25261022_98943128e13fed75.jpg) - -这个程序有什么能力呢? -1. 配置后使用简单,`retry_exec [your command] [your command args] [your command args] ...` 只要在你的命令执行前加上`retry_exec` 即可。 -2. 帮你重试,重试次数可以自定义; -3. 重试时逐渐延长间隔时间,每次延长 2 倍,最小间隔时间、最大间隔时间可以自己设置; -4. 如果程序最终执行失败,可以通过飞书自定义机器人、企业微信自定义机器人等渠道给自己发送一个提醒; -5. 失败提醒会附带本次最终失败执行输出的末尾内容:优先取 `stderr`,如果 `stderr` 为空则取 `stdout`,最多 4KB,方便直接看到真实报错。 - -![飞书提醒](https://mcoder-image-storage.oss-cn-hangzhou.aliyuncs.com/image/25261056_eba2bb5ee530ca60.jpg) - -![企微提醒](https://mcoder-image-storage.oss-cn-hangzhou.aliyuncs.com/image/25261058_42e17078663144a3.jpg) - - -**安装与配置** - -```bash -./install.sh --tool retry_exec -``` - -脚本会安装 `retry_exec`,并在 `/etc/life_tools/retry_exec.json` 不存在时写入示例配置。日志目录按系统选择:Linux 使用 `/var/log/retry_exec`,macOS 使用 `~/Library/Logs/retry_exec`。安装后编辑配置里的提醒机器人地址和日志路径。 - - -![使用照片](https://mcoder-image-storage.oss-cn-hangzhou.aliyuncs.com/image/25261103_2f53b1735abdb443.jpg) - - -## renameV1 - -命令行批量重命名文件名的工具。我在家里有一台 NAS,安装了 emby,但每次下载动漫时,因为文件名命名不规范,导致 emby 无法识别,修改文件名过于繁琐, -所以实现了一个粗糙版本的文件名批量命名工具,根据顺序排序重命名。 - -安装方式,需要先配置 Golang 的运行环境,在工程根路径下执行: - -```bash -./install.sh --tool renameV1 -``` - -只想构建不安装时,可以执行: +构建 Go CLI: ```bash ./build.sh ``` -### 使用方法 - -1. 先在需要重命名的文件夹中输出一个配置文件; -2. 修改配置文件中筛选文件的后缀名,重命名规则等,用于分别重命名外挂字幕和视频内容; -3. 更新执行程序,更新配置文件; -4. 手动修改配置文件中的文件顺序,文件名 `skip` 表示占位,用于下载的视频缺了一两集的情况,序号会跳过 1; -5. 执行程序,确认更新; - -```bash -Usage of ./renameV1: - -conf_file_name string - 工作配置文件的文件名称 (default "rename_v1.json") - -create_conf int - 创建配置文件 层级数量 - -dir string - 工作路径 (default "./") - -rename - 依据配置文件执行重命名操作 - -skip_double_check - 跳过命令行二次确认 - -update_conf - 更新配置文件 -``` - -## check_keywords - -程序员难免在闲暇时写点小的脚本和小工具,如果不小心引用了公司的依赖,或者用了公司的内部代码,可能会导致很严重的后果。 - -利用 git hook 进行敏感信息检查,检查新增的行中是否包含敏感词。比如将公司的内部 git 地址作为敏感词, -每次 git commit 时检测到新增的行中包含敏感词,就会拒绝提交。 - -1. 检测敏感词; -2. 检测正则表达式; - - -## file_share - -`file_share` 是一个临时 HTTP 文件分享工具。传入一个或多个文件、目录即可启动只读 HTTP 服务,浏览器可以查看共享范围内的目录列表、打开文件、下载文件,也可以把目录打包成 zip 下载。 - -安装: - -```bash -./install.sh --tool file_share -``` - -简单分享: - -```bash -file_share /path/to/file-or-dir -file_share -addr 127.0.0.1:9000 /path/a /path/b -``` - -通过 JSON 指定共享范围: - -```bash -file_share -config /etc/life_tools/file_share.json -``` - -配置示例在 `sample/life_tools/file_share.json`,完整说明见:[docs/file_share.md](docs/file_share.md)。这个工具默认无认证、显示隐藏文件、跟随符号链接,不要把含敏感文件或敏感链接的目录暴露到不可信网络。 - -## codex_hook_notify - -Codex 生命周期提醒工具。程序从 stdin 读取 Codex hook JSON,根据事件类型把提醒发送到飞书自定义机器人。当前处理: - -1. `Stop`:Codex 本轮完成或停止。工具会优先读取本机 `CODEX_HOME/sessions`(默认 `~/.codex/sessions`)里的 transcript,推送最后一轮 Codex 用户可见输出;如果读不到 transcript,就回退到 hook 输入里的 `last_assistant_message`。 -2. `PermissionRequest`:Codex 等待用户批准执行命令或工具。 - -所有通知都会带 `Machine:` 字段。配置文件顶层 `machine_name` 非空时优先使用它;未配置或为空时使用操作系统 hostname;仍取不到时显示 `unknown`。 - -`Stop` 内容较长时会拆成多条飞书 text 消息发送。推送内容按 Codex 输出原样发送,不做敏感信息脱敏;不要把真实 webhook 配到不可信群里。 - -配置文件默认路径:`/etc/life_tools/codex_hook_notify.json`。示例配置在 `sample/life_tools/codex_hook_notify.json`。完整使用说明见 `docs/codex_hook_notify.md`。 - -```json -{ - "machine_name": "home-nas", - "routes": [ - { - "events": ["Stop"], - "feishu_custom_robot_urls": ["https://open.feishu.cn/open-apis/bot/v2/hook/xxx"] - }, - { - "events": ["PermissionRequest"], - "feishu_custom_robot_urls": ["https://open.feishu.cn/open-apis/bot/v2/hook/yyy"] - } - ] -} -``` - -安装: - -```bash -./install.sh --tool codex_hook_notify -``` - -默认安装只会构建程序、安装 `/usr/local/bin/codex_hook_notify`、创建配置样例和日志目录,不会修改 Codex 全局 hook。确认配置后,显式执行下面命令安装 `Stop` hook: - -```bash -./install.sh --tool codex_hook_notify --install-codex-hook -``` - -`PermissionRequest` 在 approval 模式下提醒很多,默认不安装;如果旧版本已经安装过本工具的 `PermissionRequest` hook,默认安装命令会把它移除。确实需要审批提醒时再显式安装: - -```bash -./install.sh --tool codex_hook_notify --install-codex-hook --with-permission-request -``` - -日志目录按系统选择:Linux 使用 `/var/log/codex_hook_notify`,macOS 使用 `~/Library/Logs/codex_hook_notify`,其他系统使用 `~/.codex_hook_notify/logs`。发送失败只写 stderr 和日志,程序仍返回 0,不阻塞 Codex。 - -## video_subtitle - -给单个视频生成简体中文字幕的 Python 工具。它会抽取音频、调用火山 ASR、用 LLM 做字幕切分和翻译,并在视频同目录输出 `.zh-CN.srt`。 - -完整使用说明、工作流程、项目结构和排障方法见:[docs/video_subtitle.md](docs/video_subtitle.md)。Emby 后端插件见:[docs/emby_video_subtitle_plugin.md](docs/emby_video_subtitle_plugin.md)。 - -安装: - -```bash -./install.sh --tool video_subtitle -``` - -安装 Python 依赖: +Python CLI 语法检查: ```bash -python3 -m pip install -r video_subtitle/requirements.txt +PYTHONDONTWRITEBYTECODE=1 python3 -m py_compile \ + cli/video_subtitle/video_subtitle.py \ + cli/video_subtitle/video_subtitle_test.py \ + cli/video_subtitle/lib/*.py ``` -也可以让安装脚本顺手安装 Python 依赖: +Emby 插件验证: ```bash -./install.sh --tool video_subtitle --with-python-deps +dotnet test emby_plugins/video_subtitle/LifeTools.Emby.VideoSubtitle.sln +dotnet build emby_plugins/video_subtitle/LifeTools.Emby.VideoSubtitle.sln ``` -配置文件示例在 `sample/life_tools/video_subtitle.json`,真实配置默认放到 `/etc/life_tools/video_subtitle.json`。 - -生成字幕: +InterviewTimer 验证: ```bash -video_subtitle --input /path/to/video.mp4 +cd gui/interview_timer +swift test +swift build --product InterviewTimerApp +./scripts/build_app.sh ``` -常用调试命令: +具体工具的测试和排障入口见各自详细文档。 -```bash -# 只重跑翻译,复用 ASR 和切分缓存 -video_subtitle --input /path/to/video.mp4 --force-translate +## 发布 -# 跳过 ASR,强制从 ASR words 的 LLM 切分和翻译开始 -video_subtitle --input /path/to/video.mp4 --force-split --force-translate -``` +推送 `v*` tag 会通过 GitHub Actions 构建 GitHub Release 资产,包括 Linux/macOS Go 二进制包、`video_subtitle` 源码包、Emby 插件包和未签名的 `InterviewTimer.app` 包。发布流程见 [docs/release.md](docs/release.md)。 diff --git a/build.sh b/build.sh index 5602f19..679c842 100755 --- a/build.sh +++ b/build.sh @@ -3,16 +3,16 @@ mkdir -p output # 文件批量重命名 -go build -v -o ./output/renameV1 ./renameV1/... +go build -v -o ./output/renameV1 ./cli/renameV1/... # 敏感词检测 -go build -v -o ./output/check_keywords ./save_work/... +go build -v -o ./output/check_keywords ./cli/check_keywords/... # retry 包装壳子 -go build -v -o ./output/retry_exec ./retry_exec/... +go build -v -o ./output/retry_exec ./cli/retry_exec/... # Codex hook 飞书提醒 -go build -v -o ./output/codex_hook_notify ./codex_hook_notify/... +go build -v -o ./output/codex_hook_notify ./cli/codex_hook_notify/... # HTTP 文件快速分享 -go build -v -o ./output/file_share ./file_share/... +go build -v -o ./output/file_share ./cli/file_share/... diff --git a/save_work/config.go b/cli/check_keywords/config.go similarity index 100% rename from save_work/config.go rename to cli/check_keywords/config.go diff --git a/save_work/find.go b/cli/check_keywords/find.go similarity index 100% rename from save_work/find.go rename to cli/check_keywords/find.go diff --git a/save_work/git-hooks/pre-commit b/cli/check_keywords/git-hooks/pre-commit similarity index 100% rename from save_work/git-hooks/pre-commit rename to cli/check_keywords/git-hooks/pre-commit diff --git a/save_work/main.go b/cli/check_keywords/main.go similarity index 100% rename from save_work/main.go rename to cli/check_keywords/main.go diff --git a/codex_hook_notify/config.go b/cli/codex_hook_notify/config.go similarity index 100% rename from codex_hook_notify/config.go rename to cli/codex_hook_notify/config.go diff --git a/codex_hook_notify/config_test.go b/cli/codex_hook_notify/config_test.go similarity index 100% rename from codex_hook_notify/config_test.go rename to cli/codex_hook_notify/config_test.go diff --git a/codex_hook_notify/event.go b/cli/codex_hook_notify/event.go similarity index 100% rename from codex_hook_notify/event.go rename to cli/codex_hook_notify/event.go diff --git a/codex_hook_notify/event_test.go b/cli/codex_hook_notify/event_test.go similarity index 100% rename from codex_hook_notify/event_test.go rename to cli/codex_hook_notify/event_test.go diff --git a/codex_hook_notify/feishu.go b/cli/codex_hook_notify/feishu.go similarity index 100% rename from codex_hook_notify/feishu.go rename to cli/codex_hook_notify/feishu.go diff --git a/codex_hook_notify/feishu_test.go b/cli/codex_hook_notify/feishu_test.go similarity index 100% rename from codex_hook_notify/feishu_test.go rename to cli/codex_hook_notify/feishu_test.go diff --git a/codex_hook_notify/logger.go b/cli/codex_hook_notify/logger.go similarity index 100% rename from codex_hook_notify/logger.go rename to cli/codex_hook_notify/logger.go diff --git a/codex_hook_notify/logger_test.go b/cli/codex_hook_notify/logger_test.go similarity index 100% rename from codex_hook_notify/logger_test.go rename to cli/codex_hook_notify/logger_test.go diff --git a/codex_hook_notify/main.go b/cli/codex_hook_notify/main.go similarity index 100% rename from codex_hook_notify/main.go rename to cli/codex_hook_notify/main.go diff --git a/codex_hook_notify/run.go b/cli/codex_hook_notify/run.go similarity index 100% rename from codex_hook_notify/run.go rename to cli/codex_hook_notify/run.go diff --git a/codex_hook_notify/run_test.go b/cli/codex_hook_notify/run_test.go similarity index 100% rename from codex_hook_notify/run_test.go rename to cli/codex_hook_notify/run_test.go diff --git a/codex_hook_notify/transcript.go b/cli/codex_hook_notify/transcript.go similarity index 100% rename from codex_hook_notify/transcript.go rename to cli/codex_hook_notify/transcript.go diff --git a/codex_hook_notify/transcript_test.go b/cli/codex_hook_notify/transcript_test.go similarity index 100% rename from codex_hook_notify/transcript_test.go rename to cli/codex_hook_notify/transcript_test.go diff --git a/file_share/config.go b/cli/file_share/config.go similarity index 100% rename from file_share/config.go rename to cli/file_share/config.go diff --git a/file_share/config_test.go b/cli/file_share/config_test.go similarity index 100% rename from file_share/config_test.go rename to cli/file_share/config_test.go diff --git a/file_share/main.go b/cli/file_share/main.go similarity index 100% rename from file_share/main.go rename to cli/file_share/main.go diff --git a/file_share/main_test.go b/cli/file_share/main_test.go similarity index 100% rename from file_share/main_test.go rename to cli/file_share/main_test.go diff --git a/file_share/server.go b/cli/file_share/server.go similarity index 100% rename from file_share/server.go rename to cli/file_share/server.go diff --git a/file_share/server_test.go b/cli/file_share/server_test.go similarity index 100% rename from file_share/server_test.go rename to cli/file_share/server_test.go diff --git a/renameV1/config.go b/cli/renameV1/config.go similarity index 100% rename from renameV1/config.go rename to cli/renameV1/config.go diff --git a/renameV1/double_check.go b/cli/renameV1/double_check.go similarity index 100% rename from renameV1/double_check.go rename to cli/renameV1/double_check.go diff --git a/renameV1/file.go b/cli/renameV1/file.go similarity index 100% rename from renameV1/file.go rename to cli/renameV1/file.go diff --git a/renameV1/file_test.go b/cli/renameV1/file_test.go similarity index 100% rename from renameV1/file_test.go rename to cli/renameV1/file_test.go diff --git a/renameV1/main.go b/cli/renameV1/main.go similarity index 100% rename from renameV1/main.go rename to cli/renameV1/main.go diff --git a/renameV1/param.go b/cli/renameV1/param.go similarity index 100% rename from renameV1/param.go rename to cli/renameV1/param.go diff --git a/renameV1/program.go b/cli/renameV1/program.go similarity index 100% rename from renameV1/program.go rename to cli/renameV1/program.go diff --git a/renameV1/rename.go b/cli/renameV1/rename.go similarity index 100% rename from renameV1/rename.go rename to cli/renameV1/rename.go diff --git a/renameV1/testData/file_name_with_space.mkv b/cli/renameV1/testData/file_name_with_space.mkv similarity index 100% rename from renameV1/testData/file_name_with_space.mkv rename to cli/renameV1/testData/file_name_with_space.mkv diff --git a/renameV1/testData/rename_v1.json b/cli/renameV1/testData/rename_v1.json similarity index 100% rename from renameV1/testData/rename_v1.json rename to cli/renameV1/testData/rename_v1.json diff --git a/renameV1/testData/season.nfo b/cli/renameV1/testData/season.nfo similarity index 100% rename from renameV1/testData/season.nfo rename to cli/renameV1/testData/season.nfo diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01-thumb.jpg" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01-thumb.jpg" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01-thumb.jpg" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01-thumb.jpg" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E01.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02-thumb.jpg" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02-thumb.jpg" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02-thumb.jpg" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02-thumb.jpg" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E02.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03-thumb.jpg" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03-thumb.jpg" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03-thumb.jpg" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03-thumb.jpg" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03.mp4" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03.mp4" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03.mp4" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03.mp4" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E03.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04-thumb.jpg" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04-thumb.jpg" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04-thumb.jpg" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04-thumb.jpg" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E04.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05-thumb.jpg" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05-thumb.jpg" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05-thumb.jpg" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05-thumb.jpg" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E05.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06-thumb.jpg" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06-thumb.jpg" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06-thumb.jpg" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06-thumb.jpg" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E06.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07-thumb.jpg" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07-thumb.jpg" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07-thumb.jpg" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07-thumb.jpg" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E07.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09-thumb.jpg" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09-thumb.jpg" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09-thumb.jpg" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09-thumb.jpg" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E09.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E10-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E10-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E10-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E10-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E10.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E10.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E10.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E10.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E10.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E10.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E10.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E10.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E11-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E11-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E11-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E11-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E11.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E11.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E11.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E11.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E11.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E11.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E11.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E11.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E12-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E12-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E12-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E12-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E12.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E12.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E12.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E12.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E12.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E12.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E12.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E12.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E13-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E13-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E13-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E13-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E13.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E13.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E13.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E13.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E13.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E13.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E13.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E13.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E14-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E14-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E14-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E14-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E14.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E14.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E14.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E14.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E14.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E14.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E14.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E14.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E15-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E15-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E15-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E15-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E15.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E15.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E15.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E15.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E15.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E15.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E15.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E15.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E16-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E16-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E16-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E16-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E16.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E16.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E16.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E16.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E16.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E16.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E16.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E16.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E17-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E17-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E17-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E17-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E17.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E17.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E17.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E17.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E17.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E17.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E17.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E17.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E18-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E18-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E18-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E18-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E18.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E18.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E18.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E18.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E18.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E18.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E18.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E18.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E20-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E20-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E20-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E20-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E20.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E20.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E20.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E20.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E20.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E20.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E20.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E20.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E21-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E21-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E21-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E21-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E21.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E21.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E21.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E21.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E21.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E21.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E21.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E21.nfo" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E23-320-10.bif" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E23-320-10.bif" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E23-320-10.bif" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E23-320-10.bif" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E23.mkv" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E23.mkv" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E23.mkv" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E23.mkv" diff --git "a/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E23.nfo" "b/cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E23.nfo" similarity index 100% rename from "renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E23.nfo" rename to "cli/renameV1/testData/\347\224\237\346\264\273\345\244\247\347\210\206\347\202\270_S02E23.nfo" diff --git a/retry_exec/notify.go b/cli/retry_exec/notify.go similarity index 100% rename from retry_exec/notify.go rename to cli/retry_exec/notify.go diff --git a/retry_exec/param.go b/cli/retry_exec/param.go similarity index 100% rename from retry_exec/param.go rename to cli/retry_exec/param.go diff --git a/retry_exec/retry.go b/cli/retry_exec/retry.go similarity index 100% rename from retry_exec/retry.go rename to cli/retry_exec/retry.go diff --git a/retry_exec/retry_exec.go b/cli/retry_exec/retry_exec.go similarity index 100% rename from retry_exec/retry_exec.go rename to cli/retry_exec/retry_exec.go diff --git a/retry_exec/retry_test.go b/cli/retry_exec/retry_test.go similarity index 100% rename from retry_exec/retry_test.go rename to cli/retry_exec/retry_test.go diff --git a/retry_exec/tail_writer.go b/cli/retry_exec/tail_writer.go similarity index 100% rename from retry_exec/tail_writer.go rename to cli/retry_exec/tail_writer.go diff --git a/video_subtitle/__init__.py b/cli/video_subtitle/__init__.py similarity index 100% rename from video_subtitle/__init__.py rename to cli/video_subtitle/__init__.py diff --git a/video_subtitle/lib/__init__.py b/cli/video_subtitle/lib/__init__.py similarity index 100% rename from video_subtitle/lib/__init__.py rename to cli/video_subtitle/lib/__init__.py diff --git a/video_subtitle/lib/asr.py b/cli/video_subtitle/lib/asr.py similarity index 100% rename from video_subtitle/lib/asr.py rename to cli/video_subtitle/lib/asr.py diff --git a/video_subtitle/lib/audio.py b/cli/video_subtitle/lib/audio.py similarity index 100% rename from video_subtitle/lib/audio.py rename to cli/video_subtitle/lib/audio.py diff --git a/video_subtitle/lib/cli.py b/cli/video_subtitle/lib/cli.py similarity index 100% rename from video_subtitle/lib/cli.py rename to cli/video_subtitle/lib/cli.py diff --git a/video_subtitle/lib/config.py b/cli/video_subtitle/lib/config.py similarity index 100% rename from video_subtitle/lib/config.py rename to cli/video_subtitle/lib/config.py diff --git a/video_subtitle/lib/context.py b/cli/video_subtitle/lib/context.py similarity index 100% rename from video_subtitle/lib/context.py rename to cli/video_subtitle/lib/context.py diff --git a/video_subtitle/lib/embedded_subtitles.py b/cli/video_subtitle/lib/embedded_subtitles.py similarity index 100% rename from video_subtitle/lib/embedded_subtitles.py rename to cli/video_subtitle/lib/embedded_subtitles.py diff --git a/video_subtitle/lib/io_utils.py b/cli/video_subtitle/lib/io_utils.py similarity index 100% rename from video_subtitle/lib/io_utils.py rename to cli/video_subtitle/lib/io_utils.py diff --git a/video_subtitle/lib/llm.py b/cli/video_subtitle/lib/llm.py similarity index 100% rename from video_subtitle/lib/llm.py rename to cli/video_subtitle/lib/llm.py diff --git a/video_subtitle/lib/pipeline.py b/cli/video_subtitle/lib/pipeline.py similarity index 100% rename from video_subtitle/lib/pipeline.py rename to cli/video_subtitle/lib/pipeline.py diff --git a/video_subtitle/lib/progress.py b/cli/video_subtitle/lib/progress.py similarity index 100% rename from video_subtitle/lib/progress.py rename to cli/video_subtitle/lib/progress.py diff --git a/video_subtitle/lib/prompts.py b/cli/video_subtitle/lib/prompts.py similarity index 100% rename from video_subtitle/lib/prompts.py rename to cli/video_subtitle/lib/prompts.py diff --git a/video_subtitle/lib/runtime.py b/cli/video_subtitle/lib/runtime.py similarity index 100% rename from video_subtitle/lib/runtime.py rename to cli/video_subtitle/lib/runtime.py diff --git a/video_subtitle/lib/subtitle.py b/cli/video_subtitle/lib/subtitle.py similarity index 100% rename from video_subtitle/lib/subtitle.py rename to cli/video_subtitle/lib/subtitle.py diff --git a/video_subtitle/lib/tos_client.py b/cli/video_subtitle/lib/tos_client.py similarity index 100% rename from video_subtitle/lib/tos_client.py rename to cli/video_subtitle/lib/tos_client.py diff --git a/video_subtitle/prompts/consistency.txt b/cli/video_subtitle/prompts/consistency.txt similarity index 100% rename from video_subtitle/prompts/consistency.txt rename to cli/video_subtitle/prompts/consistency.txt diff --git a/video_subtitle/prompts/split.txt b/cli/video_subtitle/prompts/split.txt similarity index 100% rename from video_subtitle/prompts/split.txt rename to cli/video_subtitle/prompts/split.txt diff --git a/video_subtitle/prompts/translation.txt b/cli/video_subtitle/prompts/translation.txt similarity index 100% rename from video_subtitle/prompts/translation.txt rename to cli/video_subtitle/prompts/translation.txt diff --git a/video_subtitle/requirements.txt b/cli/video_subtitle/requirements.txt similarity index 100% rename from video_subtitle/requirements.txt rename to cli/video_subtitle/requirements.txt diff --git a/video_subtitle/video_subtitle.py b/cli/video_subtitle/video_subtitle.py similarity index 100% rename from video_subtitle/video_subtitle.py rename to cli/video_subtitle/video_subtitle.py diff --git a/video_subtitle/video_subtitle_test.py b/cli/video_subtitle/video_subtitle_test.py similarity index 100% rename from video_subtitle/video_subtitle_test.py rename to cli/video_subtitle/video_subtitle_test.py diff --git a/docs/cli/check_keywords.md b/docs/cli/check_keywords.md new file mode 100644 index 0000000..f9649da --- /dev/null +++ b/docs/cli/check_keywords.md @@ -0,0 +1,101 @@ +# check_keywords 敏感词检查工具 + +`check_keywords` 从 stdin 读取内容,只检查以 `+` 开头的行,用于 git diff 新增内容中的敏感词拦截。它适合放在个人公开仓库的 pre-commit hook 中,避免把内部域名、私有依赖、敏感词或项目代号误提交出去。 + +## 设计目标 + +| 目标 | 处理方式 | +|---|---| +| 只检查新增内容 | `PrepareContents` 只保留首字符为 `+` 的行 | +| 不把真实敏感词写入仓库 | 真实配置从本机 JSON 文件读取 | +| 适配大 diff | 按 `-parallel` 把行切分成多个批次并发检查 | +| hook 友好 | 命中时退出码为 `1`,未命中时退出码为 `0` | + +## 代码结构 + +| 路径 | 作用 | +|---|---| +| `cli/check_keywords/main.go` | 参数解析、读取 stdin、加载配置、控制退出码 | +| `cli/check_keywords/config.go` | JSON 配置结构和加载 | +| `cli/check_keywords/find.go` | 并发切分、大小写不敏感的关键词匹配 | +| `cli/check_keywords/git-hooks/pre-commit` | git pre-commit 示例脚本 | +| `sample/life_tools/check_keywords.json` | 示例配置 | + +## 使用方法 + +安装: + +```bash +./install.sh --tool check_keywords +``` + +手工检查当前 diff: + +```bash +git diff HEAD | check_keywords +``` + +静默模式适合 hook: + +```bash +git diff HEAD | check_keywords -quiet +``` + +指定配置和并发: + +```bash +git diff HEAD | check_keywords -config /etc/life_tools/check_keywords.json -parallel 4 +``` + +## 配置结构 + +默认配置路径是 `/etc/life_tools/check_keywords.json`。 + +```json +{ + "keywords": [ + "internal.example.com", + "secret project" + ] +} +``` + +| 字段 | 说明 | +|---|---| +| `keywords` | 字符串列表,匹配时会统一转小写后做 `strings.Contains` | + +## 核心流程 + +```mermaid +flowchart TD + A["读取 CLI 参数"] --> B["加载 JSON 配置"] + B --> C["从 stdin 读取 git diff"] + C --> D["只保留以 + 开头的行"] + D --> E["按 parallel 切分批次"] + E --> F["并发执行大小写不敏感匹配"] + F --> G{"是否命中关键词?"} + G -->|是| H["记录命中信息并退出 1"] + G -->|否| I["退出 0"] +``` + +## pre-commit 集成 + +示例脚本在 `cli/check_keywords/git-hooks/pre-commit`。脚本会先检查 remote 是否指向 GitHub 或 `mcoder2014`,只有外部仓库才执行关键词检查。 + +```bash +install -m 0755 cli/check_keywords/git-hooks/pre-commit .git/hooks/pre-commit +``` + +## 风险边界 + +- 工具只检查首字符为 `+` 的行,不解析 diff header;极端情况下 `+++ b/file` 这类 diff 元信息也会进入检查范围。 +- 匹配方式是普通子串匹配,不是正则。 +- `-parallel` 过大时不会提升小 diff 性能,通常保留默认值即可。 +- 示例配置只能放公开无害字符串,真实敏感词应放在本机配置文件。 + +## 验证 + +```bash +./build.sh +go test ./cli/check_keywords +``` diff --git a/docs/codex_hook_notify.md b/docs/cli/codex_hook_notify.md similarity index 79% rename from docs/codex_hook_notify.md rename to docs/cli/codex_hook_notify.md index f83bd9a..5790ab4 100644 --- a/docs/codex_hook_notify.md +++ b/docs/cli/codex_hook_notify.md @@ -9,6 +9,48 @@ 提醒失败不会阻塞 Codex。程序会向 stderr 输出错误,并写入本地日志。 +## 设计目标 + +| 目标 | 处理方式 | +|---|---| +| 不阻塞 Codex | 读取、解析、发送和写日志失败都返回 0,只写 stderr 和日志 | +| 支持按事件路由 | 配置中用 `routes[].events` 匹配 hook event | +| Stop 内容更有用 | 优先从本机 transcript 提取最后一轮 assistant 可见输出 | +| 长消息可发送 | 飞书 text 内容过长时拆成多条消息 | +| 多机器可区分 | 每条通知都带 `Machine:` 字段 | + +## 代码结构 + +| 路径 | 作用 | +|---|---| +| `cli/codex_hook_notify/main.go` | CLI 入口,读取配置并调用 `Run` | +| `cli/codex_hook_notify/config.go` | 配置结构、事件匹配、机器名解析 | +| `cli/codex_hook_notify/event.go` | Codex hook 输入解析和通知文本构造 | +| `cli/codex_hook_notify/transcript.go` | 从 `CODEX_HOME/sessions` 查找 transcript 并提取 summary | +| `cli/codex_hook_notify/feishu.go` | 飞书自定义机器人发送 | +| `cli/codex_hook_notify/logger.go` | 本地日志路径和日志写入 | +| `cli/codex_hook_notify/run.go` | 主流程编排和失败上报 | + +## 处理流程 + +```mermaid +flowchart TD + A["读取 --config"] --> B["解析 codex_hook_notify.json"] + B --> C["从 stdin 读取 Codex hook JSON"] + C --> D["解析 hook_event_name 和 session 信息"] + D --> E{"配置中是否匹配事件?"} + E -->|否| F["静默结束"] + E -->|是| G["解析 Machine 名称"] + G --> H{"是否 Stop 事件?"} + H -->|是| I["尝试读取 transcript summary"] + H -->|否| J["使用 hook 输入构造消息"] + I --> K["失败时回退 last_assistant_message"] + J --> L["按长度拆分飞书 text"] + K --> L + L --> M["发送到匹配的 webhook"] + M --> N["写入本地日志"] +``` + ## 默认路径 | 内容 | Linux | macOS | diff --git a/docs/file_share.md b/docs/cli/file_share.md similarity index 60% rename from docs/file_share.md rename to docs/cli/file_share.md index ca44623..d115197 100644 --- a/docs/file_share.md +++ b/docs/cli/file_share.md @@ -2,6 +2,43 @@ `file_share` 用来临时分享本机文件或目录。它启动一个只读 HTTP 服务,访问者只能浏览程序启动时选择的共享范围。 +## 设计目标 + +| 目标 | 处理方式 | +|---|---| +| 快速分享本机路径 | 直接把命令行路径或 JSON 配置转换成共享条目 | +| 保持只读 | HTTP 只允许 `GET` 和 `HEAD` | +| 支持目录浏览 | 目录页用 HTML 表格展示名称、类型、大小和修改时间 | +| 支持目录打包 | `/zip/` 路由按请求实时创建 zip | +| 避免明显路径穿越 | URL 相对路径包含 `.` 或 `..` 段时直接拒绝 | + +## 代码结构 + +| 路径 | 作用 | +|---|---| +| `cli/file_share/main.go` | CLI 参数、配置加载和 HTTP server 启动 | +| `cli/file_share/config.go` | JSON 配置、共享条目构造和路径 stat | +| `cli/file_share/server.go` | 路由、目录页渲染、文件下载、目录 zip 和访问日志 | +| `sample/life_tools/file_share.json` | 示例配置 | + +## 请求流程 + +```mermaid +flowchart TD + A["启动 file_share"] --> B{"是否传入 -config?"} + B -->|是| C["读取 JSON entries"] + B -->|否| D["读取命令行路径参数"] + C --> E["BuildEntries 校验路径并分配 ID"] + D --> E + E --> F["启动 http.ListenAndServe"] + F --> G{"请求路径"} + G -->|"/"| H["渲染共享入口页"] + G -->|"/browse/{id}/..."| I["浏览目录或内联打开文件"] + G -->|"/raw/{id}/..."| J["内联返回文件"] + G -->|"/download/{id}/..."| K["附件下载文件"] + G -->|"/zip/{id}/..."| L["实时打包目录 zip"] +``` + ## 安装 ```bash diff --git a/docs/cli/renameV1.md b/docs/cli/renameV1.md new file mode 100644 index 0000000..80538b5 --- /dev/null +++ b/docs/cli/renameV1.md @@ -0,0 +1,116 @@ +# renameV1 批量重命名工具 + +`renameV1` 是一个按配置批量重命名文件的 CLI。当前主要面向媒体库文件名整理:先生成或更新配置,再人工确认文件顺序,最后执行真实重命名。 + +## 设计目标 + +| 目标 | 处理方式 | +|---|---| +| 降低手工重命名成本 | 用 `rename_v1.json` 描述后缀过滤、目标命名规则和文件顺序 | +| 保留人工确认 | 默认每个写操作前都要求输入 `y`,除非显式传 `-skip_double_check` | +| 避免目标名互相覆盖 | 执行重命名时先改成带 UUID 的临时名,再改成最终名 | +| 支持缺集占位 | `fileNames` 中的 `skip` 只推进集数,不参与真实文件重命名 | + +## 代码结构 + +| 路径 | 作用 | +|---|---| +| `cli/renameV1/main.go` | CLI 参数解析和执行入口 | +| `cli/renameV1/param.go` | 参数结构 | +| `cli/renameV1/config.go` | 配置结构、配置读写和示例配置生成 | +| `cli/renameV1/file.go` | 目录扫描、后缀过滤、路径拼接 | +| `cli/renameV1/program.go` | `create_conf`、`update_conf`、`rename` 三种模式的主流程 | +| `cli/renameV1/rename.go` | 目标文件名生成、冲突检查、两阶段重命名 | +| `cli/renameV1/double_check.go` | 交互式二次确认 | +| `cli/renameV1/testData/` | 单测使用的媒体文件、nfo、bif 示例 | + +## 使用方法 + +安装: + +```bash +./install.sh --tool renameV1 +``` + +只构建: + +```bash +./build.sh +``` + +常用流程: + +```bash +# 1. 在目标目录生成配置,数字表示生成几组规则 +renameV1 -dir /path/to/media -create_conf 1 + +# 2. 按当前目录文件更新 fileNames +renameV1 -dir /path/to/media -update_conf + +# 3. 人工编辑 rename_v1.json,确认文件顺序和命名规则 + +# 4. 执行重命名 +renameV1 -dir /path/to/media -rename +``` + +参数: + +| 参数 | 默认值 | 说明 | +|---|---|---| +| `-dir` | `./` | 工作目录 | +| `-conf_file_name` | `rename_v1.json` | 配置文件名 | +| `-create_conf` | `0` | 创建示例配置,值表示配置组数量 | +| `-update_conf` | `false` | 按当前目录文件刷新 `fileNames` | +| `-rename` | `false` | 根据配置执行真实重命名 | +| `-skip_double_check` | `false` | 跳过命令行二次确认 | + +## 配置结构 + +示例配置在 `sample/life_tools/rename_v1.json`。 + +| 字段 | 说明 | +|---|---| +| `configs[]` | 多组重命名规则,可分别处理视频、字幕等文件 | +| `postfix` | `update_conf` 扫描目录时使用的后缀过滤列表 | +| `policy.name` | 目标文件名模板,`{Ep}` 会被替换成集数 | +| `policy.num_start` | 起始集数 | +| `policy.num_interval` | 每个文件递增的集数间隔 | +| `policy.rename_filename_extension` | 是否把模板里的后缀当作最终后缀 | +| `policy.ep_digit_count` | 集数补零位数 | +| `fileNames` | 待重命名文件顺序,`skip` 表示缺集占位 | + +## 核心流程 + +```mermaid +flowchart TD + A["解析 CLI 参数"] --> B{"create_conf > 0?"} + B -->|是| C["生成示例配置并写入 rename_v1.json"] + B -->|否| D{"update_conf?"} + C --> D + D -->|是| E["读取配置并按 postfix 扫描目录"] + E --> F["刷新 fileNames 并写回配置"] + D -->|否| G{"rename?"} + F --> G + G -->|是| H["读取配置并生成目标文件名"] + H --> I["检查最终文件名是否冲突"] + I --> J{"需要二次确认?"} + J -->|是| K["展示预览并等待输入 y"] + J -->|否| L["两阶段 os.Rename"] + K --> L + G -->|否| M["结束"] + L --> M +``` + +## 风险边界 + +- `-rename` 会调用 `os.Rename` 修改真实文件名,不要直接在不可恢复的媒体库目录试新规则。 +- `-skip_double_check` 会跳过覆盖配置和重命名确认,只应在可回滚的测试目录使用。 +- 当前 `filterFileName` 直接按后缀长度切片,`postfix` 不应配置成长于文件名的字符串。 +- 冲突检查只检查最终文件名冲突;执行过程中如果磁盘、权限或外部进程改动文件,仍可能失败。 + +## 验证 + +```bash +./build.sh +go test ./cli/renameV1 +``` diff --git a/docs/cli/retry_exec.md b/docs/cli/retry_exec.md new file mode 100644 index 0000000..b1aa473 --- /dev/null +++ b/docs/cli/retry_exec.md @@ -0,0 +1,103 @@ +# retry_exec 命令重试工具 + +`retry_exec` 用来包装任意命令,为它增加失败重试、指数退避、本地日志和失败通知。它适合放在 crontab、简单脚本或个人自动化任务里,处理偶发失败。 + +## 设计目标 + +| 目标 | 处理方式 | +|---|---| +| 不改变被包装命令的输入输出 | 子进程复用当前 stdin,stdout/stderr 继续透传 | +| 失败后自动重试 | `Retry.Try` 递归执行命令,失败后按配置 sleep | +| 避免通知过长 | 只保留 stdout/stderr 最后 4KB,通知优先带 stderr | +| 支持多通知渠道 | 当前支持飞书自定义机器人、企业微信机器人和 Gotify | + +## 代码结构 + +| 路径 | 作用 | +|---|---| +| `cli/retry_exec/retry_exec.go` | CLI 入口、配置读取、日志初始化 | +| `cli/retry_exec/param.go` | JSON 配置结构 | +| `cli/retry_exec/retry.go` | 命令执行、重试、退避和输出尾部截取 | +| `cli/retry_exec/tail_writer.go` | 固定大小尾部缓存 | +| `cli/retry_exec/notify.go` | 飞书、企业微信、Gotify 通知 | +| `sample/life_tools/retry_exec.json` | 示例配置 | + +## 使用方法 + +安装: + +```bash +./install.sh --tool retry_exec +``` + +包装命令: + +```bash +retry_exec curl -f https://example.com/health +``` + +指定配置: + +```bash +retry_exec --config /etc/life_tools/retry_exec.json curl -f https://example.com/health +``` + +## 配置结构 + +默认配置路径是 `/etc/life_tools/retry_exec.json`。 + +| 字段 | 说明 | +|---|---| +| `retry_times` | 总尝试次数。小于等于 1 时失败后不再重试 | +| `retry_interval` | 初始重试间隔,单位毫秒 | +| `max_retry_interval` | 最大重试间隔,单位毫秒 | +| `feishu_custom_robot_url` | 飞书自定义机器人 webhook | +| `wechat_robot_url` | 企业微信机器人 webhook | +| `gotify_config.server_url` | Gotify 服务地址 | +| `gotify_config.token` | Gotify token | +| `log` | 本地日志目录 | + +macOS 安装脚本会把默认日志目录调整为 `~/Library/Logs/retry_exec`;Linux 默认是 `/var/log/retry_exec`。 + +## 核心流程 + +```mermaid +flowchart TD + A["解析 --config 和被包装命令"] --> B["读取 retry_exec.json"] + B --> C["初始化本地日志"] + C --> D["执行子命令并透传 stdin/stdout/stderr"] + D --> E{"exit code == 0 且无错误?"} + E -->|是| F["返回成功"] + E -->|否| G{"剩余 retry_times > 1?"} + G -->|是| H["按 retry_interval 睡眠"] + H --> I["retry_times 减 1 并尝试翻倍间隔"] + I --> D + G -->|否| J["截取 stderr 或 stdout 尾部"] + J --> K["发送配置的失败通知"] + K --> L["用子命令 exit code 退出"] +``` + +## 失败通知 + +通知内容包含: + +| 内容 | 来源 | +|---|---| +| 命令参数 | `commands` | +| exit code | 子命令退出码 | +| Go error | 子进程执行错误 | +| 输出尾部 | 优先 stderr,stderr 为空时使用 stdout,最多 4KB | + +## 风险边界 + +- `runtime.GOMAXPROCS(1)` 当前限制为单线程,适合轻量包装,不适合高吞吐任务调度。 +- 命令参数来自 `os.Args`,不是 shell 字符串;需要 shell 展开时显式包装为 `sh -c '...'`。 +- 通知失败只写日志,不会改变最终退出码。 +- 重试会重复执行副作用命令,写库、发消息、扣费等操作不要直接套用。 + +## 验证 + +```bash +./build.sh +go test ./cli/retry_exec +``` diff --git a/docs/video_subtitle.md b/docs/cli/video_subtitle.md similarity index 88% rename from docs/video_subtitle.md rename to docs/cli/video_subtitle.md index beea7bf..c77389b 100644 --- a/docs/video_subtitle.md +++ b/docs/cli/video_subtitle.md @@ -15,7 +15,7 @@ 安装 Python 依赖: ```bash -python3 -m pip install -r video_subtitle/requirements.txt +python3 -m pip install -r cli/video_subtitle/requirements.txt ``` 也可以让安装脚本顺手安装 Python 依赖: @@ -91,25 +91,25 @@ LLM 相关默认策略: ```mermaid flowchart TD - A[输入视频] --> B[确定 work_dir 和输出路径] - B --> C{ASR 缓存可用?} - C -->|是| D[读取 utterances.json] - C -->|否或 force-asr| E[ffmpeg 抽取音频] - E --> F[上传 TOS 并生成预签名 URL] - F --> G[提交并轮询火山 ASR] - G --> H[写入 asr_result.json 和 utterances.json] - D --> I[清洗 ASR words] + A["输入视频"] --> B["确定 work_dir 和输出路径"] + B --> C{"ASR 缓存可用?"} + C -->|是| D["读取 utterances.json"] + C -->|否或 force-asr| E["ffmpeg 抽取音频"] + E --> F["上传 TOS 并生成预签名 URL"] + F --> G["提交并轮询火山 ASR"] + G --> H["写入 asr_result.json 和 utterances.json"] + D --> I["清洗 ASR words"] H --> I - I --> J{可采用内嵌字幕?} - J -->|是| K[生成 subtitle_units.json] - J -->|否或 force-split| L[LLM 并发切分 ASR words] - L --> M[写入 split_chunks 和 subtitle_units.json] - K --> N{翻译缓存可用?} + I --> J{"可采用内嵌字幕?"} + J -->|是| K["生成 subtitle_units.json"] + J -->|否或 force-split| L["LLM 并发切分 ASR words"] + L --> M["写入 split_chunks 和 subtitle_units.json"] + K --> N{"翻译缓存可用?"} M --> N - N -->|是| O[读取 translations.json] - N -->|否或 force-translate| P[LLM 并发翻译] - P --> Q[写入 translations.json] - O --> R[生成 SRT] + N -->|是| O["读取 translations.json"] + N -->|否或 force-translate| P["LLM 并发翻译"] + P --> Q["写入 translations.json"] + O --> R["生成 SRT"] Q --> R ``` @@ -127,7 +127,7 @@ flowchart TD ## 项目结构 ```text -video_subtitle/ +cli/video_subtitle/ video_subtitle.py 兼容旧命令的入口文件 requirements.txt Python 依赖 video_subtitle_test.py 单元测试 @@ -219,7 +219,7 @@ sudo -u emby test -w /path/to/video/dir - `Permission denied: .../.video_subtitle_work`:Emby 服务用户不能写视频目录。 - 页面任务提交成功但历史没有输出文件:先看插件任务历史的 `StderrTail`,再看视频目录 `.video_subtitle_work/*/progress.jsonl`。 -详细插件配置、Web 页面、Admin API 和本机排障记录见 [docs/emby_video_subtitle_plugin.md](emby_video_subtitle_plugin.md)。 +详细插件配置、Web 页面、Admin API 和本机排障记录见 [docs/plugins/emby_video_subtitle.md](../plugins/emby_video_subtitle.md)。 ## 故障排查 @@ -262,16 +262,16 @@ PY 只跑 Python 工具测试: ```bash -PYTHONDONTWRITEBYTECODE=1 python3 -m unittest video_subtitle/video_subtitle_test.py +PYTHONDONTWRITEBYTECODE=1 python3 -m unittest cli/video_subtitle/video_subtitle_test.py ``` 编译检查: ```bash PYTHONDONTWRITEBYTECODE=1 python3 -m py_compile \ - video_subtitle/video_subtitle.py \ - video_subtitle/video_subtitle_test.py \ - video_subtitle/lib/*.py + cli/video_subtitle/video_subtitle.py \ + cli/video_subtitle/video_subtitle_test.py \ + cli/video_subtitle/lib/*.py ``` Emby 插件核心和适配层验证: @@ -286,7 +286,7 @@ emby_plugins/video_subtitle/install.sh --help CLI 入口检查: ```bash -PYTHONDONTWRITEBYTECODE=1 python3 video_subtitle/video_subtitle.py --help +PYTHONDONTWRITEBYTECODE=1 python3 cli/video_subtitle/video_subtitle.py --help ``` 仓库空白检查: @@ -298,7 +298,7 @@ git diff --check 涉及真实视频生成时,应优先用已有 ASR 缓存验证 split 和翻译: ```bash -python3 video_subtitle/video_subtitle.py \ +python3 cli/video_subtitle/video_subtitle.py \ --input /path/to/video.mkv \ --yes \ --force-split \ diff --git a/docs/interview_timer.md b/docs/gui/interview_timer.md similarity index 85% rename from docs/interview_timer.md rename to docs/gui/interview_timer.md index 07df4ca..938d5f0 100644 --- a/docs/interview_timer.md +++ b/docs/gui/interview_timer.md @@ -2,7 +2,7 @@ `InterviewTimer` 是一个 macOS 悬浮计时 App,用于主持面试时控制节奏。它不是命令行工具,也不进入根目录 `install.sh` 的默认安装集合。 -![InterviewTimer 图标](../mac_app/interview_timer/assets/app-icon-source.png) +![InterviewTimer 图标](../../gui/interview_timer/assets/app-icon-source.png) ## 背景 @@ -13,7 +13,7 @@ ## 代码位置 ```text -mac_app/interview_timer/ +gui/interview_timer/ ``` | 路径 | 作用 | @@ -38,6 +38,31 @@ mac_app/interview_timer/ | 提醒 | 当前环节超时和整体超时时显示视觉状态,并在授权后发送 macOS 通知 | | 旧模板兼容 | 继续读取旧版单文件 `template.json`,不强制迁移 | +## 核心流程 + +```mermaid +flowchart TD + A["App 启动"] --> B["创建 Application Support 目录"] + B --> C["加载 templates/*.json 和旧版 template.json"] + C --> D{"是否存在可用模板"} + D -->|是| E["恢复上次选中模板"] + D -->|否| F["写入默认模板"] + E --> G["创建 InterviewSession"] + F --> G + G --> H["显示悬浮面板"] + H --> I{"用户操作"} + I -->|开始| J["启动计时器"] + I -->|下一环节| K["推进当前 stage"] + I -->|切换模板| L["重新加载模板并重置 session"] + J --> M["更新当前环节和整体剩余时间"] + K --> M + L --> G + M --> N{"达到提醒阈值或超时"} + N -->|是| O["更新视觉状态并发送 macOS 通知"] + N -->|否| H + O --> H +``` + ## 模板与数据文件 运行时数据统一放在: @@ -90,7 +115,7 @@ mac_app/interview_timer/ ```bash mkdir -p "$HOME/Library/Application Support/InterviewTimer/templates" -cp mac_app/interview_timer/template-presets/*.json "$HOME/Library/Application Support/InterviewTimer/templates/" +cp gui/interview_timer/template-presets/*.json "$HOME/Library/Application Support/InterviewTimer/templates/" ``` 日常操作: @@ -118,7 +143,7 @@ cp mac_app/interview_timer/template-presets/*.json "$HOME/Library/Application Su 验证命令: ```bash -cd mac_app/interview_timer +cd gui/interview_timer swift test swift build --product InterviewTimerApp ./scripts/build_app.sh @@ -136,7 +161,7 @@ swift build --product InterviewTimerApp 本地用户安装: ```bash -cd mac_app/interview_timer +cd gui/interview_timer ./scripts/build_app.sh mkdir -p "$HOME/Applications" ditto dist/InterviewTimer.app "$HOME/Applications/InterviewTimer.app" @@ -146,7 +171,7 @@ open "$HOME/Applications/InterviewTimer.app" 也可以直接运行构建产物: ```bash -open mac_app/interview_timer/dist/InterviewTimer.app +open gui/interview_timer/dist/InterviewTimer.app ``` 注意事项: diff --git a/docs/install.md b/docs/install.md index 5ec1de7..162125b 100644 --- a/docs/install.md +++ b/docs/install.md @@ -87,13 +87,13 @@ file_share -config /etc/life_tools/file_share.json `InterviewTimer` 是 macOS 面试悬浮计时 App,源码位于: ```text -mac_app/interview_timer +gui/interview_timer ``` 它不是命令行工具,不会被仓库根目录 `install.sh` 安装。构建和安装: ```bash -cd mac_app/interview_timer +cd gui/interview_timer swift test ./scripts/build_app.sh mkdir -p "$HOME/Applications" @@ -107,7 +107,7 @@ open "$HOME/Applications/InterviewTimer.app" ~/Library/Application Support/InterviewTimer/templates/ ``` -仓库内预置模板在 `mac_app/interview_timer/template-presets/`。完整说明见 [interview_timer.md](interview_timer.md)。 +仓库内预置模板在 `gui/interview_timer/template-presets/`。完整说明见 [gui/interview_timer.md](gui/interview_timer.md)。 ## video_subtitle @@ -203,5 +203,5 @@ open "$HOME/Applications/InterviewTimer.app" - Linux:`/var/log/codex_hook_notify` - macOS:`~/Library/Logs/codex_hook_notify` - 其他系统:`~/.codex_hook_notify/logs` -- `video_subtitle` 会复制 `video_subtitle/` 到 `/lib/life_tools/video_subtitle`,再安装一个同名包装命令。 -- `InterviewTimer` 需要在 `mac_app/interview_timer` 下单独构建 `.app`,根目录安装脚本不会复制或覆盖 macOS 应用。 +- `video_subtitle` 会复制 `cli/video_subtitle/` 到 `/lib/life_tools/video_subtitle`,再安装一个同名包装命令。 +- `InterviewTimer` 需要在 `gui/interview_timer` 下单独构建 `.app`,根目录安装脚本不会复制或覆盖 macOS 应用。 diff --git a/docs/emby_video_subtitle_plugin.md b/docs/plugins/emby_video_subtitle.md similarity index 99% rename from docs/emby_video_subtitle_plugin.md rename to docs/plugins/emby_video_subtitle.md index 56fe203..8d0e1e5 100644 --- a/docs/emby_video_subtitle_plugin.md +++ b/docs/plugins/emby_video_subtitle.md @@ -49,7 +49,7 @@ MaxLogTailBytes=8192 ```text ExecutablePath=/usr/bin/python3 -ExtraArgs=/path/to/life_tools/video_subtitle/video_subtitle.py +ExtraArgs=/path/to/life_tools/cli/video_subtitle/video_subtitle.py ConfigPath=/etc/life_tools/video_subtitle.json ``` @@ -405,7 +405,7 @@ sudo -u emby /usr/local/bin/video_subtitle --help ```text ExecutablePath=/usr/bin/python3 -ExtraArgs=/path/to/life_tools/video_subtitle/video_subtitle.py +ExtraArgs=/path/to/life_tools/cli/video_subtitle/video_subtitle.py ``` ### 任务失败:媒体目录不能写 diff --git a/docs/release.md b/docs/release.md index 5fb72f6..69522c7 100644 --- a/docs/release.md +++ b/docs/release.md @@ -29,13 +29,13 @@ on: - '.github/workflows/release.yml' - '.github/workflows/go-test.yml' - '.github/workflows/python-test.yml' - - 'docs/release.md' + - 'docs/**' - 'README.MD' - 'AGENTS.md' - 'go.mod' - 'go.sum' - '**/*.go' - - 'video_subtitle/**' + - 'cli/video_subtitle/**' - 'emby_plugins/video_subtitle/**' - 'sample/life_tools/**' push: @@ -45,7 +45,7 @@ on: `pull_request` 在 release workflow 中只做 Python 编译检查、Emby 插件测试和打包 dry-run;只有 tag push 才执行 `gh release create` 或 `gh release upload`。Go 测试由 `.github/workflows/go-test.yml` 单独执行,Python 单元测试由 `.github/workflows/python-test.yml` 单独执行。测试失败时 reminder workflow 只写 GitHub warning 和 summary,本身仍返回成功,不阻塞发布包流程。 -`swift-mac-app.yml` 使用 `macos-latest` runner。PR 和 `master` 推送会验证 `mac_app/interview_timer` 的 Swift 单测、可执行产物构建和 `.app` 打包;`v*` tag 会额外上传未签名的 `InterviewTimer.app` zip。 +`swift-mac-app.yml` 使用 `macos-latest` runner。PR 和 `master` 推送会验证 `gui/interview_timer` 的 Swift 单测、可执行产物构建和 `.app` 打包;`v*` tag 会额外上传未签名的 `InterviewTimer.app` zip。 ## 发布操作流程 @@ -92,7 +92,7 @@ bin/codex_hook_notify bin/file_share install.sh sample/life_tools/*.json -docs/*.md +docs/** ``` `video_subtitle` 发布包包含 Python 源码、prompts、`requirements.txt`、示例配置和文档。它不是纯二进制工具,使用前仍需要 Python 依赖、ffmpeg、TOS、ASR、LLM 配置。 @@ -111,14 +111,14 @@ LifeTools.Emby.VideoSubtitle.Emby.dll InterviewTimer.app ``` -该包由 `mac_app/interview_timer/scripts/build_app.sh` 生成,当前不做代码签名和 notarization。macOS 首次打开时可能需要用户在系统安全设置中手动允许。 +该包由 `gui/interview_timer/scripts/build_app.sh` 生成,当前不做代码签名和 notarization。macOS 首次打开时可能需要用户在系统安全设置中手动允许。 ## CI 验证 发布前 release workflow 会运行: ```bash -python3 -m py_compile video_subtitle/video_subtitle.py video_subtitle/video_subtitle_test.py video_subtitle/lib/*.py +python3 -m py_compile cli/video_subtitle/video_subtitle.py cli/video_subtitle/video_subtitle_test.py cli/video_subtitle/lib/*.py dotnet test emby_plugins/video_subtitle/LifeTools.Emby.VideoSubtitle.sln --configuration Release dotnet build emby_plugins/video_subtitle/LifeTools.Emby.VideoSubtitle.sln --configuration Release ``` @@ -126,7 +126,7 @@ dotnet build emby_plugins/video_subtitle/LifeTools.Emby.VideoSubtitle.sln --conf Swift macOS App workflow 会运行: ```bash -cd mac_app/interview_timer +cd gui/interview_timer swift test swift build --product InterviewTimerApp ./scripts/build_app.sh @@ -136,7 +136,7 @@ swift build --product InterviewTimerApp ```bash go test ./... -python3 -m unittest video_subtitle/video_subtitle_test.py +python3 -m unittest cli/video_subtitle/video_subtitle_test.py ``` 这些 workflow 失败时只写 GitHub warning 和 summary,不阻塞 release workflow,也不阻止 tag 发布资产。 diff --git a/mac_app/interview_timer/Package.swift b/gui/interview_timer/Package.swift similarity index 100% rename from mac_app/interview_timer/Package.swift rename to gui/interview_timer/Package.swift diff --git a/mac_app/interview_timer/README.md b/gui/interview_timer/README.md similarity index 95% rename from mac_app/interview_timer/README.md rename to gui/interview_timer/README.md index 87a2179..76762a1 100644 --- a/mac_app/interview_timer/README.md +++ b/gui/interview_timer/README.md @@ -2,7 +2,7 @@ 一个独立的 macOS 面试悬浮计时工具,适合在另一个系统旁边主持面试时使用。 -该应用在 `life_tools` 仓库中归档于 `mac_app/interview_timer/`。仓库级背景、安装和维护说明见 `../../docs/interview_timer.md`。 +该应用在 `life_tools` 仓库中归档于 `gui/interview_timer/`。仓库级背景、安装和维护说明见 `../../docs/gui/interview_timer.md`。 ## 功能 diff --git a/mac_app/interview_timer/Sources/InterviewTimerApp/FloatingPanel.swift b/gui/interview_timer/Sources/InterviewTimerApp/FloatingPanel.swift similarity index 100% rename from mac_app/interview_timer/Sources/InterviewTimerApp/FloatingPanel.swift rename to gui/interview_timer/Sources/InterviewTimerApp/FloatingPanel.swift diff --git a/mac_app/interview_timer/Sources/InterviewTimerApp/InterviewTimerApp.swift b/gui/interview_timer/Sources/InterviewTimerApp/InterviewTimerApp.swift similarity index 100% rename from mac_app/interview_timer/Sources/InterviewTimerApp/InterviewTimerApp.swift rename to gui/interview_timer/Sources/InterviewTimerApp/InterviewTimerApp.swift diff --git a/mac_app/interview_timer/Sources/InterviewTimerApp/InterviewTimerView.swift b/gui/interview_timer/Sources/InterviewTimerApp/InterviewTimerView.swift similarity index 100% rename from mac_app/interview_timer/Sources/InterviewTimerApp/InterviewTimerView.swift rename to gui/interview_timer/Sources/InterviewTimerApp/InterviewTimerView.swift diff --git a/mac_app/interview_timer/Sources/InterviewTimerApp/InterviewTimerViewModel.swift b/gui/interview_timer/Sources/InterviewTimerApp/InterviewTimerViewModel.swift similarity index 100% rename from mac_app/interview_timer/Sources/InterviewTimerApp/InterviewTimerViewModel.swift rename to gui/interview_timer/Sources/InterviewTimerApp/InterviewTimerViewModel.swift diff --git a/mac_app/interview_timer/Sources/InterviewTimerApp/NotificationManager.swift b/gui/interview_timer/Sources/InterviewTimerApp/NotificationManager.swift similarity index 100% rename from mac_app/interview_timer/Sources/InterviewTimerApp/NotificationManager.swift rename to gui/interview_timer/Sources/InterviewTimerApp/NotificationManager.swift diff --git a/mac_app/interview_timer/Sources/InterviewTimerCore/InterviewSession.swift b/gui/interview_timer/Sources/InterviewTimerCore/InterviewSession.swift similarity index 100% rename from mac_app/interview_timer/Sources/InterviewTimerCore/InterviewSession.swift rename to gui/interview_timer/Sources/InterviewTimerCore/InterviewSession.swift diff --git a/mac_app/interview_timer/Sources/InterviewTimerCore/InterviewTemplate.swift b/gui/interview_timer/Sources/InterviewTimerCore/InterviewTemplate.swift similarity index 100% rename from mac_app/interview_timer/Sources/InterviewTimerCore/InterviewTemplate.swift rename to gui/interview_timer/Sources/InterviewTimerCore/InterviewTemplate.swift diff --git a/mac_app/interview_timer/Sources/InterviewTimerCore/PanelPositionStore.swift b/gui/interview_timer/Sources/InterviewTimerCore/PanelPositionStore.swift similarity index 100% rename from mac_app/interview_timer/Sources/InterviewTimerCore/PanelPositionStore.swift rename to gui/interview_timer/Sources/InterviewTimerCore/PanelPositionStore.swift diff --git a/mac_app/interview_timer/Sources/InterviewTimerCore/TemplateStore.swift b/gui/interview_timer/Sources/InterviewTimerCore/TemplateStore.swift similarity index 100% rename from mac_app/interview_timer/Sources/InterviewTimerCore/TemplateStore.swift rename to gui/interview_timer/Sources/InterviewTimerCore/TemplateStore.swift diff --git a/mac_app/interview_timer/Tests/InterviewTimerCoreTests/InterviewSessionTests.swift b/gui/interview_timer/Tests/InterviewTimerCoreTests/InterviewSessionTests.swift similarity index 100% rename from mac_app/interview_timer/Tests/InterviewTimerCoreTests/InterviewSessionTests.swift rename to gui/interview_timer/Tests/InterviewTimerCoreTests/InterviewSessionTests.swift diff --git a/mac_app/interview_timer/Tests/InterviewTimerCoreTests/InterviewTemplateTests.swift b/gui/interview_timer/Tests/InterviewTimerCoreTests/InterviewTemplateTests.swift similarity index 100% rename from mac_app/interview_timer/Tests/InterviewTimerCoreTests/InterviewTemplateTests.swift rename to gui/interview_timer/Tests/InterviewTimerCoreTests/InterviewTemplateTests.swift diff --git a/mac_app/interview_timer/Tests/InterviewTimerCoreTests/TemplateStoreTests.swift b/gui/interview_timer/Tests/InterviewTimerCoreTests/TemplateStoreTests.swift similarity index 100% rename from mac_app/interview_timer/Tests/InterviewTimerCoreTests/TemplateStoreTests.swift rename to gui/interview_timer/Tests/InterviewTimerCoreTests/TemplateStoreTests.swift diff --git a/mac_app/interview_timer/assets/app-icon-source.png b/gui/interview_timer/assets/app-icon-source.png similarity index 100% rename from mac_app/interview_timer/assets/app-icon-source.png rename to gui/interview_timer/assets/app-icon-source.png diff --git a/mac_app/interview_timer/scripts/build_app.sh b/gui/interview_timer/scripts/build_app.sh similarity index 100% rename from mac_app/interview_timer/scripts/build_app.sh rename to gui/interview_timer/scripts/build_app.sh diff --git a/mac_app/interview_timer/template-presets/30min-phone-screen.json b/gui/interview_timer/template-presets/30min-phone-screen.json similarity index 100% rename from mac_app/interview_timer/template-presets/30min-phone-screen.json rename to gui/interview_timer/template-presets/30min-phone-screen.json diff --git a/mac_app/interview_timer/template-presets/45min-general-interview.json b/gui/interview_timer/template-presets/45min-general-interview.json similarity index 100% rename from mac_app/interview_timer/template-presets/45min-general-interview.json rename to gui/interview_timer/template-presets/45min-general-interview.json diff --git a/mac_app/interview_timer/template-presets/60min-coding-interview.json b/gui/interview_timer/template-presets/60min-coding-interview.json similarity index 100% rename from mac_app/interview_timer/template-presets/60min-coding-interview.json rename to gui/interview_timer/template-presets/60min-coding-interview.json diff --git a/mac_app/interview_timer/template-presets/75min-system-design.json b/gui/interview_timer/template-presets/75min-system-design.json similarity index 100% rename from mac_app/interview_timer/template-presets/75min-system-design.json rename to gui/interview_timer/template-presets/75min-system-design.json diff --git a/mac_app/interview_timer/template-presets/90min-onsite-mixed.json b/gui/interview_timer/template-presets/90min-onsite-mixed.json similarity index 100% rename from mac_app/interview_timer/template-presets/90min-onsite-mixed.json rename to gui/interview_timer/template-presets/90min-onsite-mixed.json diff --git a/install.sh b/install.sh index 40a0879..fd0f5ba 100755 --- a/install.sh +++ b/install.sh @@ -367,12 +367,12 @@ codex_log_dir() { } install_rename_v1() { - install_go_tool renameV1 renameV1 ./renameV1/... + install_go_tool renameV1 renameV1 ./cli/renameV1/... echo "renameV1 uses per-directory config files; sample: sample/life_tools/rename_v1.json" } install_check_keywords() { - install_go_tool check_keywords check_keywords ./save_work/... + install_go_tool check_keywords check_keywords ./cli/check_keywords/... install_config_if_missing "$ROOT_DIR/sample/life_tools/check_keywords.json" "$CONFIG_DIR/check_keywords.json" } @@ -417,7 +417,7 @@ install_retry_exec() { local log_dir log_dir="$(retry_exec_log_dir)" - install_go_tool retry_exec retry_exec ./retry_exec/... + install_go_tool retry_exec retry_exec ./cli/retry_exec/... install_retry_exec_config_if_missing "$log_dir" ensure_user_writable_dir "$log_dir" } @@ -426,7 +426,7 @@ install_codex_hook_notify() { local config_file="$CONFIG_DIR/codex_hook_notify.json" local log_dir - install_go_tool codex_hook_notify codex_hook_notify ./codex_hook_notify/... + install_go_tool codex_hook_notify codex_hook_notify ./cli/codex_hook_notify/... install_config_if_missing "$ROOT_DIR/sample/life_tools/codex_hook_notify.json" "$config_file" log_dir="$(codex_log_dir)" ensure_user_writable_dir "$log_dir" @@ -519,7 +519,7 @@ PYCODE install_file_share() { - install_go_tool file_share file_share ./file_share/... + install_go_tool file_share file_share ./cli/file_share/... install_config_if_missing "$ROOT_DIR/sample/life_tools/file_share.json" "$CONFIG_DIR/file_share.json" cat < "$wrapper" <