Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/workflows/alfred-workflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Alfred Workflow

on:
pull_request:
paths:
- '.github/workflows/alfred-workflow.yml'
- '.github/workflows/release.yml'
- 'plugins/alfred_remote_upload/**'
- 'docs/plugins/alfred_remote_upload.md'
- 'docs/specs/2026-07-14-alfred-remote-upload-design.md'
- 'docs/release.md'
- 'README.MD'
- 'AGENTS.md'
push:
branches:
- master
paths:
- '.github/workflows/alfred-workflow.yml'
- 'plugins/alfred_remote_upload/**'

permissions:
contents: read

jobs:
test:
name: Test and package Alfred workflow
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Check shell syntax
run: |
bash -n plugins/alfred_remote_upload/build.sh
bash -n plugins/alfred_remote_upload/tests/clipboard_integration.sh
bash -n plugins/alfred_remote_upload/tests/run.sh
bash -n plugins/alfred_remote_upload/tests/fakes/*
bash -n plugins/alfred_remote_upload/workflow/scripts/*.sh

- name: Run offline tests
run: plugins/alfred_remote_upload/tests/run.sh

- name: Run clipboard integration tests
run: plugins/alfred_remote_upload/tests/clipboard_integration.sh

- name: Build and inspect package
run: |
artifact="$RUNNER_TEMP/life_tools_alfred_remote_upload_ci.alfredworkflow"
plugins/alfred_remote_upload/build.sh "$artifact"
plutil -lint plugins/alfred_remote_upload/workflow/info.plist
unzip -t "$artifact"

- name: Upload dry-run artifact
uses: actions/upload-artifact@v4
with:
name: life_tools_alfred_remote_upload_ci
path: ${{ runner.temp }}/life_tools_alfred_remote_upload_ci.alfredworkflow
if-no-files-found: error
35 changes: 30 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
- '.github/workflows/release.yml'
- '.github/workflows/go-test.yml'
- '.github/workflows/python-test.yml'
- '.github/workflows/alfred-workflow.yml'
- 'docs/**'
- 'README.MD'
- 'AGENTS.md'
Expand All @@ -14,13 +15,14 @@ on:
- '**/*.go'
- 'cli/video_subtitle/**'
- 'emby_plugins/video_subtitle/**'
- 'plugins/alfred_remote_upload/**'
- 'sample/life_tools/**'
push:
tags:
- 'v*'

permissions:
contents: write
contents: read

jobs:
release:
Expand Down Expand Up @@ -140,23 +142,46 @@ jobs:
zip -qr "$dist/$plugin_pkg.zip" "$plugin_pkg"
)

plugins/alfred_remote_upload/build.sh "$dist/life_tools_alfred_remote_upload_${tag}.alfredworkflow"

(
cd "$dist"
sha256sum *.zip > checksums.txt
sha256sum *.zip *.alfredworkflow > checksums.txt
)

ls -lh "$dist"

- name: Upload release assets
uses: actions/upload-artifact@v4
with:
name: release-assets
path: dist/
if-no-files-found: error

publish:
name: Publish GitHub release
if: github.ref_type == 'tag'
needs: release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download release assets
uses: actions/download-artifact@v4
with:
name: release-assets
path: dist

- name: Create GitHub release
if: github.ref_type == 'tag'
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME}"
title="life_tools ${tag}"
if gh release view "$tag" >/dev/null 2>&1; then
gh release upload "$tag" dist/*.zip dist/checksums.txt --clobber
gh release upload "$tag" dist/*.zip dist/*.alfredworkflow dist/checksums.txt --clobber
else
gh release create "$tag" dist/*.zip dist/checksums.txt --title "$title" --generate-notes
gh release create "$tag" dist/*.zip dist/*.alfredworkflow dist/checksums.txt --title "$title" --generate-notes
fi
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,6 @@ gui/**/.swiftpm/
gui/**/dist/
gui/**/*.xcodeproj/
gui/**/*.xcworkspace/
plugins/**/dist/
*.app
*.dSYM/
12 changes: 12 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- `cli/codex_inspector/`:本机 Codex 历史会话、活跃度和记忆内容只读查看工具,当前是实验 CLI,不进默认安装清单。
- `cli/video_subtitle/`:单视频自动生成中文字幕工具,详细说明见 `docs/cli/video_subtitle.md`。
- `emby_plugins/video_subtitle/`:Emby Server 插件,后端调用 `video_subtitle` 生成字幕,详细说明见 `docs/plugins/emby_video_subtitle.md`。
- `plugins/alfred_remote_upload/`:Alfred 5 Workflow,将剪贴板中的图片或 Finder 单文件上传到 SSH 主机,详细说明见 `docs/plugins/alfred_remote_upload.md`。
- `gui/interview_timer/`:macOS 面试悬浮计时 GUI 应用,说明见 `docs/gui/interview_timer.md`。
- `docs/cli/`:CLI 工具文档。
- `docs/plugins/`:插件程序文档。
Expand Down Expand Up @@ -69,6 +70,15 @@ swift build --product InterviewTimerApp
./scripts/build_app.sh
```

Alfred Workflow 也不走根目录 `build.sh`。修改 `plugins/alfred_remote_upload` 时运行:

```bash
plugins/alfred_remote_upload/tests/run.sh
plugins/alfred_remote_upload/tests/clipboard_integration.sh
plugins/alfred_remote_upload/build.sh
plutil -lint plugins/alfred_remote_upload/workflow/info.plist
```

## Go 代码规则

- 保持 Go 1.18 兼容,不随手升级语言版本。
Expand Down Expand Up @@ -160,6 +170,7 @@ emby_plugins/video_subtitle/install.sh --help

`.github/workflows/release.yml` 负责 tag 发布,不是普通 CI。修改发布流程时要同时关注 Go、Python `video_subtitle` 和 Emby 插件三类产物。
`.github/workflows/swift-mac-app.yml` 负责 `gui/interview_timer` 的 Swift 单测、编译和未签名 `.app` 发布。
`.github/workflows/alfred-workflow.yml` 负责 `plugins/alfred_remote_upload` 的 macOS 离线测试、plist 校验和打包验证。

- tag 触发规则保持 `v*`,避免普通分支 push 意外创建 Release;`pull_request` 只能做 dry-run,不能创建 Release。
- Go 测试放在 `.github/workflows/go-test.yml`,PR 时必须真实运行 `go test ./...`,不能做成只提醒不阻塞的 reminder。
Expand All @@ -168,6 +179,7 @@ emby_plugins/video_subtitle/install.sh --help
- `video_subtitle` 只能按源码包发布,不能宣传成免依赖二进制;它仍依赖 Python、ffmpeg、TOS、ASR 和 LLM 配置。
- Emby 插件包只放 `LifeTools.Emby.VideoSubtitle.Emby.dll` 和文档,不要把 `MediaBrowser.*`、`Emby.*` 或核心库 DLL 打进插件发布包。
- `InterviewTimer` macOS App 只在 macOS runner 上构建,tag 发布包为未签名的 `InterviewTimer.app` zip,不要把它塞进根目录 `install.sh` 或 Go 二进制发布包。
- Alfred Workflow 只依赖 macOS/JXA/OpenSSH,发布资产名为 `life_tools_alfred_remote_upload_<tag>.alfredworkflow`;用户主机配置和 MRU 状态不能进入安装包,也不要把它加入根目录 CLI 安装脚本。
- 新增 CLI、构建入口、release 产物或测试要求时,必须检查 `.github/workflows/*` 是否需要同步,避免代码合入后没有 CI 或 tag 发布产物缺失。
- 修改发布包内容时同步更新 `docs/release.md` 和 README 的发布入口。
- 发布 workflow 需要 `permissions: contents: write`,不要扩大到无关权限。
Expand Down
14 changes: 12 additions & 2 deletions README.MD
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# life_tools

个人日常使用的小工具集合,按应用类型组织源码:命令行程序放在 `cli/`,插件程序放在 `emby_plugins/`,图形界面程序放在 `gui/`。
个人日常使用的小工具集合,按应用类型组织源码:命令行程序放在 `cli/`,独立插件放在 `plugins/`,Emby 插件放在 `emby_plugins/`,图形界面程序放在 `gui/`。

## 界面预览

Expand Down Expand Up @@ -58,6 +58,7 @@
| 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) |
| Experimental CLI | `codex_inspector` | `cli/codex_inspector/` | `codex_inspector` | 本机只读查看 Codex 会话、token 用量、活跃度和记忆内容 | [docs/cli/codex_inspector.md](docs/cli/codex_inspector.md) |
| Plugin | Alfred Remote Upload | `plugins/alfred_remote_upload/` | `Remote Upload.alfredworkflow` | 将剪贴板图片或 Finder 单文件上传到 SSH 主机,并复制远端路径 | [docs/plugins/alfred_remote_upload.md](docs/plugins/alfred_remote_upload.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) |

Expand All @@ -66,6 +67,7 @@
| 路径 | 内容 |
|---|---|
| `cli/` | 命令行工具源码。Go CLI 和 Python CLI 都放在这里。 |
| `plugins/` | 独立插件源码。Alfred Workflow 放在这里。 |
| `emby_plugins/` | 插件类应用源码。当前只有 Emby 字幕插件。 |
| `gui/` | GUI 应用源码。当前只有 InterviewTimer macOS App。 |
| `docs/cli/` | CLI 工具的设计、结构、流程和使用文档。 |
Expand Down Expand Up @@ -140,8 +142,16 @@ swift build --product InterviewTimerApp
./scripts/build_app.sh
```

Alfred Remote Upload 验证与打包:

```bash
plugins/alfred_remote_upload/tests/run.sh
plugins/alfred_remote_upload/tests/clipboard_integration.sh
plugins/alfred_remote_upload/build.sh
```

具体工具的测试和排障入口见各自详细文档。

## 发布

推送 `v*` tag 会通过 GitHub Actions 构建 GitHub Release 资产,包括 Linux/macOS Go 二进制包、`video_subtitle` 源码包、Emby 插件包和未签名的 `InterviewTimer.app` 包。Go 二进制包包含 `codex_inspector`,但它仍是实验工具,不进入默认安装清单。发布流程见 [docs/release.md](docs/release.md)。
推送 `v*` tag 会通过 GitHub Actions 构建 GitHub Release 资产,包括 Linux/macOS Go 二进制包、`video_subtitle` 源码包、Emby 插件包、Alfred Workflow 和未签名的 `InterviewTimer.app` 包。Go 二进制包包含 `codex_inspector`,但它仍是实验工具,不进入默认安装清单。发布流程见 [docs/release.md](docs/release.md)。
109 changes: 109 additions & 0 deletions docs/plugins/alfred_remote_upload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Alfred Remote Upload

`Remote Upload` 把 macOS 剪贴板中的单张图片或 Finder 单个普通文件上传到 SSH 主机,并把远端绝对路径写回剪贴板。典型用途是把本地截图上传到运行 Codex 的开发机,再把 `/home/user/tmp_share/20260714_1.png` 直接粘贴到对话中。

## 工作流程

```mermaid
flowchart TD
A["复制 Finder 单文件或截图到剪贴板"] --> B["打开 Alfred,输入 up"]
B --> C["按名称或 SSH Host 过滤"]
C --> D["选择目标主机"]
D --> E["SSH 分配路径并用 SCP 上传"]
E --> F["远端文件权限设为 0644"]
F --> G["远端绝对路径写回剪贴板"]
```

## 环境要求

| 依赖 | 要求 |
|---|---|
| macOS | 提供 JXA、`pbcopy` 和 OpenSSH |
| Alfred | Alfred 5 Powerpack,可运行 Workflow |
| SSH | OpenSSH 客户端支持 `scp -s`,Host key 已确认,密钥或 agent 可以无交互登录 |
| 远端 | Linux 或 macOS,启用 SFTP,并提供 POSIX `sh`、`mkdir`、`rm`、`test`、`chmod` |

Workflow 不读取密码,也不管理密钥。用户、端口、IdentityFile 和 ProxyJump 都继续配置在 `~/.ssh/config`。

## 构建与安装

在仓库根目录执行:

```bash
plugins/alfred_remote_upload/tests/run.sh
plugins/alfred_remote_upload/tests/clipboard_integration.sh
plugins/alfred_remote_upload/build.sh
open "plugins/alfred_remote_upload/dist/Remote Upload.alfredworkflow"
```

打开产物后由 Alfred 完成安装。升级 Workflow 时,Alfred Workflow Configuration 中的用户值独立保存,不会被仓库中的默认值覆盖。

## 配置

在 Alfred Preferences 的 `Remote Upload` Workflow 配置页填写:

| 变量 | 必填 | 默认值 | 规则 |
|---|---:|---:|---|
| `hosts_json` | 是 | `[]` | JSON 数组;每项包含唯一 `name`、安全 SSH `host` 别名和绝对 `remote_dir` |
| `max_size_mb` | 否 | `100` | 普通文件和图片共用的大小上限,单位 MiB,必须为正整数 |

完整示例:

```json
[
{
"name": "devbox",
"host": "devbox",
"remote_dir": "/home/jiangchaoqun.true/tmp_share"
},
{
"name": "devbox-test",
"host": "devbox",
"remote_dir": "/tmp/life_tools_alfred_remote_upload_e2e"
}
]
```

`host` 必须是 `~/.ssh/config` 中可直接使用的别名,不能写 `user@host`、端口参数或任意 shell 片段。可先验证无交互连接:

```bash
ssh -o BatchMode=yes -o StrictHostKeyChecking=yes -o ConnectTimeout=10 devbox true
```

## 使用方式

1. 在 Finder 中复制一个普通文件,或把一张图片放入剪贴板。
2. 打开 Alfred,输入 `up`;继续输入名称或 Host 可以过滤主机。
3. 选择目标主机并回车。
4. 收到成功通知后直接粘贴,剪贴板内容是远端绝对路径。

最近成功使用的主机会排在前面。未使用主机保持配置顺序;MRU 状态损坏时自动回退,不影响上传。

## 文件处理规则

| 场景 | 行为 |
|---|---|
| PNG、JPEG、GIF、WebP | 原样上传 |
| TIFF、HEIC、未知但可解码的图片 | 转换为 PNG |
| 截图命名 | `<remote_dir>/YYYYMMDD_N.ext`,跨后缀统一递增 |
| 普通文件命名 | `<remote_dir>/YYYYMMDD/原名.ext` |
| 普通文件重名 | 扩展名前追加 `_1`、`_2` |
| 上传成功 | 远端权限为 `0644`,剪贴板替换为远端绝对路径 |
| 上传失败 | 不修改剪贴板;上传或权限失败会清理远端残片 |

不支持目录、多文件、特殊文件、失效 Finder 路径、纯文本和文件名中的控制字符。

## 安全与排障

Workflow 强制非交互 SSH、严格 Host key 校验、10 秒连接超时,并用 `scp -s` 锁定 SFTP 协议,避免 legacy SCP 通过远端 shell 解释文件名。远端命名先占位再上传,已有文件不会被覆盖。

| 提示或现象 | 检查项 |
|---|---|
| `主机配置错误` | 用 JSON 校验器检查 `hosts_json`,确认字段齐全、名称唯一、目录是绝对路径 |
| `无法在远端创建上传路径` | 确认 `remote_dir` 的父目录可写、磁盘未满、远端提供 POSIX `sh` |
| `SCP 上传失败` | 直接运行 SSH 探测命令,检查密钥、端口、ProxyJump 和网络 |
| `远端权限设置失败` | 检查目标文件属主和远端文件系统是否允许 `chmod` |
| `远端文件已上传,但写入剪贴板失败` | 错误中包含已保留的远端路径;手动复制该路径,MRU 不会更新 |
| 纯文本剪贴板无法上传 | 这是明确边界;请复制 Finder 文件或图片数据 |

实现细节和失败语义见[设计文档](../specs/2026-07-14-alfred-remote-upload-design.md)。
Loading
Loading