diff --git a/HOW_TO_USE_EMAIL_REPLY.md b/HOW_TO_USE_EMAIL_REPLY.md new file mode 100644 index 0000000..69c0a77 --- /dev/null +++ b/HOW_TO_USE_EMAIL_REPLY.md @@ -0,0 +1,154 @@ +# TaskPing 邮件回复功能使用指南 + +## 📋 完整使用流程 + +### 步骤1:启动邮件监听服务 +在终端1中运行: +```bash +cd /Users/jessytsui/dev/TaskPing +npm run relay:pty +``` + +这会启动邮件监听服务,监听 `noreply@pandalla.ai` 收到的回复邮件。 + +### 步骤2:启动Claude Code并集成TaskPing +在终端2中运行: +```bash +# 启动Claude Code +claude + +# 在Claude Code中使用TaskPing发送邮件通知 +# 例如:当任务完成时会自动发送邮件 +``` + +### 步骤3:配置Claude Code钩子(如果还没配置) +在Claude Code中运行: +```bash +# 查看当前钩子配置 +cat ~/.config/claude-code/settings/hooks.json + +# 如果没有配置,需要设置TaskPing钩子 +# 复制TaskPing的钩子配置文件 +``` + +## 📧 邮件回复测试流程 + +### 方法1:手动测试发送邮件 +```bash +# 在TaskPing目录中运行 +node test-smtp-token.js +``` + +这会发送一封测试邮件到 `jiaxicui446@gmail.com`,邮件主题包含Token格式: +`[TaskPing #XXXXXXXX] Claude Code 任务完成 - TaskPing-Token-Test` + +### 方法2:实际集成测试 +1. 在Claude Code中执行一个任务 +2. 任务完成后,TaskPing会自动发送邮件通知 +3. 邮件会发送到配置的邮箱(`jiaxicui446@gmail.com`) + +## 💌 如何回复邮件发送命令 + +### 收到邮件后: +1. 在 `jiaxicui446@gmail.com` 收到邮件,主题如: + ``` + [TaskPing #A53PXR7F] Claude Code 任务完成 - 项目名 + ``` + +2. **直接回复邮件**,在正文中输入命令: + ``` + 继续优化代码 + ``` + 或 + ``` + 生成单元测试 + ``` + 或 + ``` + 解释这个函数的作用 + ``` + +3. 发送回复后,邮件监听服务会: + - 收到回复邮件 + - 提取Token(A53PXR7F) + - 找到对应的PTY会话 + - 将命令注入到Claude Code CLI + +## 🔧 配置文件说明 + +### .env 配置 +```env +# 发件配置(飞书邮箱) +SMTP_HOST=smtp.feishu.cn +SMTP_USER=noreply@pandalla.ai +SMTP_PASS=kKgS3tNReRTL3RQC + +# 收件配置(飞书邮箱) +IMAP_HOST=imap.feishu.cn +IMAP_USER=noreply@pandalla.ai +IMAP_PASS=kKgS3tNReRTL3RQC + +# 用户通知邮箱 +EMAIL_TO=jiaxicui446@gmail.com + +# 允许发送命令的邮箱(安全白名单) +ALLOWED_SENDERS=jiaxicui446@gmail.com +``` + +## 🐛 故障排除 + +### 1. 收不到邮件回复 +检查: +- 邮件监听服务是否正在运行(`npm run relay:pty`) +- 是否从白名单邮箱(`jiaxicui446@gmail.com`)发送回复 +- 邮件主题是否包含正确的Token格式 + +### 2. 命令没有注入到Claude Code +检查: +- Claude Code是否还在运行 +- PTY会话是否还有效(Token未过期) +- 检查服务日志输出 + +### 3. 查看调试日志 +```bash +# 查看详细的邮件监听日志 +DEBUG=true npm run relay:pty +``` + +## 📱 支持的邮件客户端 + +用户可以从任意邮箱回复到 `noreply@pandalla.ai`: +- ✅ Gmail 网页版/客户端 +- ✅ 手机Gmail APP +- ✅ Apple Mail +- ✅ Outlook +- ✅ QQ邮箱 +- ✅ 163邮箱 +- ✅ 任何支持SMTP的邮箱 + +## 🔒 安全特性 + +1. **Token验证**:每个会话有唯一Token,防止误操作 +2. **发件人白名单**:只有授权邮箱可以发送命令 +3. **会话过期**:Token有24小时有效期 +4. **命令过滤**:自动过滤潜在危险命令 + +## 🎯 实际使用场景 + +### 场景1:长时间构建 +``` +1. 在Claude Code中启动项目构建 +2. 离开电脑,在手机收到构建完成邮件 +3. 手机直接回复:"继续部署到生产环境" +4. 回到电脑时部署已完成 +``` + +### 场景2:代码审查 +``` +1. Claude Code完成代码生成 +2. 收到邮件通知 +3. 回复:"请添加单元测试和文档" +4. Claude自动生成测试和文档 +``` + +这样就可以实现真正的远程控制Claude Code了! \ No newline at end of file diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md deleted file mode 100644 index 7adf51d..0000000 --- a/PROJECT_STRUCTURE.md +++ /dev/null @@ -1,42 +0,0 @@ -# TaskPing 项目结构 - -## 📁 核心文件 - -### 🎯 主要脚本 -- **`hook-notify.js`** - 核心通知脚本,被Claude Code hooks调用 -- **`config-tool.js`** - 交互式配置管理工具 -- **`install.js`** - 自动安装脚本,配置Claude Code hooks - -### ⚙️ 配置文件 -- **`config.json`** - 用户配置(语言、音效、自定义消息等) -- **`i18n.json`** - 多语言文本 -- **`claude-hooks.json`** - Claude Code hooks配置模板 - -### 📚 文档 -- **`README.md`** - 完整项目文档 -- **`QUICKSTART.md`** - 快速开始指南 -- **`TaskPing.md`** - 产品规格文档 - -### 🎵 音效 -- **`sounds/`** - 自定义音效目录 - - 支持格式:`.wav`, `.mp3`, `.m4a`, `.aiff`, `.ogg` - - 用户可以添加自己的音效文件 - -### 📦 包管理 -- **`package.json`** - Node.js项目配置 -- **`LICENSE`** - MIT开源协议 - -## 🚀 使用流程 - -1. **安装**: `node install.js` -2. **配置**: `node config-tool.js` -3. **使用**: 正常使用Claude Code,自动收到通知 - -## 🔧 开发说明 - -- 主要语言:JavaScript (Node.js) -- 支持平台:macOS, Windows, Linux -- 核心依赖:无(使用Node.js内置模块) -- 音效播放:系统原生通知API - -项目专注于简洁、高效的Claude Code任务通知功能。 \ No newline at end of file diff --git a/PROJECT_STRUCTURE_NEW.md b/PROJECT_STRUCTURE_NEW.md deleted file mode 100644 index 6afe323..0000000 --- a/PROJECT_STRUCTURE_NEW.md +++ /dev/null @@ -1,106 +0,0 @@ -# TaskPing 新项目结构设计 - -## 📁 重构后的目录结构 - -``` -TaskPing/ -├── 📦 src/ # 源代码目录 -│ ├── 🎯 core/ # 核心模块 -│ │ ├── notifier.js # 通知核心类 -│ │ ├── config.js # 配置管理 -│ │ └── logger.js # 日志记录 -│ ├── 📢 channels/ # 通知渠道 -│ │ ├── base/ # 基础接口 -│ │ │ └── channel.js # 通知渠道基类 -│ │ ├── local/ # 本地通知 -│ │ │ └── desktop.js # 桌面通知 -│ │ ├── email/ # 邮件通知 -│ │ │ └── smtp.js # SMTP邮件 -│ │ ├── chat/ # 聊天平台 -│ │ │ ├── discord.js # Discord -│ │ │ ├── telegram.js # Telegram -│ │ │ ├── whatsapp.js # WhatsApp -│ │ │ └── feishu.js # 飞书 -│ │ └── webhook/ # Webhook通知 -│ │ └── generic.js # 通用Webhook -│ ├── 🔄 relay/ # 命令中继模块 -│ │ ├── server.js # 中继服务器 -│ │ ├── client.js # 中继客户端 -│ │ └── commands.js # 命令处理 -│ ├── 🛠️ tools/ # 工具模块 -│ │ ├── cli.js # 命令行工具 -│ │ ├── installer.js # 安装器 -│ │ └── config-manager.js # 配置管理器 -│ └── 🎵 assets/ # 静态资源 -│ └── sounds/ # 音效文件 -├── 📋 config/ # 配置文件 -│ ├── default.json # 默认配置 -│ ├── user.json # 用户配置 -│ ├── channels.json # 渠道配置 -│ └── templates/ # 配置模板 -│ ├── claude-hooks.json # Claude Code hooks -│ └── channel-templates/ # 各渠道配置模板 -├── 📚 docs/ # 文档目录 -│ ├── README.md # 主文档 -│ ├── QUICKSTART.md # 快速开始 -│ ├── api/ # API文档 -│ ├── channels/ # 各渠道使用指南 -│ └── examples/ # 使用示例 -├── 🧪 tests/ # 测试目录 -│ ├── unit/ # 单元测试 -│ ├── integration/ # 集成测试 -│ └── fixtures/ # 测试数据 -├── 📦 项目根文件 -│ ├── package.json # Node.js配置 -│ ├── taskping.js # 主入口文件 -│ ├── LICENSE # 开源协议 -│ └── .gitignore # Git忽略 -└── 🚀 scripts/ # 构建脚本 - ├── build.js # 构建脚本 - ├── dev.js # 开发脚本 - └── deploy.js # 部署脚本 -``` - -## 🏗️ 模块设计原则 - -### 1. 核心抽象 -- **NotificationChannel**: 所有通知渠道的基类 -- **CommandRelay**: 命令中继的统一接口 -- **ConfigManager**: 统一的配置管理 - -### 2. 插件化架构 -- 每个通知渠道独立模块 -- 支持动态加载和卸载 -- 标准化的接口和生命周期 - -### 3. 配置分离 -- 用户配置与默认配置分离 -- 渠道配置模块化 -- 敏感信息加密存储 - -## 🔄 工作流程 - -### Phase 1: 本地通知 (当前) -Claude Code → hook-notify.js → Desktop Notification - -### Phase 2: 多渠道通知 -Claude Code → Core Notifier → Channel Router → [Email|Discord|Telegram|...] - -### Phase 3: 命令中继 -Channel → Command Relay Server → Claude Code Client → Execute Command - -## 🎯 入口点设计 - -```bash -# 主命令 -taskping [command] [options] - -# 子命令 -taskping install # 安装和配置 -taskping config # 配置管理 -taskping test # 测试通知 -taskping serve # 启动中继服务 -taskping relay # 连接中继服务 -``` - -这个结构为未来的扩展提供了良好的基础。 \ No newline at end of file diff --git a/QUICKSTART.md b/QUICKSTART.md deleted file mode 100644 index c05f4f1..0000000 --- a/QUICKSTART.md +++ /dev/null @@ -1,74 +0,0 @@ -# TaskPing 快速开始指南 - -## 30秒快速设置 - -### 1️⃣ 安装 -```bash -node taskping.js install -# 按提示输入 'y' 确认安装 -``` - -### 2️⃣ 测试 -```bash -node taskping.js test -# 应该看到两个测试通知 -``` - -### 3️⃣ 使用 -```bash -claude -# 正常使用Claude Code,会自动收到通知 -``` - -## 工作原理 - -**TaskPing通过Claude Code的hooks机制工作:** - -- ✅ **任务完成时** → 桌面通知:"任务已完成,Claude正在等待下一步指令" -- ⏳ **等待输入时** → 桌面通知:"Claude需要您的进一步指导" - -## 实际效果 - -``` -你: 请帮我重构这个函数 -Claude: [分析代码中...] - -📱 通知: 任务已完成,Claude正在等待下一步指令 - -Claude: 我发现了3个改进点,你想看哪个? - -📱 通知: Claude需要您的进一步指导 -``` - -## 配置 - -```bash -# 打开配置菜单 -node taskping.js config - -# 查看当前配置 -node taskping.js config --show - -# 查看系统状态 -node taskping.js status - -# 可以调整: -# - 语言 (中文/英文/日文) -# - 提示音 -# - 启用/禁用通知 -``` - -## 故障排除 - -**收不到通知?** -- macOS: 在系统偏好设置中允许终端发送通知 -- Linux: 安装 `sudo apt-get install libnotify-bin` -- 测试: `node taskping.js test` - -**安装失败?** -- 确保Node.js版本 >= 14 -- 检查Claude Code配置目录权限 - ---- - -**就这么简单!现在你可以专心做其他事情,Claude完成任务时会主动通知你。** \ No newline at end of file diff --git a/README.md b/README.md index fa33e2f..0263570 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,41 @@ -# TaskPing - Claude Code 邮件自动化 +# TaskPing - 智能邮件自动化 Claude Code 助手 -TaskPing 是一个智能的邮件自动化工具,可以监听你的邮件回复,并将回复内容自动输入到 Claude Code 中执行。 +TaskPing 是一个智能的邮件自动化工具,实现了 Claude Code 与邮件系统的深度集成。通过监听邮件回复,自动将回复内容输入到对应的 Claude Code 会话中执行,让你可以在任何地方通过邮件远程控制 Claude Code。 -## 🚀 快速开始 +## 🚀 核心功能 -### 1. 配置邮箱 -```bash -npm run config -``` -按照提示配置你的邮箱信息(SMTP和IMAP)。 +### 📧 智能邮件通知 +- **自动检测**: 基于 Claude Code 官方 hooks 机制,自动识别任务完成和等待输入状态 +- **实时通知**: 任务完成时自动发送邮件,包含完整的用户问题和 Claude 回复内容 +- **会话绑定**: 邮件与特定的 tmux 会话绑定,确保回复到正确的 Claude Code 窗口 -### 2. 启动服务 -```bash -npm start -``` +### 🔄 邮件回复自动执行 +- **远程控制**: 直接回复邮件,内容自动输入到对应的 Claude Code 会话中 +- **智能注入**: 自动检测 tmux 会话状态,将命令精确注入到正确的窗口 +- **防重复处理**: 实现邮件去重机制,避免重复处理同一封邮件 -### 3. 使用流程 -1. 当 Claude Code 完成任务时,TaskPing 会发送邮件通知到你的邮箱 -2. 直接回复这封邮件,在邮件中写入你想让 Claude Code 执行的下一个命令 -3. TaskPing 会自动监听到你的回复,提取命令内容,并自动输入到 Claude Code 中 -4. 命令会自动执行,无需任何手动操作 - -## ✨ 核心特性 - -### 🎯 智能检测 -- 基于Claude Code官方hooks机制 -- 自动识别任务完成和等待输入状态 -- 无需手动监控,完全自动化 - -### 📢 多渠道通知 -- **桌面通知**:即时本地通知 -- **邮件通知**:远程邮件提醒 + 回复执行命令 -- 支持自定义通知声音和消息内容 -- 同时启用多个通知渠道 - -### 🏠 远程命令执行 -- **邮件回复**:直接回复邮件执行下一步命令 -- **自动化流程**:人不在电脑前也能继续对话 -- **安全机制**:会话过期、命令过滤、来源验证 - -### 🌍 跨平台支持 -- **macOS**:原生通知中心 + 系统提示音 -- **Windows**:Toast通知系统 -- **Linux**:libnotify桌面通知 - -### 🎛️ 灵活配置 -- 多语言支持(中文、英文、日文) -- 自定义提示音(支持系统音效) -- 邮件 SMTP/IMAP 配置 -- 可调节通知频率和超时时间 +### 🛡️ 稳定性保障 +- **单实例运行**: 确保只有一个邮件监听进程运行,避免重复处理 +- **状态管理**: 完善的会话状态跟踪和错误恢复机制 +- **安全验证**: 邮件来源验证,确保只处理授权用户的回复 ## 📦 快速安装 -### 自动安装(推荐) - +### 1. 克隆项目 ```bash -# 1. 克隆或下载项目 -git clone +git clone https://github.com/your-username/TaskPing.git cd TaskPing - -# 2. 运行安装脚本 -node taskping.js install - -# 3. 按提示完成配置 -# 安装器会自动配置Claude Code的hooks设置 +npm install ``` -### 手动安装 - +### 2. 配置邮箱 ```bash -# 1. 测试通知功能 -node taskping.js test - -# 2. 配置Claude Code -# 将以下内容添加到 ~/.claude/settings.json 的 "hooks" 部分: +npm run config ``` +按照提示配置你的邮箱信息(SMTP 和 IMAP)。 + +### 3. 配置 Claude Code 钩子 +将以下内容添加到 `~/.claude/settings.json` 的 `hooks` 部分: ```json { @@ -99,266 +60,155 @@ node taskping.js test } ``` +### 4. 全局安装 claude-control 命令 +```bash +node install-global.js +``` + +### 5. 启动邮件监听服务 +```bash +npm run relay:pty +``` + ## 🎮 使用方法 -### 基本使用 - -安装完成后,TaskPing会自动工作: - +### 1. 创建 Claude Code 会话 ```bash -# 1. 正常启动Claude Code -claude - -# 2. 执行任务 -> 请帮我重构这个项目的代码结构 - -# 3. 当Claude完成任务时,你会收到通知: -# 📱 "任务已完成,Claude正在等待下一步指令" - -# 4. 当Claude需要你的输入时,你会收到提醒: -# 📱 "Claude需要您的进一步指导" +# 在任何目录下都可以运行 +claude-control --session project-name ``` -### 配置管理 +### 2. 正常使用 Claude Code +在 tmux 会话中正常与 Claude 对话: +``` +> 请帮我分析这个项目的代码结构 -```bash -# 启动配置工具 -node taskping.js config - -# 快速查看当前配置 -node taskping.js config --show - -# 查看系统状态 -node taskping.js status - -# 测试通知功能 -node taskping.js test - -# 启动邮件命令中继服务 (NEW!) -node taskping.js relay start +Claude 回复... ``` -## 🔧 配置选项 +### 3. 自动邮件通知 +当 Claude 完成任务时,你会收到包含完整对话内容的邮件通知。 -### 语言设置 -- `zh-CN`:简体中文 -- `en`:英语 -- `ja`:日语 - -### 提示音选择(macOS) -- `Glass`:清脆玻璃音(推荐用于任务完成) -- `Tink`:轻柔提示音(推荐用于等待输入) -- `Ping`、`Pop`、`Basso` 等系统音效 - -### 基础配置示例 - -```json -{ - "language": "zh-CN", - "sound": { - "completed": "Glass", - "waiting": "Tink" - }, - "enabled": true, - "timeout": 5 -} +### 4. 邮件回复控制 +直接回复邮件,输入下一个指令: +``` +请继续优化代码性能 ``` -### 邮件配置示例 +### 5. 自动执行 +你的回复会自动注入到对应的 Claude Code 会话中并执行。 -```json -{ - "email": { - "enabled": true, - "config": { - "smtp": { - "host": "smtp.gmail.com", - "port": 587, - "secure": false, - "auth": { - "user": "your-email@gmail.com", - "pass": "your-app-password" - } - }, - "imap": { - "host": "imap.gmail.com", - "port": 993, - "secure": true - }, - "from": "TaskPing ", - "to": "your-email@gmail.com" - } - } -} -``` - -**📧 邮件功能设置指南**: 查看 [邮件功能详细指南](docs/EMAIL_GUIDE.md) 了解完整的配置和使用方法。 - -## 💡 实际应用场景 - -### 🏗️ 代码重构项目 -``` -你:请帮我重构这个React组件,提高性能 -Claude:开始分析组件结构... -📱 通知:任务完成! -Claude:我找到了3个优化方案,你倾向于哪种? -📱 通知:Claude需要您的进一步指导 -``` - -### 📚 文档生成 -``` -你:为这个API生成完整的文档 -Claude:正在分析API接口... -📱 通知:任务完成! -Claude:文档已生成,需要我添加使用示例吗? -📱 通知:Claude需要您的进一步指导 -``` - -### 🐛 Bug调试 -``` -你:帮我找出这个内存泄漏的原因 -Claude:开始深度分析代码... -📱 通知:任务完成! -Claude:发现了2个可能的原因,需要查看哪个文件? -📱 通知:Claude需要您的进一步指导 -``` - -### 📧 远程邮件工作流程 -``` -1. 你在家里:启动 Claude Code 任务 -2. 你出门了:📧 收到邮件 "任务完成,Claude等待下一步指令" -3. 在路上:回复邮件 "请继续优化代码性能" -4. 自动执行:命令自动在你的电脑上执行 -5. 再次收到:📧 "优化完成" 邮件通知 -6. 继续回复:进行下一步操作 - -真正实现远程 AI 编程!🚀 -``` - -## 🛠️ 故障排除 - -### macOS权限问题 -如果收不到通知,请检查: -1. 打开"系统偏好设置" → "安全性与隐私" → "隐私" -2. 选择"通知",确保终端应用有权限 -3. 或者在"系统偏好设置" → "通知"中启用终端通知 - -### Linux依赖缺失 -```bash -# Ubuntu/Debian -sudo apt-get install libnotify-bin - -# Fedora/RHEL -sudo dnf install libnotify - -# Arch Linux -sudo pacman -S libnotify -``` - -### Windows执行策略 -```powershell -# 如果遇到PowerShell执行策略限制 -Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -``` - -### 测试通知 -```bash -# 测试所有通知渠道 -node taskping.js test - -# 手动发送通知 -node taskping.js notify --type completed -node taskping.js notify --type waiting - -# 查看系统状态 -node taskping.js status -``` - -## 📊 项目结构 +## 🔧 项目架构 ``` TaskPing/ -├── 📄 README.md # 项目文档 -├── 🚀 taskping.js # 主要CLI入口 -├── 📋 TaskPing.md # 产品规格文档 -├── 📦 package.json # 项目依赖 -├── config/ # 配置文件目录 -│ ├── defaults/ # 默认配置模板 -│ ├── user.json # 用户个人配置 -│ └── channels.json # 通知渠道配置 -├── src/ # 核心源代码 -│ ├── core/ # 核心模块 -│ │ ├── config.js # 配置管理器 -│ │ ├── logger.js # 日志系统 -│ │ └── notifier.js # 通知编排器 -│ ├── channels/ # 通知渠道实现 -│ │ ├── local/ # 本地通知 -│ │ ├── email/ # 邮件通知 -│ │ └── chat/ # 聊天应用通知 -│ ├── tools/ # 管理工具 -│ │ ├── installer.js # 安装器 -│ │ └── config-manager.js # 配置管理器 -│ └── assets/ # 静态资源 -└── docs/ # 文档目录 +├── src/ +│ ├── channels/email/ +│ │ └── smtp.js # SMTP 邮件发送 +│ ├── core/ +│ │ ├── config.js # 配置管理 +│ │ ├── logger.js # 日志系统 +│ │ └── notifier.js # 通知协调器 +│ ├── data/ +│ │ ├── session-map.json # 会话映射表 +│ │ └── processed-messages.json # 已处理邮件记录 +│ ├── relay/ +│ │ └── relay-pty.js # 邮件监听和 PTY 注入服务 +│ └── utils/ +│ └── tmux-monitor.js # Tmux 会话监控 +├── taskping.js # 主入口文件 +├── claude-control.js # Claude Code 会话管理 +├── start-relay-pty.js # 邮件监听服务启动器 +└── install-global.js # 全局安装脚本 ``` -## 🔮 发展规划 +## 🛠️ 核心技术实现 -TaskPing按照产品规格文档分阶段开发: +### 邮件监听与处理 +- 使用 `node-imap` 监听 IMAP 邮箱新邮件 +- 实现邮件去重机制(基于 UID、messageId 和内容哈希) +- 异步事件处理,避免竞态条件 -### ✅ Phase 1 - 本地通知MVP(已完成) -- 本地桌面通知 -- Claude Code hooks集成 -- 基础配置管理 +### 会话管理 +- Tmux 会话自动检测和命令注入 +- 会话状态持久化存储 +- 支持多会话并发处理 -### ✅ Phase 2 - 邮件通知和远程执行(已完成) -- 📧 邮件通知功能 -- 🔄 邮件回复命令执行 -- 🔒 安全会话管理 -- 🛠️ 命令中继服务 +### 通知系统 +- 自动捕获当前 tmux 会话的用户问题和 Claude 回复 +- 生成包含完整对话内容的邮件通知 +- 支持多种通知渠道(桌面通知、邮件等) -### 🚧 Phase 3 - 多渠道通知(规划中) -- Telegram/Discord/WhatsApp/飞书集成 -- 移动端推送通知 -- 多渠道命令中继 +## 🔍 故障排除 -### 🌟 Phase 4 - 企业级功能(未来) -- 团队协作功能 -- 用户权限管理 -- 审计日志 -- API 接口 +### 邮件重复处理问题 +确保只运行一个邮件监听进程: +```bash +# 检查运行状态 +ps aux | grep relay-pty + +# 停止所有进程 +pkill -f relay-pty + +# 重新启动 +npm run relay:pty +``` + +### 命令注入失败 +检查 tmux 会话状态: +```bash +# 查看所有会话 +tmux list-sessions + +# 检查会话内容 +tmux capture-pane -t session-name -p +``` + +### 邮件配置问题 +测试邮件连接: +```bash +# 测试 SMTP +node -e " +const config = require('./config/user.json'); +console.log('SMTP Config:', config.email.config.smtp); +" + +# 测试 IMAP +node -e " +const config = require('./config/user.json'); +console.log('IMAP Config:', config.email.config.imap); +" +``` + +## 🎯 使用场景 + +### 远程编程工作流 +1. 在办公室启动一个 Claude Code 代码审查任务 +2. 下班回家,收到邮件"代码审查完成,发现3个问题" +3. 回复邮件"请修复第一个问题" +4. Claude 自动开始修复,完成后再次发送邮件通知 +5. 继续回复邮件进行下一步操作 + +### 长时间任务监控 +1. 启动大型项目重构任务 +2. Claude 分步骤完成各个模块 +3. 每个阶段完成都发送邮件通知进度 +4. 通过邮件回复指导下一步方向 ## 🤝 贡献指南 -欢迎参与TaskPing的开发! - -### 如何贡献 -1. Fork本项目 +1. Fork 本项目 2. 创建功能分支:`git checkout -b feature/new-feature` 3. 提交更改:`git commit -am 'Add new feature'` 4. 推送分支:`git push origin feature/new-feature` -5. 提交Pull Request - -### 开发环境 -- Node.js >= 14.0.0 -- 支持macOS、Linux、Windows开发 +5. 提交 Pull Request ## 📄 许可证 -本项目采用 [MIT License](LICENSE) 开源协议。 - -## 💬 联系我们 - -- 🐛 **问题反馈**:[提交Issue](https://github.com/TaskPing/TaskPing/issues) -- 💡 **功能建议**:[Discussion](https://github.com/TaskPing/TaskPing/discussions) -- 📧 **邮件联系**:contact@taskping.dev +本项目采用 MIT License 开源协议。 --- -
+**让 Claude Code 工作流程更加智能高效!** -**让Claude Code工作流程更加智能高效!** - -⭐ 如果这个项目对你有帮助,请给我们一个Star! - -
\ No newline at end of file +如果这个项目对你有帮助,请给我们一个 ⭐! \ No newline at end of file diff --git a/TaskPing.md b/TaskPing.md deleted file mode 100644 index bc6fff8..0000000 --- a/TaskPing.md +++ /dev/null @@ -1,199 +0,0 @@ -# Claude Code Notify Assistant – Product Specification - -## 1  Purpose & Vision - -Provide developers with an **event‑driven companion** for Claude‑powered CLI sessions. When a long‑running task finishes, the assistant instantly notifies the user on their preferred channel and lets them **reply from mobile to trigger the next command**, achieving a seamless *desktop↔mobile↔AI* loop. - ---- - -## 2  Problem Statement - -CLI workflows often involve scripts that run for minutes or hours. Users must poll the terminal or stay near the computer, wasting time and focus. Existing notification tools are channel‑specific or lack bi‑directional control, and none are optimised for Claude Code agents. - ---- - -## 3  Solution Overview - -1. **CLI Hook**: A tiny cross‑platform wrapper (`claude-notify run `) that executes any shell command, streams logs to the backend, and raises a `TaskFinished` event when exit‑code ≠ “running”. -2. **Notification Orchestrator (backend)**: Consumes events, applies user quota/business rules, and fan‑outs messages via pluggable channel adapters. -3. **Interactive Relay**: Converts user replies (e.g., from Telegram) into authenticated API calls that queue the next CLI command on the origin machine through a persistent WebSocket tunnel. -4. **Usage Metering**: Tracks daily quota (3 e‑mails for free tier) and subscription status (Stripe webhook). - ---- - -## 4  Personas & User Journeys - -| Persona | Primary Goal | Typical Flow | -| -------------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -| **Indie Dev – Free** | Compile & test large project remotely | 1) `claude-notify run make` → 2) E‑mail sent on finish → 3) Next morning sees mail; quota resets | -| **Startup Engineer – Pro** | Continuous fine‑tuning jobs on GPU VM | 1) Task completes → 2) Telegram alert with success log snippet → 3) Replies `launch next.sh` from subway → 4) Claude Code executes instantly | -| **Ops Lead – Pro** | Batch data pipelines | 1) Weekly cron triggers job → 2) Feishu group ping with status + link to dashboard | - ---- - -## 5  Feature Matrix - -| Area | Free | Pro (USD 4.9/mo) | -| ------------------------ | ----------- | ------------------------------------------------ | -| Daily notification quota | 3 | Unlimited | -| Channels | E‑mail | E‑mail, Discord, Telegram, SMS, Feishu, Webhooks | -| Command Relay | — | ✅ | -| Notification Templates | Default | Custom markdown & rich‑link cards | -| Log Attachment | 10 KB tail | Full log (configurable) | -| Team Workspaces | — | Up to 5 members | -| SLA | Best effort | 99.9 % | - ---- - -## 6  System Architecture - -``` -+-----------+ HTTPS / WSS +-----------------+ -| CLI Agent | <──────────▶ API Gateway ───────────▶ | Auth Service | -| (Go/Rust)| +-----------------+ -| |── stream logs ─▶ Event Queue (NATS) ──▶ Task Processor -+-----------+ │(Python Worker) - ▲ │ │ - │ SSH/WebSocket │ │ - │ ▼ ▼ - │ +-----------------+ +-----------------+ - │ | Notification | | Usage / Billing| - └── receive command ◀── Relay ◀──────┤ Fan‑out | | (Postgres + | - +-----------------+ | Stripe) | -``` - -**Tech choices** - -- **CLI Agent**: Rust binary, \~3 MB, single‑file, auto‑updates (GitHub Releases). -- **Backend**: FastAPI + Celery, Redis broker, Postgres store. -- **Channel Adapters**: Modular; each implements `send(message: Notification) -> DeliveryResult`. -- **Security**: JWT (CLI) + signed HMAC Webhook secrets; all data in‑flight TLS 1.2+. -- **Scaling**: Horizontal via Kubernetes; stateless workers. - ---- - -## 7  API Contracts (simplified) - -```http -POST /v1/tasks -{ - "cmd": "python train.py", - "session_id": "abc", - "notify_on": "exit", // or "partial", "custom_regex" - "channels": ["email", "tg"] -} -→ 201 Created {"task_id":"t123"} - -POST /v1/commands -Authorization: Bearer -{ - "session_id": "abc", - "command": "git pull && make test" -} -→ 202 Accepted -``` - -*Complete OpenAPI spec in appendix.* - ---- - -## 8  Data Model (Postgres) - -```sql -CREATE TABLE users ( - id UUID PK, email TEXT UNIQUE, plan TEXT, quota_used INT, … -); -CREATE TABLE tasks ( - id UUID PK, user_id FK, session_id TEXT, cmd TEXT, status TEXT, - started_at TIMESTAMPTZ, finished_at TIMESTAMPTZ, log_url TEXT, … -); -CREATE TABLE notifications ( - id UUID PK, task_id FK, channel TEXT, delivered BOOL, … -); -``` - ---- - -## 9  Quota & Pricing Logic - -```text -If plan == "free": - limit daily_sent_email <= 3 - reject other channel -Else if plan == "pro": - no channel limit - fair‑use: 30 SMS / day default -``` - -Billing via **Stripe Billing Portal**; pro‑rated upgrades. - ---- - -## 10  On‑boarding Flow - -1. OAuth (GitHub) or E‑mail link signup. -2. Download CLI (`curl -sL https://cli.claude‑notify.sh | bash`). -3. `claude-notify login ` – stores token locally. -4. Configure channels in Web UI (Discord bot auth, etc.). -5. First task run offers interactive tour. - ---- - -## 11  Operational & Security Considerations - -- **Secrets**: Only hash task logs > 10 MB for storage; encrypt full logs (AES‑256‑GCM) at rest. -- **Abuse prevention**: Per‑minute rate limits on incoming mobile commands; banlist keywords. -- **Observability**: Prometheus metrics, Grafana dashboard; Sentry for exceptions. -- **Compliance**: GDPR + China PIPL data residency via multi‑region storage. - ---- - -## 11a Implementation Phases - -### Phase 1 – Local Desktop Notification MVP - -- **Scope**: Notify on local machine only. -- **Trigger**: CLI Agent detects task exit and calls OS‑native notification helper. -- **OS Support**: - - macOS: `terminal-notifier` or AppleScript. - - Windows: Toast via `SnoreToast`. - - Linux: `libnotify`. -- **Customization**: `--title`, `--message`, `--sound=/path/to/file` flags; sensible defaults provided. -- **Offline & Privacy**: No network calls; runs entirely locally. -- **Quota**: Unlimited (cloud quota applies in later phases). -- **Future‑Proofing**: Uses the same `TaskFinished` event schema as the cloud orchestrator, enabling a smooth upgrade path. - -### Phase 2 – Cloud Notification Service (E‑mail, Telegram, Discord) - -- Add backend Notification Orchestrator, channel adapters, and basic auth. -- Implement daily quota enforcement and Pro billing (Stripe). - -### Phase 3 – Mobile Command Relay & Team Features - -- Enable interactive replies that queue new commands via WebSocket tunnel. -- Introduce Workspaces, role permissions, and activity audit logs. - ---- - -## 12 Roadmap - -| Quarter | Milestone | -| ------- | ---------------------------------------------------------- | -|  Q3‑25 | MVP (E‑mail + Telegram) / Invite‑only beta | -|  Q4‑25 | Payments, Discord & Feishu adapters / Public launch | -|  Q1‑26 | Mobile app (Flutter) push notifications / Team workspaces | -|  Q2‑26 | Marketplace for community adapters (e.g., Slack, WhatsApp) | - ---- - -## 13  Open Questions - -1. Support for streaming partial logs? -2. Granular role permissions for team plans. -3. Enterprise SSO & on‑prem backend – worth pursuing? -4. Automatic Claude Code context carry‑over between sequential commands. - ---- - -*© 2025 Panda Villa Tech Limited – Internal draft – v0.9* - diff --git a/claude-control.js b/claude-control.js new file mode 100644 index 0000000..bb60b43 --- /dev/null +++ b/claude-control.js @@ -0,0 +1,287 @@ +#!/usr/bin/env node + +/** + * TaskPing 无人值守远程控制设置助手 + */ + +const { exec, spawn } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +class RemoteControlSetup { + constructor(sessionName = null) { + this.sessionName = sessionName || 'claude-taskping'; + this.taskpingHome = this.findTaskPingHome(); + } + + findTaskPingHome() { + // If TASKPING_HOME environment variable is set, use it + if (process.env.TASKPING_HOME) { + return process.env.TASKPING_HOME; + } + + // If running from the TaskPing directory, use current directory + if (fs.existsSync(path.join(__dirname, 'package.json'))) { + const packagePath = path.join(__dirname, 'package.json'); + try { + const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')); + if (packageJson.name && packageJson.name.includes('taskping')) { + return __dirname; + } + } catch (e) { + // Continue searching + } + } + + // Search for TaskPing in common locations + const commonPaths = [ + path.join(process.env.HOME, 'dev', 'TaskPing'), + path.join(process.env.HOME, 'Projects', 'TaskPing'), + path.join(process.env.HOME, 'taskping'), + __dirname // fallback to current script directory + ]; + + for (const searchPath of commonPaths) { + if (fs.existsSync(searchPath) && fs.existsSync(path.join(searchPath, 'package.json'))) { + try { + const packageJson = JSON.parse(fs.readFileSync(path.join(searchPath, 'package.json'), 'utf8')); + if (packageJson.name && packageJson.name.toLowerCase().includes('taskping')) { + return searchPath; + } + } catch (e) { + // Continue searching + } + } + } + + // If not found, use current directory as fallback + return __dirname; + } + + async setup() { + console.log('🚀 TaskPing 无人值守远程控制设置\n'); + console.log('🎯 目标: 人在外面用手机→家中电脑Claude Code自动执行命令\n'); + + try { + // 1. 检查tmux + await this.checkAndInstallTmux(); + + // 2. 检查Claude CLI + await this.checkClaudeCLI(); + + // 3. 设置Claude tmux会话 + await this.setupClaudeSession(); + + // 4. 会话创建完成 + console.log('\n4️⃣ 会话创建完成'); + + // 5. 提供使用指南 + this.showUsageGuide(); + + } catch (error) { + console.error('❌ 设置过程中发生错误:', error.message); + } + } + + async checkAndInstallTmux() { + console.log('1️⃣ 检查tmux安装状态...'); + + return new Promise((resolve) => { + exec('which tmux', (error, stdout) => { + if (error) { + console.log('❌ tmux未安装'); + console.log('📦 正在安装tmux...'); + + exec('brew install tmux', (installError, installStdout, installStderr) => { + if (installError) { + console.log('❌ tmux安装失败,请手动安装:'); + console.log(' brew install tmux'); + console.log(' 或从 https://github.com/tmux/tmux 下载'); + } else { + console.log('✅ tmux安装成功'); + } + resolve(); + }); + } else { + console.log(`✅ tmux已安装: ${stdout.trim()}`); + resolve(); + } + }); + }); + } + + async checkClaudeCLI() { + console.log('\n2️⃣ 检查Claude CLI状态...'); + + return new Promise((resolve) => { + exec('which claude', (error, stdout) => { + if (error) { + console.log('❌ Claude CLI未找到'); + console.log('📦 请安装Claude CLI:'); + console.log(' npm install -g @anthropic-ai/claude-code'); + } else { + console.log(`✅ Claude CLI已安装: ${stdout.trim()}`); + + // 检查版本 + exec('claude --version', (versionError, versionStdout) => { + if (!versionError) { + console.log(`📋 版本: ${versionStdout.trim()}`); + } + }); + } + resolve(); + }); + }); + } + + async setupClaudeSession() { + console.log('\n3️⃣ 设置Claude tmux会话...'); + + return new Promise((resolve) => { + // 检查是否已有会话 + exec(`tmux has-session -t ${this.sessionName} 2>/dev/null`, (checkError) => { + if (!checkError) { + console.log('⚠️ Claude tmux会话已存在'); + console.log('🔄 是否重新创建会话? (会杀死现有会话)'); + + // 简单起见,直接重建 + this.killAndCreateSession(resolve); + } else { + this.createNewSession(resolve); + } + }); + }); + } + + killAndCreateSession(resolve) { + exec(`tmux kill-session -t ${this.sessionName} 2>/dev/null`, () => { + setTimeout(() => { + this.createNewSession(resolve); + }, 1000); + }); + } + + createNewSession(resolve) { + // 使用TaskPing主目录作为工作目录 + const workingDir = this.taskpingHome; + const command = `tmux new-session -d -s ${this.sessionName} -c "${workingDir}" clauderun`; + + console.log(`🚀 创建Claude tmux会话: ${this.sessionName}`); + console.log(`📁 工作目录: ${workingDir}`); + console.log(`💡 使用便捷命令: clauderun (等同于 claude --dangerously-skip-permissions)`); + + exec(command, (error, stdout, stderr) => { + if (error) { + console.log(`❌ 会话创建失败: ${error.message}`); + if (stderr) { + console.log(`错误详情: ${stderr}`); + } + // 如果clauderun失败,尝试使用完整路径命令 + console.log('🔄 尝试使用完整路径命令...'); + const fallbackCommand = `tmux new-session -d -s ${this.sessionName} -c "${workingDir}" /Users/jessytsui/.nvm/versions/node/v18.17.0/bin/claude --dangerously-skip-permissions`; + exec(fallbackCommand, (fallbackError) => { + if (fallbackError) { + console.log(`❌ 完整路径命令也失败: ${fallbackError.message}`); + } else { + console.log('✅ Claude tmux会话创建成功 (使用完整路径)'); + console.log(`📺 查看会话: tmux attach -t ${this.sessionName}`); + console.log(`🔚 退出会话: Ctrl+B, D (不会关闭Claude)`); + } + resolve(); + }); + } else { + console.log('✅ Claude tmux会话创建成功'); + console.log(`📺 查看会话: tmux attach -t ${this.sessionName}`); + console.log(`🔚 退出会话: Ctrl+B, D (不会关闭Claude)`); + resolve(); + } + }); + } + + async testRemoteInjection() { + console.log('\n💡 会话已就绪,可以开始使用'); + console.log('📋 Claude Code正在等待您的指令'); + console.log('🔧 如需测试注入功能,请使用单独的测试脚本'); + return Promise.resolve(); + } + + showUsageGuide() { + console.log('\n🎉 设置完成!无人值守远程控制已就绪\n'); + + console.log('🎯 新功能: clauderun 便捷命令'); + console.log(' 现在可以使用 clauderun 代替 claude --dangerously-skip-permissions'); + console.log(' 更清晰的Claude Code启动方式\n'); + + console.log('📋 使用流程:'); + console.log('1. 🏠 在家启动邮件监听: npm run relay:pty'); + console.log('2. 🚪 出门时Claude继续在tmux中运行'); + console.log('3. 📱 手机收到TaskPing邮件通知'); + console.log('4. 💬 手机回复邮件输入命令'); + console.log('5. 🤖 家中Claude自动接收并执行命令'); + console.log('6. 🔄 循环上述过程,完全无人值守\n'); + + console.log('🔧 管理命令:'); + console.log(` 查看Claude会话: tmux attach -t ${this.sessionName}`); + console.log(` 退出会话(不关闭): Ctrl+B, D`); + console.log(` 杀死会话: tmux kill-session -t ${this.sessionName}`); + console.log(` 查看所有会话: tmux list-sessions\n`); + + console.log('🎛️ 多会话支持:'); + console.log(' 创建自定义会话: node claude-control.js --session my-project'); + console.log(' 创建多个会话: node claude-control.js --session frontend'); + console.log(' node claude-control.js --session backend'); + console.log(' 邮件回复会自动路由到对应的会话\n'); + + console.log('📱 邮件测试:'); + console.log(' Token将包含会话信息,自动路由到正确的tmux会话'); + console.log(' 收件邮箱: jiaxicui446@gmail.com'); + console.log(' 回复邮件输入: echo "远程控制测试"\n'); + + console.log('🚨 重要提醒:'); + console.log('- Claude会话在tmux中持续运行,断网重连也不会中断'); + console.log('- 邮件监听服务需要保持运行状态'); + console.log('- 家中电脑需要保持开机和网络连接'); + console.log('- 手机可以从任何地方发送邮件命令'); + console.log('- 支持同时运行多个不同项目的Claude会话\n'); + + console.log('✅ 现在你可以实现真正的无人值守远程控制了!🎯'); + } + + // 快速重建会话的方法 + async quickRestart() { + console.log('🔄 快速重启Claude会话...'); + + return new Promise((resolve) => { + this.killAndCreateSession(() => { + console.log('✅ Claude会话已重启'); + resolve(); + }); + }); + } +} + +// 命令行参数处理 +if (require.main === module) { + const args = process.argv.slice(2); + + // 解析会话名称参数 + let sessionName = null; + const sessionIndex = args.indexOf('--session'); + if (sessionIndex !== -1 && args[sessionIndex + 1]) { + sessionName = args[sessionIndex + 1]; + } + + const setup = new RemoteControlSetup(sessionName); + + if (sessionName) { + console.log(`🎛️ 使用自定义会话名称: ${sessionName}`); + } + + if (args.includes('--restart')) { + setup.quickRestart(); + } else { + setup.setup(); + } +} + +module.exports = RemoteControlSetup; \ No newline at end of file diff --git a/debug-email.js b/debug-email.js deleted file mode 100755 index 907aa5b..0000000 --- a/debug-email.js +++ /dev/null @@ -1,494 +0,0 @@ -#!/usr/bin/env node - -/** - * TaskPing 邮件调试工具 - * 用于调试邮件监听和自动化问题 - */ - -const Imap = require('node-imap'); -const { simpleParser } = require('mailparser'); -const { spawn } = require('child_process'); -const fs = require('fs'); -const path = require('path'); - -class DebugEmailAutomation { - constructor() { - this.configPath = path.join(__dirname, 'config/channels.json'); - this.config = null; - this.imap = null; - } - - async start() { - console.log('🔍 TaskPing 邮件调试工具启动\n'); - - // 1. 检查配置 - console.log('📋 1. 检查配置文件...'); - if (!this.loadConfig()) { - return; - } - console.log('✅ 配置文件加载成功'); - console.log(`📧 IMAP服务器: ${this.config.imap.host}:${this.config.imap.port}`); - console.log(`👤 用户: ${this.config.imap.auth.user}`); - console.log(`📬 通知发送到: ${this.config.to}\n`); - - // 2. 测试IMAP连接 - console.log('🔌 2. 测试IMAP连接...'); - try { - await this.testConnection(); - console.log('✅ IMAP连接成功\n'); - } catch (error) { - console.error('❌ IMAP连接失败:', error.message); - return; - } - - // 3. 检查最近邮件 - console.log('📧 3. 检查最近邮件...'); - try { - await this.checkRecentEmails(); - } catch (error) { - console.error('❌ 检查邮件失败:', error.message); - } - - // 4. 开始实时监听 - console.log('\n👂 4. 开始实时监听邮件回复...'); - console.log('💌 现在可以回复TaskPing邮件来测试自动化功能'); - console.log('🔍 调试信息会实时显示\n'); - - this.startRealTimeListening(); - } - - loadConfig() { - try { - const data = fs.readFileSync(this.configPath, 'utf8'); - const config = JSON.parse(data); - - if (!config.email?.enabled) { - console.error('❌ 邮件功能未启用'); - console.log('💡 请运行: npm run config'); - return false; - } - - this.config = config.email.config; - return true; - } catch (error) { - console.error('❌ 配置文件读取失败:', error.message); - console.log('💡 请运行: npm run config'); - return false; - } - } - - async testConnection() { - return new Promise((resolve, reject) => { - this.imap = new Imap({ - user: this.config.imap.auth.user, - password: this.config.imap.auth.pass, - host: this.config.imap.host, - port: this.config.imap.port, - tls: this.config.imap.secure, - connTimeout: 30000, - authTimeout: 15000, - // debug: console.log // 暂时禁用调试 - }); - - this.imap.once('ready', () => { - console.log('🔗 IMAP ready事件触发'); - resolve(); - }); - - this.imap.once('error', (error) => { - console.error('🔗 IMAP error事件:', error.message); - reject(error); - }); - - this.imap.connect(); - }); - } - - async checkRecentEmails() { - return new Promise((resolve, reject) => { - this.imap.openBox('INBOX', true, (err, box) => { - if (err) { - reject(err); - return; - } - - console.log(`📫 收件箱状态: 总邮件 ${box.messages.total}, 未读 ${box.messages.unseen}`); - - // 查找最近1小时的所有邮件 - const since = new Date(); - since.setHours(since.getHours() - 1); - - console.log(`🔍 搜索 ${since.toLocaleString()} 之后的邮件...`); - - this.imap.search([['SINCE', since]], (searchErr, results) => { - if (searchErr) { - reject(searchErr); - return; - } - - console.log(`📨 找到 ${results.length} 封最近邮件`); - - if (results.length === 0) { - console.log('ℹ️ 没有找到最近的邮件'); - resolve(); - return; - } - - // 获取最近几封邮件的详情 - const fetch = this.imap.fetch(results.slice(-3), { - bodies: 'HEADER', - struct: true - }); - - fetch.on('message', (msg, seqno) => { - console.log(`\n📧 邮件 ${seqno}:`); - - msg.on('body', (stream, info) => { - let buffer = ''; - stream.on('data', (chunk) => { - buffer += chunk.toString('utf8'); - }); - stream.once('end', () => { - const lines = buffer.split('\n'); - const subject = lines.find(line => line.startsWith('Subject:')); - const from = lines.find(line => line.startsWith('From:')); - const date = lines.find(line => line.startsWith('Date:')); - - console.log(` 📄 ${subject || 'Subject: (未知)'}`); - console.log(` 👤 ${from || 'From: (未知)'}`); - console.log(` 📅 ${date || 'Date: (未知)'}`); - - if (subject && subject.includes('[TaskPing]')) { - console.log(' 🎯 这是TaskPing邮件!'); - } - }); - }); - }); - - fetch.once('end', () => { - resolve(); - }); - - fetch.once('error', (fetchErr) => { - reject(fetchErr); - }); - }); - }); - }); - } - - startRealTimeListening() { - // 重新连接用于监听 - this.imap.end(); - - setTimeout(() => { - this.connectForListening(); - }, 1000); - } - - async connectForListening() { - this.imap = new Imap({ - user: this.config.imap.auth.user, - password: this.config.imap.auth.pass, - host: this.config.imap.host, - port: this.config.imap.port, - tls: this.config.imap.secure, - connTimeout: 60000, - authTimeout: 30000, - keepalive: true - }); - - this.imap.once('ready', () => { - console.log('✅ 监听连接建立'); - this.openInboxForListening(); - }); - - this.imap.once('error', (error) => { - console.error('❌ 监听连接错误:', error.message); - }); - - this.imap.once('end', () => { - console.log('🔄 连接断开,尝试重连...'); - setTimeout(() => this.connectForListening(), 5000); - }); - - this.imap.connect(); - } - - openInboxForListening() { - this.imap.openBox('INBOX', false, (err, box) => { - if (err) { - console.error('❌ 打开收件箱失败:', err.message); - return; - } - - console.log('📬 收件箱已打开,开始监听新邮件...'); - - // 设置定期检查 - setInterval(() => { - this.checkNewEmails(); - }, 10000); // 每10秒检查一次 - - // 立即检查一次 - this.checkNewEmails(); - }); - } - - checkNewEmails() { - const since = new Date(); - since.setMinutes(since.getMinutes() - 5); // 检查最近5分钟的邮件 - - this.imap.search([['UNSEEN'], ['SINCE', since]], (err, results) => { - if (err) { - console.error('🔍 搜索新邮件失败:', err.message); - return; - } - - if (results.length > 0) { - console.log(`\n🚨 发现 ${results.length} 封新邮件!`); - this.processNewEmails(results); - } - }); - } - - processNewEmails(emailUids) { - const fetch = this.imap.fetch(emailUids, { - bodies: '', - markSeen: true - }); - - fetch.on('message', (msg, seqno) => { - console.log(`\n📨 处理新邮件 ${seqno}:`); - let buffer = ''; - - msg.on('body', (stream) => { - stream.on('data', (chunk) => { - buffer += chunk.toString('utf8'); - }); - - stream.once('end', async () => { - try { - const parsed = await simpleParser(buffer); - await this.analyzeEmail(parsed, seqno); - } catch (error) { - console.error(`❌ 解析邮件 ${seqno} 失败:`, error.message); - } - }); - }); - }); - } - - async analyzeEmail(email, seqno) { - console.log(`📧 邮件 ${seqno} 分析:`); - console.log(` 📄 主题: ${email.subject || '(无主题)'}`); - console.log(` 👤 发件人: ${email.from?.text || '(未知)'}`); - console.log(` 📅 时间: ${email.date || '(未知)'}`); - - // 检查是否是TaskPing回复 - const isTaskPingReply = this.isTaskPingReply(email); - console.log(` 🎯 TaskPing回复: ${isTaskPingReply ? '是' : '否'}`); - - if (!isTaskPingReply) { - console.log(` ⏭️ 跳过非TaskPing邮件`); - return; - } - - // 提取命令 - const command = this.extractCommand(email); - console.log(` 💬 邮件内容长度: ${(email.text || '').length} 字符`); - console.log(` 🎯 提取的命令: "${command || '(无)'}"`); - - if (!command || command.trim().length === 0) { - console.log(` ⚠️ 未找到有效命令`); - return; - } - - console.log(`\n🚀 准备执行命令...`); - await this.executeCommand(command, seqno); - } - - isTaskPingReply(email) { - const subject = email.subject || ''; - return subject.includes('[TaskPing]') || - subject.match(/^(Re:|RE:|回复:)/i); - } - - extractCommand(email) { - let text = email.text || ''; - console.log(` 📝 原始邮件文本:\n${text.substring(0, 200)}${text.length > 200 ? '...' : ''}`); - - const lines = text.split('\n'); - const commandLines = []; - - for (const line of lines) { - if (line.includes('-----Original Message-----') || - line.includes('--- Original Message ---') || - line.includes('在') && line.includes('写道:') || - line.includes('On') && line.includes('wrote:') || - line.match(/^>\s*/) || - line.includes('会话ID:')) { - console.log(` ✂️ 在此行停止解析: ${line.substring(0, 50)}`); - break; - } - - if (line.includes('--') || - line.includes('Sent from') || - line.includes('发自我的')) { - console.log(` ✂️ 跳过签名行: ${line.substring(0, 50)}`); - break; - } - - commandLines.push(line); - } - - const extractedCommand = commandLines.join('\n').trim(); - console.log(` 🎯 清理后的命令:\n"${extractedCommand}"`); - return extractedCommand; - } - - async executeCommand(command, seqno) { - console.log(`🤖 执行命令 (来自邮件 ${seqno}):`); - console.log(`📝 命令内容: "${command}"`); - - try { - // 1. 复制到剪贴板 - console.log(`📋 1. 复制到剪贴板...`); - const clipboardSuccess = await this.copyToClipboard(command); - console.log(`📋 剪贴板: ${clipboardSuccess ? '✅ 成功' : '❌ 失败'}`); - - // 2. 尝试自动化 - console.log(`🤖 2. 尝试自动输入...`); - const automationSuccess = await this.attemptAutomation(command); - console.log(`🤖 自动化: ${automationSuccess ? '✅ 成功' : '❌ 失败'}`); - - // 3. 发送通知 - console.log(`🔔 3. 发送通知...`); - const notificationSuccess = await this.sendNotification(command); - console.log(`🔔 通知: ${notificationSuccess ? '✅ 成功' : '❌ 失败'}`); - - if (automationSuccess) { - console.log(`\n🎉 邮件命令已自动执行到Claude Code!`); - } else { - console.log(`\n⚠️ 自动化失败,但命令已复制到剪贴板`); - console.log(`💡 请手动在Claude Code中粘贴 (Cmd+V)`); - } - - } catch (error) { - console.error(`❌ 执行命令失败:`, error.message); - } - } - - async copyToClipboard(command) { - return new Promise((resolve) => { - const pbcopy = spawn('pbcopy'); - pbcopy.stdin.write(command); - pbcopy.stdin.end(); - - pbcopy.on('close', (code) => { - resolve(code === 0); - }); - - pbcopy.on('error', () => resolve(false)); - }); - } - - async attemptAutomation(command) { - return new Promise((resolve) => { - const escapedCommand = command - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/'/g, "\\'"); - - const script = ` - tell application "System Events" - set claudeApps to {"Claude", "Claude Code", "Claude Desktop"} - set devApps to {"Terminal", "iTerm2", "iTerm", "Visual Studio Code", "Code"} - set targetApp to null - set appName to "" - - -- 查找Claude应用 - repeat with app in claudeApps - try - if application process app exists then - set targetApp to application process app - set appName to app - exit repeat - end if - end try - end repeat - - -- 如果没找到Claude,查找开发工具 - if targetApp is null then - repeat with app in devApps - try - if application process app exists then - set targetApp to application process app - set appName to app - exit repeat - end if - end try - end repeat - end if - - if targetApp is not null then - set frontmost of targetApp to true - delay 1 - - keystroke "a" using command down - delay 0.3 - keystroke "${escapedCommand}" - delay 0.5 - keystroke return - - return "success:" & appName - else - return "no_app" - end if - end tell - `; - - console.log(`🍎 执行AppleScript自动化...`); - - const osascript = spawn('osascript', ['-e', script]); - let output = ''; - - osascript.stdout.on('data', (data) => { - output += data.toString().trim(); - }); - - osascript.on('close', (code) => { - console.log(`🍎 AppleScript结果: 退出码=${code}, 输出="${output}"`); - const success = code === 0 && output.startsWith('success:'); - if (success) { - const appName = output.split(':')[1]; - console.log(`🍎 成功输入到应用: ${appName}`); - } - resolve(success); - }); - - osascript.on('error', (error) => { - console.log(`🍎 AppleScript错误: ${error.message}`); - resolve(false); - }); - }); - } - - async sendNotification(command) { - const shortCommand = command.length > 50 ? command.substring(0, 50) + '...' : command; - - const script = ` - display notification "邮件命令: ${shortCommand.replace(/"/g, '\\"')}" with title "TaskPing Debug" sound name "default" - `; - - const osascript = spawn('osascript', ['-e', script]); - - return new Promise((resolve) => { - osascript.on('close', (code) => resolve(code === 0)); - osascript.on('error', () => resolve(false)); - }); - } -} - -// 启动调试工具 -const debugTool = new DebugEmailAutomation(); -debugTool.start().catch(console.error); \ No newline at end of file diff --git a/diagnose-automation.js b/diagnose-automation.js deleted file mode 100755 index 876c584..0000000 --- a/diagnose-automation.js +++ /dev/null @@ -1,380 +0,0 @@ -#!/usr/bin/env node - -/** - * 自动化功能诊断工具 - * 详细检测和诊断自动粘贴功能的问题 - */ - -const { spawn } = require('child_process'); - -class AutomationDiagnostic { - constructor() { - this.tests = []; - } - - async runDiagnostic() { - console.log('🔍 自动化功能诊断工具\n'); - - if (process.platform !== 'darwin') { - console.log('❌ 此诊断工具仅适用于 macOS'); - return; - } - - // 运行所有测试 - await this.testBasicAppleScript(); - await this.testSystemEventsAccess(); - await this.testKeystrokePermission(); - await this.testApplicationDetection(); - await this.testClipboardAccess(); - await this.testDetailedPermissions(); - - // 显示总结 - this.showSummary(); - await this.provideSolutions(); - } - - async testBasicAppleScript() { - console.log('1. 📋 测试基本 AppleScript 执行...'); - try { - const result = await this.runAppleScript('return "AppleScript works"'); - if (result === 'AppleScript works') { - console.log(' ✅ 基本 AppleScript 执行正常'); - this.tests.push({ name: 'AppleScript', status: 'pass' }); - } else { - console.log(' ❌ AppleScript 返回异常结果'); - this.tests.push({ name: 'AppleScript', status: 'fail', error: 'Unexpected result' }); - } - } catch (error) { - console.log(' ❌ AppleScript 执行失败:', error.message); - this.tests.push({ name: 'AppleScript', status: 'fail', error: error.message }); - } - console.log(''); - } - - async testSystemEventsAccess() { - console.log('2. 🖥️ 测试 System Events 访问...'); - try { - const script = ` - tell application "System Events" - return name of first application process whose frontmost is true - end tell - `; - const result = await this.runAppleScript(script); - if (result && result !== 'permission_denied') { - console.log(` ✅ 可以访问 System Events,当前前台应用: ${result}`); - this.tests.push({ name: 'System Events', status: 'pass', data: result }); - } else { - console.log(' ❌ System Events 访问被拒绝'); - this.tests.push({ name: 'System Events', status: 'fail', error: 'Access denied' }); - } - } catch (error) { - console.log(' ❌ System Events 访问失败:', error.message); - this.tests.push({ name: 'System Events', status: 'fail', error: error.message }); - } - console.log(''); - } - - async testKeystrokePermission() { - console.log('3. ⌨️ 测试按键发送权限...'); - try { - const script = ` - tell application "System Events" - try - keystroke "test" without key down - return "keystroke_success" - on error errorMessage - return "keystroke_failed: " & errorMessage - end try - end tell - `; - const result = await this.runAppleScript(script); - if (result === 'keystroke_success') { - console.log(' ✅ 按键发送权限正常'); - this.tests.push({ name: 'Keystroke', status: 'pass' }); - } else { - console.log(` ❌ 按键发送失败: ${result}`); - this.tests.push({ name: 'Keystroke', status: 'fail', error: result }); - } - } catch (error) { - console.log(' ❌ 按键测试异常:', error.message); - this.tests.push({ name: 'Keystroke', status: 'fail', error: error.message }); - } - console.log(''); - } - - async testApplicationDetection() { - console.log('4. 🔍 测试应用程序检测...'); - try { - const script = ` - tell application "System Events" - set appList to {"Terminal", "iTerm2", "iTerm", "Visual Studio Code", "Code", "Cursor", "Claude Code"} - set foundApps to {} - repeat with appName in appList - try - if application process appName exists then - set foundApps to foundApps & {appName} - end if - end try - end repeat - return foundApps as string - end tell - `; - const result = await this.runAppleScript(script); - if (result) { - console.log(` ✅ 检测到以下应用: ${result}`); - this.tests.push({ name: 'App Detection', status: 'pass', data: result }); - } else { - console.log(' ⚠️ 未检测到目标应用程序'); - this.tests.push({ name: 'App Detection', status: 'warn', error: 'No target apps found' }); - } - } catch (error) { - console.log(' ❌ 应用检测失败:', error.message); - this.tests.push({ name: 'App Detection', status: 'fail', error: error.message }); - } - console.log(''); - } - - async testClipboardAccess() { - console.log('5. 📋 测试剪贴板访问...'); - try { - // 测试写入剪贴板 - await this.setClipboard('test_clipboard_content'); - const content = await this.getClipboard(); - - if (content.includes('test_clipboard_content')) { - console.log(' ✅ 剪贴板读写正常'); - this.tests.push({ name: 'Clipboard', status: 'pass' }); - } else { - console.log(' ❌ 剪贴板内容不匹配'); - this.tests.push({ name: 'Clipboard', status: 'fail', error: 'Content mismatch' }); - } - } catch (error) { - console.log(' ❌ 剪贴板访问失败:', error.message); - this.tests.push({ name: 'Clipboard', status: 'fail', error: error.message }); - } - console.log(''); - } - - async testDetailedPermissions() { - console.log('6. 🔐 详细权限检查...'); - - try { - // 检查当前运行的进程 - const whoami = await this.runCommand('whoami'); - console.log(` 👤 当前用户: ${whoami}`); - - // 检查终端应用 - const terminal = process.env.TERM_PROGRAM || 'Unknown'; - console.log(` 💻 终端程序: ${terminal}`); - - // 检查是否在 IDE 中运行 - const isInIDE = process.env.VSCODE_PID || process.env.CURSOR_SESSION_ID || process.env.JB_IDE_PID; - if (isInIDE) { - console.log(' 🔧 检测到在 IDE 中运行'); - } - - // 尝试获取更详细的权限信息 - const script = ` - tell application "System Events" - try - set frontApp to name of first application process whose frontmost is true - set allApps to name of every application process - return "Front: " & frontApp & ", All: " & (count of allApps) - on error errorMsg - return "Error: " & errorMsg - end try - end tell - `; - - const permResult = await this.runAppleScript(script); - console.log(` 📊 权限测试结果: ${permResult}`); - - this.tests.push({ - name: 'Detailed Permissions', - status: 'info', - data: { user: whoami, terminal, result: permResult } - }); - - } catch (error) { - console.log(' ❌ 详细检查失败:', error.message); - this.tests.push({ name: 'Detailed Permissions', status: 'fail', error: error.message }); - } - console.log(''); - } - - showSummary() { - console.log('📊 诊断结果总结:\n'); - - const passed = this.tests.filter(t => t.status === 'pass').length; - const failed = this.tests.filter(t => t.status === 'fail').length; - const warned = this.tests.filter(t => t.status === 'warn').length; - - this.tests.forEach(test => { - const icon = test.status === 'pass' ? '✅' : - test.status === 'fail' ? '❌' : - test.status === 'warn' ? '⚠️' : 'ℹ️'; - console.log(`${icon} ${test.name}`); - if (test.error) { - console.log(` 错误: ${test.error}`); - } - if (test.data) { - console.log(` 数据: ${test.data}`); - } - }); - - console.log(`\n📈 总计: ${passed} 通过, ${failed} 失败, ${warned} 警告\n`); - } - - async provideSolutions() { - const keystrokeTest = this.tests.find(t => t.name === 'Keystroke'); - const systemEventsTest = this.tests.find(t => t.name === 'System Events'); - - console.log('💡 解决方案建议:\n'); - - if (keystrokeTest && keystrokeTest.status === 'fail') { - console.log('🔧 按键发送问题解决方案:'); - console.log(' 1. 打开 系统偏好设置 > 安全性与隐私 > 隐私 > 辅助功能'); - console.log(' 2. 移除并重新添加你的终端应用 (Terminal/iTerm2/VS Code)'); - console.log(' 3. 确保勾选框已被选中'); - console.log(' 4. 重启终端应用'); - console.log(''); - } - - if (systemEventsTest && systemEventsTest.status === 'fail') { - console.log('🔧 System Events 访问问题解决方案:'); - console.log(' 1. 检查 系统偏好设置 > 安全性与隐私 > 隐私 > 自动化'); - console.log(' 2. 确保你的终端应用下勾选了 "System Events"'); - console.log(' 3. 如果没有看到你的应用,先运行一次自动化脚本触发权限请求'); - console.log(''); - } - - console.log('🚀 额外建议:'); - console.log(' • 尝试完全退出并重启终端应用'); - console.log(' • 在 Terminal 中运行而不是在 IDE 集成终端中'); - console.log(' • 检查是否有安全软件阻止自动化'); - console.log(' • 尝试在不同的终端应用中运行测试'); - - const readline = require('readline'); - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); - - const answer = await this.question(rl, '\n是否尝试一个简单的修复测试?(y/n): '); - - if (answer.toLowerCase() === 'y') { - await this.runSimpleFixTest(); - } - - rl.close(); - } - - async runSimpleFixTest() { - console.log('\n🔨 运行简单修复测试...'); - - try { - // 尝试最基本的自动化 - const script = ` - display dialog "TaskPing 自动化测试" with title "权限测试" buttons {"确定"} default button 1 giving up after 3 - `; - - await this.runAppleScript(script); - console.log('✅ 基本对话框测试成功'); - - } catch (error) { - console.log('❌ 基本测试失败:', error.message); - } - } - - async runAppleScript(script) { - return new Promise((resolve, reject) => { - const osascript = spawn('osascript', ['-e', script]); - let output = ''; - let error = ''; - - osascript.stdout.on('data', (data) => { - output += data.toString(); - }); - - osascript.stderr.on('data', (data) => { - error += data.toString(); - }); - - osascript.on('close', (code) => { - if (code === 0) { - resolve(output.trim()); - } else { - reject(new Error(error || `Exit code: ${code}`)); - } - }); - }); - } - - async runCommand(command) { - return new Promise((resolve, reject) => { - const proc = spawn('sh', ['-c', command]); - let output = ''; - - proc.stdout.on('data', (data) => { - output += data.toString(); - }); - - proc.on('close', (code) => { - if (code === 0) { - resolve(output.trim()); - } else { - reject(new Error(`Command failed with exit code ${code}`)); - } - }); - }); - } - - async setClipboard(text) { - return new Promise((resolve, reject) => { - const pbcopy = spawn('pbcopy'); - pbcopy.stdin.write(text); - pbcopy.stdin.end(); - - pbcopy.on('close', (code) => { - if (code === 0) { - resolve(); - } else { - reject(new Error('Failed to set clipboard')); - } - }); - }); - } - - async getClipboard() { - return new Promise((resolve, reject) => { - const pbpaste = spawn('pbpaste'); - let output = ''; - - pbpaste.stdout.on('data', (data) => { - output += data.toString(); - }); - - pbpaste.on('close', (code) => { - if (code === 0) { - resolve(output); - } else { - reject(new Error('Failed to get clipboard')); - } - }); - }); - } - - question(rl, prompt) { - return new Promise(resolve => { - rl.question(prompt, resolve); - }); - } -} - -// 运行诊断 -if (require.main === module) { - const diagnostic = new AutomationDiagnostic(); - diagnostic.runDiagnostic().catch(console.error); -} - -module.exports = AutomationDiagnostic; \ No newline at end of file diff --git a/docs/EMAIL_ARCHITECTURE.md b/docs/EMAIL_ARCHITECTURE.md deleted file mode 100644 index c8cadd9..0000000 --- a/docs/EMAIL_ARCHITECTURE.md +++ /dev/null @@ -1,207 +0,0 @@ -# TaskPing 邮件功能架构设计 - -## 功能概述 - -实现邮件通知和远程命令执行功能,用户可以: -1. 接收 Claude Code 任务完成的邮件通知 -2. 通过回复邮件来远程执行下一步命令 -3. 在不坐在电脑前的情况下继续 Claude Code 对话 - -## 整体架构 - -``` -┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ -│ Claude Code │ ──▶│ TaskPing CLI │ ──▶│ Email Channel │ -│ (hooks) │ │ │ │ (SMTP Send) │ -└─────────────────┘ └──────────────────┘ └─────────────────┘ - │ - ▼ - ┌──────────────────┐ ┌─────────────────┐ - │ Command Relay │◀───│ Email Listener │ - │ Service │ │ (IMAP Receive) │ - └──────────────────┘ └─────────────────┘ - │ - ▼ - ┌──────────────────┐ - │ Claude Code │ - │ (stdin input) │ - └──────────────────┘ -``` - -## 核心组件 - -### 1. 邮件通知渠道 (Email Channel) -- **位置**: `src/channels/email/smtp.js` -- **功能**: 发送任务完成通知邮件 -- **特性**: - - 支持多种 SMTP 配置 - - 邮件模板化 - - 会话 ID 生成和嵌入 - - 安全回复指令 - -### 2. 邮件监听器 (Email Listener) -- **位置**: `src/relay/email-listener.js` -- **功能**: 监听和解析邮件回复 -- **特性**: - - IMAP/POP3 邮件接收 - - 邮件解析和命令提取 - - 会话 ID 验证 - - 安全检查 - -### 3. 命令中继服务 (Command Relay Service) -- **位置**: `src/relay/command-relay.js` -- **功能**: 管理命令队列和执行 -- **特性**: - - 会话管理 - - 命令队列 - - Claude Code 集成 - - 安全验证 - -### 4. 会话管理器 (Session Manager) -- **位置**: `src/relay/session-manager.js` -- **功能**: 管理 Claude Code 会话状态 -- **特性**: - - 会话创建和跟踪 - - 超时管理 - - 状态持久化 - -## 数据流程 - -### 发送通知流程 -1. Claude Code 触发 hook → TaskPing CLI -2. TaskPing 创建会话 ID 和通知 -3. 邮件渠道发送包含会话信息的邮件 -4. 会话管理器记录会话状态 - -### 命令执行流程 -1. 用户回复邮件 → 邮件监听器接收 -2. 解析邮件内容和会话 ID -3. 验证会话有效性和安全性 -4. 将命令加入执行队列 -5. 命令中继服务执行命令 - -## 邮件模板设计 - -### 通知邮件模板 -``` -主题: [TaskPing] Claude Code 任务完成 - {project} - -{user_name},您好! - -Claude Code 已完成任务,正在等待您的下一步指令。 - -项目: {project} -时间: {timestamp} -状态: {status} - -要继续对话,请直接回复此邮件,在邮件正文中输入您的指令。 - -示例回复: -"请继续优化代码" -"生成单元测试" -"解释这个函数的作用" - ---- -会话ID: {session_id} -安全提示: 请勿转发此邮件,会话将在24小时后过期 -``` - -### 回复解析规则 -- 提取邮件正文作为 Claude Code 指令 -- 忽略邮件签名和引用内容 -- 验证会话 ID 有效性 -- 检查指令安全性 - -## 安全机制 - -### 1. 会话验证 -- 唯一会话 ID (UUID v4) -- 24小时过期时间 -- 用户邮箱验证 - -### 2. 命令安全 -- 危险命令黑名单 -- 长度限制 (< 1000 字符) -- 特殊字符过滤 - -### 3. 频率限制 -- 每会话最多 10 次命令 -- 每小时最多 5 个新会话 -- 异常行为检测 - -## 配置结构 - -### 邮件配置 (config/channels.json) -```json -{ - "email": { - "type": "email", - "enabled": true, - "config": { - "smtp": { - "host": "smtp.gmail.com", - "port": 587, - "secure": false, - "auth": { - "user": "your-email@gmail.com", - "pass": "your-app-password" - } - }, - "imap": { - "host": "imap.gmail.com", - "port": 993, - "secure": true, - "auth": { - "user": "your-email@gmail.com", - "pass": "your-app-password" - } - }, - "from": "TaskPing ", - "to": "your-email@gmail.com", - "template": { - "subject": "[TaskPing] Claude Code 任务完成 - {{project}}", - "checkInterval": 30 - } - } - } -} -``` - -### 中继配置 (config/relay.json) -```json -{ - "enabled": true, - "security": { - "sessionTimeout": 86400, - "maxCommandsPerSession": 10, - "maxSessionsPerHour": 5, - "commandMaxLength": 1000 - }, - "claudeCode": { - "detectMethod": "ps", - "inputMethod": "stdin" - } -} -``` - -## 实现计划 - -1. **Phase 1**: 邮件发送功能 - - 实现 SMTP 邮件渠道 - - 创建邮件模板系统 - - 配置管理界面 - -2. **Phase 2**: 邮件接收功能 - - IMAP 邮件监听器 - - 邮件解析和命令提取 - - 会话管理器 - -3. **Phase 3**: 命令中继功能 - - 命令队列系统 - - Claude Code 集成 - - 安全验证机制 - -4. **Phase 4**: 测试和优化 - - 端到端测试 - - 错误处理完善 - - 性能优化 \ No newline at end of file diff --git a/docs/EMAIL_ARCHITECTURE_FINAL.md b/docs/EMAIL_ARCHITECTURE_FINAL.md deleted file mode 100644 index 11ab821..0000000 --- a/docs/EMAIL_ARCHITECTURE_FINAL.md +++ /dev/null @@ -1,145 +0,0 @@ -# TaskPing 邮件架构说明 - -## 📧 正确的邮件流程 - -### 1. 服务端配置(TaskPing 系统) - -``` -发送邮箱: noreply@pandalla.ai (飞书邮箱) -接收邮箱: noreply@pandalla.ai (飞书邮箱) -``` - -**用途**: -- 发送通知邮件给用户 -- 接收用户的回复命令 -- 处理邮件并注入到 Claude Code CLI - -### 2. 用户端配置 - -``` -通知接收邮箱: jiaxicui446@gmail.com (可配置为任意邮箱) -回复目标邮箱: noreply@pandalla.ai -``` - -**用途**: -- 接收 TaskPing 的任务通知 -- 从任意邮箱回复命令到服务端 - -## 🔄 邮件流程图 - -``` -1. 任务通知流程: - TaskPing 系统 → noreply@pandalla.ai → jiaxicui446@gmail.com - -2. 命令回复流程: - 用户邮箱(任意) → noreply@pandalla.ai → TaskPing 系统 → Claude Code CLI -``` - -## 📱 支持的用户邮箱 - -用户可以从以下**任意邮箱**发送回复到 `noreply@pandalla.ai`: - -- ✅ Gmail (`jiaxicui446@gmail.com`) -- ✅ QQ邮箱 (`xxx@qq.com`) -- ✅ 163邮箱 (`xxx@163.com`) -- ✅ Outlook (`xxx@outlook.com`) -- ✅ 企业邮箱 (`xxx@company.com`) -- ✅ 任何支持SMTP的邮箱 - -## 🔧 配置文件 - -### .env 配置 - -```env -# 发件配置(飞书邮箱) -SMTP_HOST=smtp.feishu.cn -SMTP_USER=noreply@pandalla.ai -SMTP_PASS=kKgS3tNReRTL3RQC - -# 收件配置(飞书邮箱) -IMAP_HOST=imap.feishu.cn -IMAP_USER=noreply@pandalla.ai -IMAP_PASS=kKgS3tNReRTL3RQC - -# 用户通知邮箱(可配置) -EMAIL_TO=jiaxicui446@gmail.com - -# 白名单(可配置多个用户邮箱) -ALLOWED_SENDERS=jiaxicui446@gmail.com -``` - -## 📝 使用方法 - -### 1. 接收通知 -用户在 `jiaxicui446@gmail.com` 收到类似邮件: -``` -发件人: TaskPing 通知系统 -主题: [TaskPing #ABC123] 任务等待您的指示 -内容: 任务详情... -``` - -### 2. 发送命令 -用户可以: - -**方式1:直接回复** -- 直接回复邮件 -- 输入命令,如:`继续执行` - -**方式2:新邮件** -- 从任意邮箱发送到 `noreply@pandalla.ai` -- 主题包含:`[TaskPing #ABC123]` -- 内容为命令 - -### 3. 系统处理 -1. 飞书邮箱接收用户回复 -2. TaskPing 解析命令 -3. 通过 PTY 注入到 Claude Code -4. 任务继续执行 - -## 🔒 安全特性 - -1. **发件人验证**:只有白名单中的邮箱可以发送命令 -2. **Token验证**:邮件主题必须包含有效的会话Token -3. **命令过滤**:自动过滤危险命令 -4. **会话过期**:Token有时间限制 -5. **去重处理**:防止重复执行同一命令 - -## 🚀 优势 - -1. **统一管理**:服务端使用单一邮箱管理 -2. **用户灵活**:用户可用任意邮箱接收和回复 -3. **简单配置**:只需配置一个飞书邮箱 -4. **多用户支持**:可配置多个用户邮箱到白名单 -5. **跨平台**:支持所有邮件客户端 - -## 📊 实际场景 - -### 场景1:移动办公 -``` -1. 开发者在电脑上运行 Claude Code 构建项目 -2. 离开电脑,在手机 Gmail 收到构建完成通知 -3. 直接在手机回复:"继续部署" -4. 电脑上的 Claude Code 自动执行部署命令 -``` - -### 场景2:团队协作 -``` -1. 团队领导 leader@company.com 收到项目通知 -2. 从企业邮箱回复:"批准发布" -3. 系统自动执行发布流程 -4. 所有团队成员收到发布完成通知 -``` - -### 场景3:多设备同步 -``` -1. 在办公室电脑启动长时间任务 -2. 回家路上用个人 QQ 邮箱收到通知 -3. 在家用 163 邮箱发送下一步指令 -4. 办公室电脑自动执行,第二天查看结果 -``` - -这种架构实现了: -- 服务端邮箱统一管理 -- 用户邮箱灵活配置 -- 多邮箱品牌支持 -- 简单可靠的通信机制 \ No newline at end of file diff --git a/docs/EMAIL_GUIDE.md b/docs/EMAIL_GUIDE.md deleted file mode 100644 index d943b25..0000000 --- a/docs/EMAIL_GUIDE.md +++ /dev/null @@ -1,346 +0,0 @@ -# TaskPing 邮件功能使用指南 - -## 🌟 功能概述 - -TaskPing 现在支持邮件通知和远程命令执行功能,让您可以: - -1. **📧 接收邮件通知** - 当 Claude Code 任务完成时,自动发送邮件通知 -2. **🔄 远程命令执行** - 通过回复邮件来远程执行 Claude Code 命令 -3. **🏠 真正的远程工作** - 即使不在电脑前,也能继续与 Claude Code 对话 - -## 🚀 快速开始 - -### 步骤 1: 配置邮件设置 - -```bash -# 启动配置管理器 -node taskping.js config - -# 选择 "3. 通知渠道" -# 然后选择 "2. 邮件通知" -``` - -### 步骤 2: 输入邮箱配置 - -以 Gmail 为例: - -``` -SMTP 主机: smtp.gmail.com -SMTP 端口: 587 -使用 SSL/TLS: n (使用 STARTTLS) -SMTP 用户名: your-email@gmail.com -SMTP 密码: your-app-password # 需要使用应用密码 - -IMAP 主机: imap.gmail.com -IMAP 端口: 993 -IMAP 使用 SSL: y - -收件人邮箱: your-email@gmail.com -发件人显示名: TaskPing -``` - -### 步骤 3: 测试邮件发送 - -配置完成后,选择测试邮件发送功能,您应该会收到一封测试邮件。 - -### 步骤 4: 启动命令中继服务 - -```bash -# 启动邮件命令中继服务 -node taskping.js relay start -``` - -## 📋 详细配置指南 - -### Gmail 配置 - -1. **启用两步验证** - - 登录 Google 账户 - - 进入"安全"设置 - - 启用两步验证 - -2. **生成应用密码** - - 在 Google 账户安全设置中 - - 选择"应用密码" - - 选择"邮件"和您的设备 - - 复制生成的 16 位密码 - -3. **启用 IMAP** - - 登录 Gmail - - 进入设置 → 转发和POP/IMAP - - 启用 IMAP 访问 - -### Outlook/Hotmail 配置 - -``` -SMTP 主机: smtp.live.com -SMTP 端口: 587 -IMAP 主机: imap-mail.outlook.com -IMAP 端口: 993 -``` - -### 其他邮箱提供商 - -| 提供商 | SMTP 主机 | SMTP 端口 | IMAP 主机 | IMAP 端口 | -|--------|-----------|-----------|-----------|-----------| -| QQ邮箱 | smtp.qq.com | 587 | imap.qq.com | 993 | -| 163邮箱 | smtp.163.com | 587 | imap.163.com | 993 | -| 126邮箱 | smtp.126.com | 587 | imap.126.com | 993 | - -## 🔄 使用流程 - -### 1. 正常工作流程 - -``` -1. 启动 Claude Code -2. 执行任务 (如: "帮我重构这个组件") -3. 📧 收到邮件通知: "任务完成" -4. 💬 回复邮件: "请添加单元测试" -5. ⚡ 命令自动在 Claude Code 中执行 -``` - -### 2. 邮件通知示例 - -当 Claude Code 完成任务时,您会收到如下邮件: - -``` -主题: [TaskPing] Claude Code 任务完成 - MyProject - -🎉 Claude Code 任务完成 - -项目: MyProject -时间: 2025-07-12 19:45:30 -状态: 任务完成 - -消息: 任务已完成,Claude正在等待下一步指令 - -💡 如何继续对话 -要继续与 Claude Code 对话,请直接回复此邮件,在邮件正文中输入您的指令。 - -示例回复: -• "请继续优化代码" -• "生成单元测试" -• "解释这个函数的作用" - -会话ID: 123e4567-e89b-12d3-a456-426614174000 -🔒 安全提示: 请勿转发此邮件,会话将在24小时后自动过期 -``` - -### 3. 回复邮件执行命令 - -直接回复邮件,在正文中输入要执行的命令: - -``` -请添加错误处理和日志记录功能 -``` - -系统会自动: -1. 识别邮件回复 -2. 提取命令内容 -3. 验证会话有效性 -4. 在 Claude Code 中执行命令 - -## 🛠️ 管理命令 - -### 查看中继服务状态 - -```bash -node taskping.js relay status -``` - -输出示例: -``` -📊 命令中继服务状态 - -✅ 邮件配置已启用 -📧 SMTP: smtp.gmail.com:587 -📥 IMAP: imap.gmail.com:993 -📬 收件人: your-email@gmail.com - -📋 命令队列: 3 个命令 - -最近的命令: - ✅ abc123: 请添加错误处理功能... - ⏳ def456: 生成单元测试... - ⏸️ ghi789: 优化性能... -``` - -### 清理命令历史 - -```bash -node taskping.js relay cleanup -``` - -### 停止中继服务 - -在运行中继服务的终端中按 `Ctrl+C` - -## 🔒 安全特性 - -### 会话管理 -- **唯一会话ID**: 每个通知邮件包含唯一的 UUID -- **24小时过期**: 会话自动过期,防止滥用 -- **命令限制**: 每个会话最多 10 个命令 - -### 内容安全 -- **邮件验证**: 验证回复邮件来源 -- **命令过滤**: 过滤危险命令 -- **长度限制**: 命令长度限制在 1000 字符内 - -### 危险命令黑名单 -系统会自动拒绝以下类型的命令: -- `rm -rf` (删除文件) -- `sudo` (提权操作) -- `curl | sh` (执行远程脚本) -- `eval` / `exec` (代码执行) - -## 🚨 故障排除 - -### 无法发送邮件 - -**问题**: 邮件发送失败 -**解决方案**: -1. 检查 SMTP 配置是否正确 -2. 确认使用应用密码而非普通密码 -3. 检查网络连接 -4. 查看防火墙设置 - -```bash -# 测试邮件发送 -node taskping.js config -# 选择通知渠道 → 邮件通知 → 测试 -``` - -### 无法接收回复 - -**问题**: 回复邮件后命令不执行 -**解决方案**: -1. 确认中继服务正在运行 -2. 检查 IMAP 配置 -3. 确认回复的是 TaskPing 邮件 -4. 检查命令是否被安全过滤器拦截 - -```bash -# 检查中继服务状态 -node taskping.js relay status - -# 重启中继服务 -node taskping.js relay start -``` - -### 会话过期 - -**问题**: 提示会话过期 -**解决方案**: -- 会话在24小时后自动过期 -- 需要等待新的任务完成通知 -- 或手动发送测试通知 - -### Claude Code 进程检测失败 - -**问题**: 无法找到 Claude Code 进程 -**解决方案**: -1. 确保 Claude Code 正在运行 -2. 检查进程名称是否正确 -3. 目前支持自动化输入的平台:macOS - -## 📱 高级用法 - -### 多项目管理 - -不同项目的通知邮件会包含项目名称,方便区分: - -``` -[TaskPing] Claude Code 任务完成 - Frontend-Project -[TaskPing] Claude Code 任务完成 - Backend-API -[TaskPing] Claude Code 任务完成 - Mobile-App -``` - -### 命令模板 - -常用命令模板: - -```bash -# 代码优化 -"请优化性能并添加注释" - -# 测试生成 -"为这个函数生成单元测试" - -# 文档生成 -"生成 API 文档" - -# 代码审查 -"审查代码并指出潜在问题" - -# 重构建议 -"建议如何重构这段代码" -``` - -### 批量操作 - -可以在一封回复邮件中包含多个步骤: - -``` -请按以下步骤处理: -1. 优化函数性能 -2. 添加错误处理 -3. 生成单元测试 -4. 更新文档 -``` - -## 🎯 最佳实践 - -### 1. 安全建议 -- 不要在邮件中包含敏感信息 -- 定期更换应用密码 -- 不要转发 TaskPing 通知邮件 -- 及时清理过期会话 - -### 2. 命令编写 -- 使用清晰、具体的指令 -- 避免过于复杂的命令 -- 一次专注一个任务 -- 使用自然语言,无需特殊格式 - -### 3. 工作流程 -- 启动工作时开启中继服务 -- 结束工作时关闭中继服务 -- 定期查看中继状态 -- 及时清理命令历史 - -## 🆘 常见问题 - -**Q: 邮件功能会影响现有的桌面通知吗?** -A: 不会。邮件通知和桌面通知是独立的,可以同时启用。 - -**Q: 可以配置多个邮箱吗?** -A: 目前支持一个邮箱配置,但可以发送给多个收件人(在配置中用逗号分隔)。 - -**Q: 支持哪些邮箱提供商?** -A: 支持所有标准的 SMTP/IMAP 邮箱服务,包括 Gmail、Outlook、QQ、163 等。 - -**Q: 命令执行失败怎么办?** -A: 系统会自动重试 3 次,如果仍然失败,会在状态中显示错误信息。 - -**Q: 如何确保数据安全?** -A: 所有邮件配置存储在本地,使用应用密码而非主密码,会话自动过期。 - -## 🎉 开始使用 - -现在您已经了解了 TaskPing 邮件功能的所有细节,可以开始配置和使用了: - -```bash -# 1. 配置邮箱 -node taskping.js config - -# 2. 测试通知 -node taskping.js test - -# 3. 启动中继服务 -node taskping.js relay start - -# 4. 开始使用 Claude Code,享受远程工作的便利! -``` - -享受您的远程 AI 编程体验!🚀 \ No newline at end of file diff --git a/docs/EMAIL_REPLY_GUIDE.md b/docs/EMAIL_REPLY_GUIDE.md deleted file mode 100644 index 3d5a5ca..0000000 --- a/docs/EMAIL_REPLY_GUIDE.md +++ /dev/null @@ -1,274 +0,0 @@ -# TaskPing 邮件回复功能使用指南 - -## 概述 - -TaskPing 的邮件回复功能允许您通过回复邮件的方式向 Claude Code CLI 发送命令。当您在移动设备或其他电脑上收到 TaskPing 的任务提醒邮件时,可以直接回复邮件来控制 Claude Code 继续执行任务。 - -## 工作原理 - -1. **发送提醒**: Claude Code 在任务暂停时发送包含会话 Token 的提醒邮件 -2. **邮件监听**: PTY Relay 服务持续监听您的收件箱 -3. **命令提取**: 从回复邮件中提取命令内容 -4. **命令注入**: 通过 node-pty 将命令注入到对应的 Claude Code 会话 - -## 快速开始 - -### 1. 配置邮件账号 - -复制环境配置文件: -```bash -cp .env.example .env -``` - -编辑 `.env` 文件,填入您的邮件配置: -```env -# Gmail 示例 -IMAP_HOST=imap.gmail.com -IMAP_PORT=993 -IMAP_SECURE=true -IMAP_USER=your-email@gmail.com -IMAP_PASS=your-app-password # 使用应用专用密码 - -# 安全设置 -ALLOWED_SENDERS=your-email@gmail.com,trusted@company.com -``` - -### 2. 启动 PTY Relay 服务 - -```bash -npm run relay:pty -# 或者 -./start-relay-pty.js -``` - -### 3. 测试邮件解析 - -运行测试工具验证配置: -```bash -npm run relay:test -``` - -## 使用方法 - -### 邮件格式要求 - -#### 主题格式 -邮件主题必须包含 TaskPing Token,支持以下格式: -- `[TaskPing #TOKEN]` - 推荐格式 -- `[TaskPing TOKEN]` -- `TaskPing: TOKEN` - -例如: -- `Re: [TaskPing #ABC123] 任务等待您的指示` -- `回复: TaskPing: XYZ789` - -#### 命令格式 -支持三种命令输入方式: - -1. **直接输入**(最简单) - ``` - 继续执行 - ``` - -2. **CMD 前缀**(明确标识) - ``` - CMD: npm run build - ``` - -3. **代码块**(复杂命令) - ```` - ``` - git add . - git commit -m "Update features" - git push - ``` - ```` - -### 示例场景 - -#### 场景 1: 简单确认 -收到邮件: -``` -主题: [TaskPing #TASK001] 是否继续部署到生产环境? -``` - -回复: -``` -yes -``` - -#### 场景 2: 执行具体命令 -收到邮件: -``` -主题: [TaskPing #BUILD123] 构建失败,请输入修复命令 -``` - -回复: -``` -CMD: npm install missing-package -``` - -#### 场景 3: 多行命令 -收到邮件: -``` -主题: [TaskPing #DEPLOY456] 准备部署,请确认步骤 -``` - -回复: -```` -执行以下命令: - -``` -npm run test -npm run build -npm run deploy -``` -```` - -## 高级配置 - -### 会话管理 - -会话映射文件 `session-map.json` 结构: -```json -{ - "TOKEN123": { - "type": "pty", - "createdAt": 1234567890, - "expiresAt": 1234654290, - "cwd": "/path/to/project", - "description": "构建项目 X" - } -} -``` - -### 环境变量说明 - -| 变量名 | 说明 | 默认值 | -|--------|------|--------| -| `IMAP_HOST` | IMAP 服务器地址 | 必需 | -| `IMAP_PORT` | IMAP 端口 | 993 | -| `IMAP_SECURE` | 使用 SSL/TLS | true | -| `IMAP_USER` | 邮箱账号 | 必需 | -| `IMAP_PASS` | 邮箱密码 | 必需 | -| `ALLOWED_SENDERS` | 允许的发件人列表 | 空(接受所有) | -| `SESSION_MAP_PATH` | 会话映射文件路径 | ./src/data/session-map.json | -| `CLAUDE_CLI_PATH` | Claude CLI 路径 | claude | -| `LOG_LEVEL` | 日志级别 | info | -| `PTY_OUTPUT_LOG` | 记录 PTY 输出 | false | - -### 邮件服务器配置示例 - -#### Gmail -1. 启用 IMAP: 设置 → 转发和 POP/IMAP → 启用 IMAP -2. 生成应用专用密码: 账号设置 → 安全 → 应用专用密码 - -```env -IMAP_HOST=imap.gmail.com -IMAP_PORT=993 -IMAP_SECURE=true -``` - -#### Outlook/Office 365 -```env -IMAP_HOST=outlook.office365.com -IMAP_PORT=993 -IMAP_SECURE=true -``` - -#### QQ 邮箱 -```env -IMAP_HOST=imap.qq.com -IMAP_PORT=993 -IMAP_SECURE=true -``` - -## 安全注意事项 - -1. **发件人验证**: 始终配置 `ALLOWED_SENDERS` 以限制谁可以发送命令 -2. **命令过滤**: 系统会自动过滤危险命令(如 `rm -rf`) -3. **会话过期**: 会话有过期时间,过期后无法接受命令 -4. **邮件加密**: 使用 SSL/TLS 加密的 IMAP 连接 -5. **密码安全**: 使用应用专用密码而非主密码 - -## 故障排查 - -### 常见问题 - -1. **无法连接到邮件服务器** - - 检查 IMAP 是否已启用 - - 验证服务器地址和端口 - - 确认防火墙设置 - -2. **邮件未被处理** - - 检查发件人是否在白名单中 - - 验证邮件主题格式 - - 查看日志中的错误信息 - -3. **命令未执行** - - 确认 Claude Code 进程正在运行 - - 检查会话是否已过期 - - 验证命令格式是否正确 - -### 查看日志 - -```bash -# 启动时查看详细日志 -LOG_LEVEL=debug npm run relay:pty - -# 查看 PTY 输出 -PTY_OUTPUT_LOG=true npm run relay:pty -``` - -### 测试命令 - -```bash -# 测试邮件解析 -npm run relay:test - -# 手动启动(调试模式) -INJECTION_MODE=pty LOG_LEVEL=debug node src/relay/relay-pty.js -``` - -## 最佳实践 - -1. **使用专用邮箱**: 为 TaskPing 创建专用邮箱账号 -2. **定期清理**: 定期清理过期会话和已处理邮件 -3. **命令简洁**: 保持命令简短明确 -4. **及时回复**: 在会话过期前回复邮件 -5. **安全优先**: 不要在邮件中包含敏感信息 - -## 集成到现有项目 - -如果您想将邮件回复功能集成到现有的 Claude Code 工作流: - -1. 在发送提醒时创建会话: -```javascript -const token = generateToken(); -const session = { - type: 'pty', - createdAt: Math.floor(Date.now() / 1000), - expiresAt: Math.floor((Date.now() + 3600000) / 1000), - cwd: process.cwd() -}; -saveSession(token, session); -``` - -2. 在邮件主题中包含 Token: -```javascript -const subject = `[TaskPing #${token}] ${taskDescription}`; -``` - -3. 启动 PTY Relay 服务监听回复 - -## 相关文档 - -- [邮件配置指南](./EMAIL_GUIDE.md) -- [快速邮件设置](./QUICK_EMAIL_SETUP.md) -- [系统架构说明](./EMAIL_ARCHITECTURE.md) - -## 支持 - -如有问题,请查看: -- 项目 Issue: https://github.com/JessyTsui/TaskPing/issues -- 详细日志: `LOG_LEVEL=debug npm run relay:pty` \ No newline at end of file diff --git a/docs/QUICK_EMAIL_SETUP.md b/docs/QUICK_EMAIL_SETUP.md deleted file mode 100644 index c635f4c..0000000 --- a/docs/QUICK_EMAIL_SETUP.md +++ /dev/null @@ -1,266 +0,0 @@ -# TaskPing 邮件功能快速配置指南 - -## 🚀 三种配置方式 - -TaskPing 现在提供三种方式来配置邮件功能,您可以选择最适合的方式: - -### 方式 1: 快速配置向导 (推荐 ⭐) - -```bash -node taskping.js setup-email -``` - -**优点**: -- 🎯 一步到位,引导式配置 -- 🛡️ 内置常见邮箱提供商设置 -- 🧪 配置完成后可立即测试 - -**适用于**: 初次配置,想要快速开始使用的用户 - ---- - -### 方式 2: 直接编辑配置文件 - -```bash -node taskping.js edit-config channels -``` - -**优点**: -- ⚡ 最快速,适合有经验的用户 -- 🔧 完全控制所有配置选项 -- 📝 支持批量修改和复制粘贴 - -**适用于**: 熟悉JSON格式,需要精确控制配置的用户 - ---- - -### 方式 3: 交互式配置管理器 - -```bash -node taskping.js config -# 选择 "3. 通知渠道" → "2. 邮件通知" -``` - -**优点**: -- 🎮 交互式界面,逐步配置 -- 💡 详细的配置说明和提示 -- 🔄 可以随时修改现有配置 - -**适用于**: 喜欢逐步配置,需要详细指导的用户 - ---- - -## 📧 常见邮箱配置 - -### Gmail 配置 - -**前提条件**: -1. 启用两步验证 -2. 生成应用密码 (16位) - -**配置参数**: -```json -{ - "smtp": { - "host": "smtp.gmail.com", - "port": 587, - "secure": false - }, - "imap": { - "host": "imap.gmail.com", - "port": 993, - "secure": true - } -} -``` - -### QQ邮箱配置 - -**前提条件**: -1. 开启SMTP/IMAP服务 -2. 获取授权码 - -**配置参数**: -```json -{ - "smtp": { - "host": "smtp.qq.com", - "port": 587, - "secure": false - }, - "imap": { - "host": "imap.qq.com", - "port": 993, - "secure": true - } -} -``` - -### 163邮箱配置 - -**配置参数**: -```json -{ - "smtp": { - "host": "smtp.163.com", - "port": 587, - "secure": false - }, - "imap": { - "host": "imap.163.com", - "port": 993, - "secure": true - } -} -``` - ---- - -## 🛠️ 配置后的使用流程 - -### 1. 测试邮件发送 - -```bash -node taskping.js test -``` - -应该能看到: -``` -Testing notification channels... - -✅ desktop: PASS -✅ email: PASS - -Test completed: 2/2 channels passed -``` - -### 2. 查看系统状态 - -```bash -node taskping.js status -``` - -应该能看到: -``` -TaskPing Status - -Configuration: - Enabled: Yes - Language: zh-CN - Sounds: Submarine / Hero - -Channels: - desktop: - Enabled: ✅ - Configured: ✅ - Supports Relay: ❌ - email: - Enabled: ✅ - Configured: ✅ - Supports Relay: ✅ -``` - -### 3. 启动命令中继服务 - -```bash -node taskping.js relay start -``` - -看到以下信息表示成功: -``` -🚀 启动邮件命令中继服务... -✅ 命令中继服务已启动 -📧 正在监听邮件回复... -💡 现在您可以通过回复邮件来远程执行Claude Code命令 - -按 Ctrl+C 停止服务 -``` - -### 4. 开始使用 - -现在当您使用 Claude Code 时: -1. 任务完成时会收到邮件通知 -2. 回复邮件即可远程执行下一步命令 -3. 享受远程AI编程的便利! - ---- - -## 🚨 常见问题解决 - -### Q: 邮件发送失败 - -**检查清单**: -- ✅ 邮箱密码是否使用应用密码 (不是登录密码) -- ✅ SMTP/IMAP 服务是否已开启 -- ✅ 网络连接是否正常 -- ✅ 防火墙是否阻止连接 - -**解决方法**: -```bash -# 重新配置 -node taskping.js setup-email - -# 或检查配置文件 -node taskping.js edit-config channels -``` - -### Q: 收不到邮件 - -**检查清单**: -- ✅ 垃圾邮件文件夹 -- ✅ 邮件地址是否正确 -- ✅ 邮箱存储空间是否充足 - -### Q: 无法接收回复 - -**检查清单**: -- ✅ 中继服务是否运行 (`taskping relay status`) -- ✅ IMAP 配置是否正确 -- ✅ 是否回复的是 TaskPing 发送的邮件 - ---- - -## 📱 使用技巧 - -### 1. 邮件模板定制 - -您可以通过编辑配置文件自定义邮件检查间隔: - -```bash -node taskping.js edit-config channels -``` - -找到 `template.checkInterval` 并修改值 (单位:秒): -```json -"template": { - "checkInterval": 30 // 每30秒检查一次新邮件 -} -``` - -### 2. 多项目管理 - -不同项目的通知会自动包含项目名称: -``` -[TaskPing] Claude Code 任务完成 - MyProject -[TaskPing] Claude Code 任务完成 - AnotherProject -``` - -### 3. 批量配置 - -如果您有多台电脑需要配置,可以: -1. 在一台电脑上配置好 -2. 复制 `config/channels.json` 文件 -3. 粘贴到其他电脑的同一位置 - ---- - -## 🎯 最佳实践 - -1. **安全性**: 定期更换应用密码 -2. **性能**: 根据使用频率调整检查间隔 -3. **维护**: 定期清理命令历史 (`taskping relay cleanup`) -4. **监控**: 定期检查中继服务状态 (`taskping relay status`) - ---- - -开始享受远程AI编程的便利吧!🚀 \ No newline at end of file diff --git a/docs/QUICK_SETUP_GUIDE.md b/docs/QUICK_SETUP_GUIDE.md deleted file mode 100644 index 2360e83..0000000 --- a/docs/QUICK_SETUP_GUIDE.md +++ /dev/null @@ -1,163 +0,0 @@ -# TaskPing 快速配置指南 - -## 🚀 5分钟快速开始 - -本指南帮助您快速配置 TaskPing 的邮件通知和回复功能。 - -## 第一步:配置邮件账号 - -### 1.1 复制环境配置文件 -```bash -cp .env.example .env -``` - -### 1.2 编辑 .env 文件 - -您的配置已经部分完成: -- ✅ 发件服务器:飞书邮箱 (noreply@pandalla.ai) -- ✅ 收件邮箱:Gmail (jiaxicui446@gmail.com) -- ❌ Gmail 应用密码:需要您配置 - -### 1.3 获取 Gmail 应用专用密码 - -1. 登录您的 Gmail 账号 (jiaxicui446@gmail.com) -2. 访问 [Google 账号安全设置](https://myaccount.google.com/security) -3. 开启"两步验证"(如果未开启) -4. 访问 [应用专用密码页面](https://myaccount.google.com/apppasswords) -5. 选择应用"邮件",设备选择"其他" -6. 输入名称"TaskPing" -7. 点击"生成"获取16位密码 -8. 将密码复制到 .env 文件的 `IMAP_PASS` 字段 - -## 第二步:更新配置 - -```bash -# 更新邮件配置到系统 -npm run email:config -``` - -## 第三步:测试邮件发送 - -```bash -# 发送测试邮件 -npm run email:test -``` - -成功后您会看到: -``` -✅ 邮件发送成功! - Message ID: - Response: 250 2.0.0 OK: queued -``` - -## 第四步:启动邮件监听服务 - -```bash -# 启动 PTY 模式的邮件监听 -npm run relay:pty -``` - -服务启动后会显示: -``` -🚀 正在启动 TaskPing PTY Relay 服务... -📧 IMAP服务器: imap.gmail.com -👤 邮件账号: jiaxicui446@gmail.com -``` - -## 第五步:测试完整流程 - -1. **检查收件箱** - - 查看 jiaxicui446@gmail.com 是否收到测试邮件 - - 主题类似:`[TaskPing #TESTXXXXX] 测试邮件 - 等待您的指令` - -2. **回复测试命令** - - 直接回复邮件 - - 内容输入:`echo "Hello from TaskPing"` - - 发送 - -3. **查看服务日志** - - PTY Relay 服务会显示收到的命令 - - Claude Code 会执行该命令 - -## 📝 常用命令速查 - -| 功能 | 命令 | -|------|------| -| 更新邮件配置 | `npm run email:config` | -| 测试邮件发送 | `npm run email:test` | -| 启动监听服务 | `npm run relay:pty` | -| 测试邮件解析 | `npm run relay:test` | -| 查看配置状态 | `cat .env` | - -## 🔧 故障排查 - -### 邮件发送失败 -- 检查飞书邮箱 SMTP 密码是否正确 -- 确认网络可以访问 smtp.feishu.cn:465 - -### 邮件接收失败 -- 确认 Gmail 应用专用密码已配置 -- 检查 Gmail 是否开启了 IMAP -- 查看服务日志中的错误信息 - -### 命令未执行 -- 确认回复邮件的主题包含原始 Token -- 检查邮件内容格式是否正确 -- 验证 Claude Code 是否在运行 - -## 💡 使用技巧 - -1. **邮件回复格式** - ``` - # 简单命令 - 继续 - - # 明确命令 - CMD: npm run build - - # 多行命令 - ``` - git add . - git commit -m "Update" - ``` - ``` - -2. **会话管理** - - 每个会话有1小时有效期 - - Token 在邮件主题中:`[TaskPing #TOKEN]` - - 过期后需要新的通知邮件 - -3. **安全建议** - - 只从受信任的邮箱发送命令 - - 定期更换应用专用密码 - - 不要在邮件中包含敏感信息 - -## 🎯 实际使用场景 - -1. **移动办公** - - 在手机上收到任务通知 - - 直接回复邮件继续任务 - - 无需返回电脑操作 - -2. **远程协作** - - 团队成员可以通过邮件控制任务 - - 支持多人协作(配置白名单) - - 保留邮件审计记录 - -3. **自动化工作流** - - 集成到现有邮件系统 - - 支持邮件规则触发 - - 可以配置自动回复 - -## 📚 更多文档 - -- [详细邮件配置指南](./EMAIL_GUIDE.md) -- [邮件回复功能说明](./EMAIL_REPLY_GUIDE.md) -- [系统架构文档](./EMAIL_ARCHITECTURE.md) - ---- - -有问题?查看 [GitHub Issues](https://github.com/JessyTsui/TaskPing/issues) 或运行调试模式: -```bash -LOG_LEVEL=debug npm run relay:pty -``` \ No newline at end of file diff --git a/email-automation.js b/email-automation.js deleted file mode 100755 index 4d32c4e..0000000 --- a/email-automation.js +++ /dev/null @@ -1,369 +0,0 @@ -#!/usr/bin/env node - -/** - * TaskPing 邮件自动化 - * 监听邮件回复并自动输入到Claude Code - */ - -const Imap = require('node-imap'); -const { simpleParser } = require('mailparser'); -const { spawn } = require('child_process'); -const fs = require('fs'); -const path = require('path'); - -class EmailAutomation { - constructor() { - this.configPath = path.join(__dirname, 'config/channels.json'); - this.imap = null; - this.isRunning = false; - this.config = null; - } - - async start() { - console.log('🚀 TaskPing 邮件自动化启动中...\n'); - - // 加载配置 - if (!this.loadConfig()) { - console.log('❌ 请先配置邮件: npm run config'); - process.exit(1); - } - - console.log(`📧 监听邮箱: ${this.config.imap.auth.user}`); - console.log(`📬 发送通知到: ${this.config.to}\n`); - - try { - await this.connectToEmail(); - this.startListening(); - - console.log('✅ 邮件监听启动成功'); - console.log('💌 现在可以回复TaskPing邮件来发送命令到Claude Code'); - console.log('按 Ctrl+C 停止服务\n'); - - this.setupGracefulShutdown(); - process.stdin.resume(); - - } catch (error) { - console.error('❌ 启动失败:', error.message); - process.exit(1); - } - } - - loadConfig() { - try { - const data = fs.readFileSync(this.configPath, 'utf8'); - const config = JSON.parse(data); - - if (!config.email?.enabled) { - console.log('❌ 邮件功能未启用'); - return false; - } - - this.config = config.email.config; - return true; - } catch (error) { - console.log('❌ 配置文件读取失败'); - return false; - } - } - - async connectToEmail() { - return new Promise((resolve, reject) => { - this.imap = new Imap({ - user: this.config.imap.auth.user, - password: this.config.imap.auth.pass, - host: this.config.imap.host, - port: this.config.imap.port, - tls: this.config.imap.secure, - connTimeout: 60000, - authTimeout: 30000, - keepalive: true - }); - - this.imap.once('ready', () => { - console.log('📬 IMAP连接成功'); - resolve(); - }); - - this.imap.once('error', reject); - this.imap.once('end', () => { - if (this.isRunning) { - console.log('🔄 连接断开,尝试重连...'); - setTimeout(() => this.connectToEmail().catch(console.error), 5000); - } - }); - - this.imap.connect(); - }); - } - - startListening() { - this.isRunning = true; - console.log('👂 开始监听新邮件...'); - - // 每15秒检查一次新邮件 - setInterval(() => { - if (this.isRunning) { - this.checkNewEmails(); - } - }, 15000); - - // 立即检查一次 - this.checkNewEmails(); - } - - async checkNewEmails() { - try { - await this.openInbox(); - - // 查找最近1小时内的未读邮件 - const since = new Date(); - since.setHours(since.getHours() - 1); - - this.imap.search([['UNSEEN'], ['SINCE', since]], (err, results) => { - if (err) { - console.error('搜索邮件失败:', err.message); - return; - } - - if (results.length > 0) { - console.log(`📧 发现 ${results.length} 封新邮件`); - this.processEmails(results); - } - }); - } catch (error) { - console.error('检查邮件失败:', error.message); - } - } - - openInbox() { - return new Promise((resolve, reject) => { - this.imap.openBox('INBOX', false, (err, box) => { - if (err) reject(err); - else resolve(box); - }); - }); - } - - processEmails(emailUids) { - const fetch = this.imap.fetch(emailUids, { - bodies: '', - markSeen: true - }); - - fetch.on('message', (msg) => { - let buffer = ''; - - msg.on('body', (stream) => { - stream.on('data', (chunk) => { - buffer += chunk.toString('utf8'); - }); - - stream.once('end', async () => { - try { - const parsed = await simpleParser(buffer); - await this.handleEmailReply(parsed); - } catch (error) { - console.error('处理邮件失败:', error.message); - } - }); - }); - }); - } - - async handleEmailReply(email) { - // 检查是否是TaskPing回复 - if (!this.isTaskPingReply(email)) { - return; - } - - // 提取命令 - const command = this.extractCommand(email); - if (!command) { - console.log('邮件中未找到有效命令'); - return; - } - - console.log(`🎯 收到命令: ${command.substring(0, 100)}${command.length > 100 ? '...' : ''}`); - - // 执行命令到Claude Code - await this.sendToClaudeCode(command); - } - - isTaskPingReply(email) { - const subject = email.subject || ''; - return subject.includes('[TaskPing]') || - subject.match(/^(Re:|RE:|回复:)/i); - } - - extractCommand(email) { - let text = email.text || ''; - const lines = text.split('\n'); - const commandLines = []; - - for (const line of lines) { - // 停止处理当遇到原始邮件标记 - if (line.includes('-----Original Message-----') || - line.includes('--- Original Message ---') || - line.includes('在') && line.includes('写道:') || - line.includes('On') && line.includes('wrote:') || - line.match(/^>\s*/) || - line.includes('会话ID:')) { - break; - } - - // 跳过签名 - if (line.includes('--') || - line.includes('Sent from') || - line.includes('发自我的')) { - break; - } - - commandLines.push(line); - } - - return commandLines.join('\n').trim(); - } - - async sendToClaudeCode(command) { - console.log('🤖 正在发送命令到Claude Code...'); - - try { - // 方法1: 复制到剪贴板 - await this.copyToClipboard(command); - - // 方法2: 强制自动化输入 - const success = await this.forceAutomation(command); - - if (success) { - console.log('✅ 命令已自动输入到Claude Code'); - } else { - console.log('⚠️ 自动输入失败,命令已复制到剪贴板'); - console.log('💡 请手动在Claude Code中粘贴 (Cmd+V)'); - - // 发送通知提醒 - await this.sendNotification(command); - } - } catch (error) { - console.error('❌ 发送命令失败:', error.message); - } - } - - async copyToClipboard(command) { - return new Promise((resolve, reject) => { - const pbcopy = spawn('pbcopy'); - pbcopy.stdin.write(command); - pbcopy.stdin.end(); - - pbcopy.on('close', (code) => { - if (code === 0) { - resolve(); - } else { - reject(new Error('剪贴板复制失败')); - } - }); - }); - } - - async forceAutomation(command) { - return new Promise((resolve) => { - // 转义命令中的特殊字符 - const escapedCommand = command - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/'/g, "\\'"); - - const script = ` - tell application "System Events" - -- 查找Claude Code相关应用 - set claudeApps to {"Claude", "Claude Code", "Claude Desktop", "Anthropic Claude"} - set targetApp to null - - repeat with appName in claudeApps - try - if application process appName exists then - set targetApp to application process appName - exit repeat - end if - end try - end repeat - - -- 如果没找到Claude,查找开发工具 - if targetApp is null then - set devApps to {"Terminal", "iTerm2", "iTerm", "Visual Studio Code", "Code", "Cursor"} - repeat with appName in devApps - try - if application process appName exists then - set targetApp to application process appName - exit repeat - end if - end try - end repeat - end if - - if targetApp is not null then - -- 激活应用 - set frontmost of targetApp to true - delay 1 - - -- 确保窗口激活 - repeat 10 times - if frontmost of targetApp then exit repeat - delay 0.1 - end repeat - - -- 清空并输入命令 - keystroke "a" using command down - delay 0.3 - keystroke "${escapedCommand}" - delay 0.5 - keystroke return - - return "success" - else - return "no_app" - end if - end tell - `; - - const osascript = spawn('osascript', ['-e', script]); - let output = ''; - - osascript.stdout.on('data', (data) => { - output += data.toString().trim(); - }); - - osascript.on('close', (code) => { - const success = code === 0 && output === 'success'; - resolve(success); - }); - - osascript.on('error', () => resolve(false)); - }); - } - - async sendNotification(command) { - const shortCommand = command.length > 50 ? command.substring(0, 50) + '...' : command; - - const script = ` - display notification "邮件命令已准备好,请在Claude Code中粘贴执行" with title "TaskPing" subtitle "${shortCommand.replace(/"/g, '\\"')}" sound name "default" - `; - - spawn('osascript', ['-e', script]); - } - - setupGracefulShutdown() { - process.on('SIGINT', () => { - console.log('\n🛑 正在停止邮件监听...'); - this.isRunning = false; - if (this.imap) { - this.imap.end(); - } - console.log('✅ 服务已停止'); - process.exit(0); - }); - } -} - -// 启动服务 -const automation = new EmailAutomation(); -automation.start().catch(console.error); \ No newline at end of file diff --git a/email-checker.js b/email-checker.js deleted file mode 100644 index 669dda4..0000000 --- a/email-checker.js +++ /dev/null @@ -1,364 +0,0 @@ -#!/usr/bin/env node - -/** - * TaskPing 邮件检查器 - * 更强大的邮件搜索和内容提取工具 - */ - -const Imap = require('node-imap'); -const { simpleParser } = require('mailparser'); -const { spawn } = require('child_process'); -const fs = require('fs'); -const path = require('path'); - -class EmailChecker { - constructor() { - this.configPath = path.join(__dirname, 'config/channels.json'); - this.config = null; - this.imap = null; - } - - async start() { - console.log('🔍 TaskPing 强化邮件检查器启动\n'); - - // 加载配置 - if (!this.loadConfig()) { - console.log('❌ 请先配置邮件: npm run config'); - process.exit(1); - } - - console.log(`📧 邮箱: ${this.config.imap.auth.user}`); - console.log(`📬 通知发送到: ${this.config.to}\n`); - - try { - await this.connectToEmail(); - await this.comprehensiveEmailCheck(); - await this.startContinuousMonitoring(); - } catch (error) { - console.error('❌ 启动失败:', error.message); - process.exit(1); - } - } - - loadConfig() { - try { - const data = fs.readFileSync(this.configPath, 'utf8'); - const config = JSON.parse(data); - - if (!config.email?.enabled) { - console.log('❌ 邮件功能未启用'); - return false; - } - - this.config = config.email.config; - return true; - } catch (error) { - console.log('❌ 配置文件读取失败'); - return false; - } - } - - async connectToEmail() { - return new Promise((resolve, reject) => { - this.imap = new Imap({ - user: this.config.imap.auth.user, - password: this.config.imap.auth.pass, - host: this.config.imap.host, - port: this.config.imap.port, - tls: this.config.imap.secure, - connTimeout: 60000, - authTimeout: 30000, - keepalive: true - }); - - this.imap.once('ready', () => { - console.log('✅ IMAP连接成功'); - resolve(); - }); - - this.imap.once('error', reject); - this.imap.connect(); - }); - } - - async comprehensiveEmailCheck() { - console.log('\n🔍 开始全面邮件检查...\n'); - - await this.openInbox(); - - // 1. 检查最近24小时所有邮件(不仅是未读) - console.log('📅 1. 检查最近24小时所有邮件...'); - await this.searchEmails('24h', false); - - // 2. 检查最近1小时未读邮件 - console.log('\n📧 2. 检查最近1小时未读邮件...'); - await this.searchEmails('1h', true); - - // 3. 检查主题包含特定关键词的邮件 - console.log('\n🎯 3. 检查TaskPing相关邮件...'); - await this.searchBySubject(); - - // 4. 检查来自特定发件人的邮件 - console.log('\n👤 4. 检查来自目标邮箱的邮件...'); - await this.searchByFrom(); - } - - async openInbox() { - return new Promise((resolve, reject) => { - this.imap.openBox('INBOX', false, (err, box) => { - if (err) { - reject(err); - return; - } - console.log(`📫 收件箱: 总计${box.messages.total}封邮件`); - resolve(box); - }); - }); - } - - async searchEmails(timeRange, unseenOnly = false) { - return new Promise((resolve) => { - const since = new Date(); - if (timeRange === '1h') { - since.setHours(since.getHours() - 1); - } else if (timeRange === '24h') { - since.setDate(since.getDate() - 1); - } - - let searchCriteria = [['SINCE', since]]; - if (unseenOnly) { - searchCriteria.push(['UNSEEN']); - } - - console.log(`🔍 搜索条件: ${timeRange}, ${unseenOnly ? '仅未读' : '全部'}`); - - this.imap.search(searchCriteria, (err, results) => { - if (err) { - console.error('❌ 搜索失败:', err.message); - resolve(); - return; - } - - console.log(`📨 找到 ${results.length} 封邮件`); - - if (results.length > 0) { - this.analyzeEmails(results.slice(-5)); // 只分析最新5封 - } - resolve(); - }); - }); - } - - async searchBySubject() { - return new Promise((resolve) => { - // 搜索主题包含Re:或TaskPing的邮件 - this.imap.search([['OR', ['SUBJECT', 'Re:'], ['SUBJECT', 'TaskPing']]], (err, results) => { - if (err) { - console.error('❌ 主题搜索失败:', err.message); - resolve(); - return; - } - - console.log(`📨 找到 ${results.length} 封相关邮件`); - - if (results.length > 0) { - this.analyzeEmails(results.slice(-3)); // 只分析最新3封 - } - resolve(); - }); - }); - } - - async searchByFrom() { - return new Promise((resolve) => { - // 搜索来自目标邮箱的邮件 - const targetEmail = this.config.to; - - this.imap.search([['FROM', targetEmail]], (err, results) => { - if (err) { - console.error('❌ 发件人搜索失败:', err.message); - resolve(); - return; - } - - console.log(`📨 找到来自 ${targetEmail} 的 ${results.length} 封邮件`); - - if (results.length > 0) { - this.analyzeEmails(results.slice(-3)); // 只分析最新3封 - } - resolve(); - }); - }); - } - - analyzeEmails(emailUids) { - const fetch = this.imap.fetch(emailUids, { - bodies: '', - markSeen: false // 不标记为已读 - }); - - fetch.on('message', (msg, seqno) => { - console.log(`\n📧 分析邮件 ${seqno}:`); - let buffer = ''; - - msg.on('body', (stream) => { - stream.on('data', (chunk) => { - buffer += chunk.toString('utf8'); - }); - - stream.once('end', async () => { - try { - const parsed = await simpleParser(buffer); - await this.processEmailContent(parsed, seqno); - } catch (error) { - console.error(`❌ 解析邮件 ${seqno} 失败:`, error.message); - } - }); - }); - }); - } - - async processEmailContent(email, seqno) { - console.log(` 📄 主题: ${email.subject || '(无主题)'}`); - console.log(` 👤 发件人: ${email.from?.text || '(未知)'}`); - console.log(` 📅 时间: ${email.date || '(未知)'}`); - - // 判断是否是潜在的回复邮件 - const isPotentialReply = this.isPotentialReply(email); - console.log(` 🎯 潜在回复: ${isPotentialReply ? '是' : '否'}`); - - if (isPotentialReply) { - const command = this.extractCommand(email); - console.log(` 💬 邮件内容长度: ${(email.text || '').length} 字符`); - console.log(` 📝 提取的内容:\n"${command.substring(0, 200)}${command.length > 200 ? '...' : ''}"`); - - if (command && command.trim().length > 0) { - console.log(`\n🎉 发现有效命令! (邮件 ${seqno})`); - await this.handleCommand(command, seqno); - } - } - } - - isPotentialReply(email) { - const subject = email.subject || ''; - const from = email.from?.text || ''; - const targetEmail = this.config.to; - - // 检查多种条件 - return ( - subject.includes('[TaskPing]') || - subject.match(/^(Re:|RE:|回复:)/i) || - from.includes(targetEmail) || - (email.text && email.text.length > 10) // 有实际内容 - ); - } - - extractCommand(email) { - let text = email.text || ''; - const lines = text.split('\n'); - const commandLines = []; - - for (const line of lines) { - // 停止处理当遇到原始邮件标记 - if (line.includes('-----Original Message-----') || - line.includes('--- Original Message ---') || - (line.includes('在') && line.includes('写道:')) || - (line.includes('On') && line.includes('wrote:')) || - line.match(/^>\s*/) || - line.includes('会话ID:') || - line.includes('TaskPing <') || - line.match(/\d{4}年\d{1,2}月\d{1,2}日/)) { - break; - } - - // 跳过签名和空行 - if (line.includes('--') || - line.includes('Sent from') || - line.includes('发自我的') || - line.trim() === '') { - continue; - } - - commandLines.push(line); - } - - return commandLines.join('\n').trim(); - } - - async handleCommand(command, seqno) { - console.log(`\n🚀 处理命令 (来自邮件 ${seqno}):`); - console.log(`📝 命令内容: "${command}"`); - - try { - // 复制到剪贴板 - await this.copyToClipboard(command); - console.log('✅ 命令已复制到剪贴板'); - - // 发送通知 - await this.sendNotification(command); - console.log('✅ 通知已发送'); - - console.log('\n🎯 请在Claude Code中粘贴命令 (Cmd+V)'); - - } catch (error) { - console.error('❌ 处理命令失败:', error.message); - } - } - - async copyToClipboard(command) { - return new Promise((resolve, reject) => { - const pbcopy = spawn('pbcopy'); - pbcopy.stdin.write(command); - pbcopy.stdin.end(); - - pbcopy.on('close', (code) => { - if (code === 0) { - resolve(); - } else { - reject(new Error('剪贴板复制失败')); - } - }); - }); - } - - async sendNotification(command) { - const shortCommand = command.length > 50 ? command.substring(0, 50) + '...' : command; - - const script = ` - display notification "邮件命令已复制到剪贴板,请在Claude Code中粘贴" with title "TaskPing" subtitle "${shortCommand.replace(/"/g, '\\"')}" sound name "Glass" - `; - - spawn('osascript', ['-e', script]); - } - - async startContinuousMonitoring() { - console.log('\n👂 开始持续监控新邮件...'); - console.log('💌 现在可以回复邮件测试功能'); - console.log('🔍 每30秒检查一次新邮件\n'); - - setInterval(async () => { - try { - console.log('🔄 检查新邮件...'); - await this.searchEmails('1h', false); // 搜索所有邮件,不只是未读 - } catch (error) { - console.error('❌ 监控检查失败:', error.message); - } - }, 30000); // 每30秒检查一次 - - // 设置优雅关闭 - process.on('SIGINT', () => { - console.log('\n🛑 停止邮件监控...'); - if (this.imap) { - this.imap.end(); - } - console.log('✅ 服务已停止'); - process.exit(0); - }); - - process.stdin.resume(); - } -} - -// 启动检查器 -const checker = new EmailChecker(); -checker.start().catch(console.error); \ No newline at end of file diff --git a/install-global.js b/install-global.js new file mode 100644 index 0000000..ef8a6a3 --- /dev/null +++ b/install-global.js @@ -0,0 +1,155 @@ +#!/usr/bin/env node + +/** + * Global Installation Script for TaskPing claude-control + * Makes claude-control.js accessible from any directory + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +const SCRIPT_NAME = 'claude-control'; +const SOURCE_PATH = path.join(__dirname, 'claude-control.js'); +const TARGET_DIR = '/usr/local/bin'; +const TARGET_PATH = path.join(TARGET_DIR, SCRIPT_NAME); + +function checkRequirements() { + // Check if claude-control.js exists + if (!fs.existsSync(SOURCE_PATH)) { + console.error('❌ Error: claude-control.js not found in current directory'); + process.exit(1); + } + + // Check if /usr/local/bin is writable + try { + fs.accessSync(TARGET_DIR, fs.constants.W_OK); + } catch (error) { + console.error('❌ Error: No write permission to /usr/local/bin'); + console.log('💡 Try running with sudo:'); + console.log(' sudo node install-global.js'); + process.exit(1); + } +} + +function createGlobalScript() { + const scriptContent = `#!/usr/bin/env node + +/** + * Global Claude Control Wrapper + * Executes claude-control.js from its original location + */ + +const path = require('path'); +const { spawn } = require('child_process'); + +// TaskPing installation directory +const TASKPING_DIR = '${__dirname}'; +const CLAUDE_CONTROL_PATH = path.join(TASKPING_DIR, 'claude-control.js'); + +// Get command line arguments (excluding node and script name) +const args = process.argv.slice(2); + +// Change to TaskPing directory before execution +process.chdir(TASKPING_DIR); + +// Execute claude-control.js with original arguments +const child = spawn('node', [CLAUDE_CONTROL_PATH, ...args], { + stdio: 'inherit', + env: { ...process.env, TASKPING_HOME: TASKPING_DIR } +}); + +child.on('error', (error) => { + console.error('Error executing claude-control:', error.message); + process.exit(1); +}); + +child.on('exit', (code, signal) => { + if (signal) { + process.kill(process.pid, signal); + } else { + process.exit(code || 0); + } +}); +`; + + return scriptContent; +} + +function install() { + console.log('🚀 Installing claude-control globally...\n'); + + try { + // Create the global script + const scriptContent = createGlobalScript(); + fs.writeFileSync(TARGET_PATH, scriptContent); + + // Make it executable + fs.chmodSync(TARGET_PATH, 0o755); + + console.log('✅ Installation completed successfully!'); + console.log(`📁 Installed to: ${TARGET_PATH}`); + console.log('\n🎉 Usage:'); + console.log(' claude-control --session myproject'); + console.log(' claude-control --list'); + console.log(' claude-control --kill all'); + console.log('\nYou can now run claude-control from any directory!'); + + } catch (error) { + console.error('❌ Installation failed:', error.message); + process.exit(1); + } +} + +function uninstall() { + console.log('🗑️ Uninstalling claude-control...\n'); + + try { + if (fs.existsSync(TARGET_PATH)) { + fs.unlinkSync(TARGET_PATH); + console.log('✅ Uninstallation completed successfully!'); + console.log(`🗑️ Removed: ${TARGET_PATH}`); + } else { + console.log('⚠️ claude-control is not installed globally'); + } + } catch (error) { + console.error('❌ Uninstallation failed:', error.message); + process.exit(1); + } +} + +function showHelp() { + console.log('TaskPing Claude Control - Global Installation\n'); + console.log('Usage:'); + console.log(' node install-global.js [install] - Install globally'); + console.log(' node install-global.js uninstall - Uninstall'); + console.log(' node install-global.js --help - Show this help\n'); + console.log('Requirements:'); + console.log(' - Write permission to /usr/local/bin (may need sudo)'); + console.log(' - claude-control.js must exist in current directory'); +} + +function main() { + const command = process.argv[2]; + + if (command === '--help' || command === '-h') { + showHelp(); + return; + } + + if (command === 'uninstall') { + uninstall(); + return; + } + + // Default action is install + checkRequirements(); + install(); +} + +// Run only if this script is executed directly +if (require.main === module) { + main(); +} + +module.exports = { install, uninstall }; \ No newline at end of file diff --git a/relay-injection.md b/relay-injection.md deleted file mode 100644 index cfd5318..0000000 --- a/relay-injection.md +++ /dev/null @@ -1,447 +0,0 @@ -# TaskPing — 邮件回复写回 Claude Code CLI(执行手册) - -> 目标读者:能执行脚本的 AI/自动化代理 -> 运行环境:Linux/macOS(建议),Node.js ≥ 18,Claude Code 已安装并可在终端运行 `claude` - ---- - -## 1. 背景与目标 - -**项目背景**:用户在本机通过 Claude Code(CLI)进行编码。当前已实现: -- 通过 hooks 在任务结束/等待下一步时**发出邮件提醒**; -- 用户在手机上**回复邮件**; -- 本机有一个常驻“中继/Relay”进程轮询收件箱,能够拿到该回复。 - -**当前卡点**:**把邮件正文写回到对应的 Claude Code CLI 会话,并回车执行**。 - -**本文档目的**:为 AI/自动化代理提供两种可执行的落地方案: -1) **子进程/PTY 模式**:用 `node-pty` 驱动一个 Claude Code 子进程,直接向伪终端写入并回车; -2) **tmux 模式**:让 Claude Code 运行在指定 `tmux` pane,收到邮件后用 `tmux send-keys` 注入文本并回车。 - -两种方案都要求: -- 能**路由**到正确的 Claude 会话; -- 能**清洗**邮件正文,仅保留要注入的命令; -- 有**安全**与**幂等**控制; -- 有**可观测性**(日志/Tracing)。 - ---- - -## 2. 全局约束与安全要求 - -- **会话识别**:优先按 `In-Reply-To` 的 `Message-ID` 匹配;回退使用 `Subject` 中的 token(形如 `[TaskPing #ABC123]`)。 -- **白名单**:仅允许来自配置的发件人域/邮箱(通过 SPF/DKIM/DMARC 验证后再放行)。 -- **正文提取**:只取**最新回复**;去除历史引用、签名、HTML 标签、图片占位。支持三种输入: - - 纯文本一行命令; - - 代码块 ``` 包起来的命令(优先级最高); - - 主题/首行以 `CMD:` 前缀标识。 -- **幂等**:用 `Message-ID` 或 `gmailThreadId` 去重(重复投递不再次注入)。 -- **限流**:同一会话每分钟最多 1 条;单条长度上限(例如 8KB)。 -- **日志**:记录 token、会话、pane/pty id、注入摘要(前 120 字符),屏蔽隐私。 - ---- - -## 3. 公共依赖与配置 - -### 3.1 依赖(Node.js) -```bash -npm i imapflow mailparser node-pty pino dotenv execa -# tmux 方案需要系统安装 tmux:macOS: brew install tmux;Debian/Ubuntu: apt-get install tmux -``` - -### 3.2 环境变量(`.env`) -```bash -# 邮件接收 -IMAP_HOST=imap.example.com -IMAP_PORT=993 -IMAP_SECURE=true -IMAP_USER=bot@example.com -IMAP_PASS=******** - -# 邮件路由安全 -ALLOWED_SENDERS=jessy@example.com,panda@company.com - -# 路由与会话存储(JSON 文件路径) -SESSION_MAP_PATH=/var/lib/taskping/session-map.json - -# 模式选择:pty 或 tmux -INJECTION_MODE=pty - -# tmux 方案可选:默认 session/pane 名称前缀 -TMUX_SESSION_PREFIX=taskping -``` - -### 3.3 会话映射(`SESSION_MAP_PATH`) -结构示例: -```json -{ - "ABC123": { - "type": "pty", - "ptyId": "b4c1...", - "createdAt": 1732672100, - "expiresAt": 1732679300, - "messageId": "", - "imapUid": 12345 - }, - "XYZ789": { - "type": "tmux", - "session": "taskping-XYZ789", - "pane": "taskping-XYZ789.0", - "createdAt": 1732672200, - "expiresAt": 1732679400, - "messageId": "" - } -} -``` -> 注:会话映射由**发送提醒时**创建(包含 token 与目标 CLI 会话信息)。 - ---- - -## 4. 方案一:子进程/PTY 模式(推荐) - -### 4.1 适用场景 -- 由中继程序**直接管理** Claude Code 生命周期; -- 需要最稳定的注入通道(不依赖额外终端复用器)。 - -### 4.2 实现思路 -- 用 `node-pty` `spawn('claude', [...])` 启动 CLI; -- 将返回的 `pty` 与生成的 token 绑定,写入 `SESSION_MAP_PATH`; -- 收到邮件 → 路由 token → 清洗正文 → `pty.write(cmd + '\r')`; -- 监控 `pty.onData`,必要时截取摘要回传提醒(可选)。 - -### 4.3 关键代码骨架(`relay-pty.js`) -```js -import { ImapFlow } from 'imapflow'; -import { simpleParser } from 'mailparser'; -import pino from 'pino'; -import { readFileSync, writeFileSync, existsSync } from 'fs'; -import { spawn as spawnPty } from 'node-pty'; -import dotenv from 'dotenv'; - -dotenv.config(); -const log = pino({ level: 'info' }); -const SESS_PATH = process.env.SESSION_MAP_PATH; - -function loadSessions() { - if (!existsSync(SESS_PATH)) return {}; - return JSON.parse(readFileSync(SESS_PATH, 'utf8')); -} -function saveSessions(map) { - writeFileSync(SESS_PATH, JSON.stringify(map, null, 2)); -} - -function normalizeAllowlist() { - return (process.env.ALLOWED_SENDERS || '') - .split(',') - .map(s => s.trim().toLowerCase()) - .filter(Boolean); -} -const ALLOW = new Set(normalizeAllowlist()); - -function isAllowed(addressObj) { - const list = [] - .concat(addressObj?.value || []) - .map(a => (a.address || '').toLowerCase()); - return list.some(addr => ALLOW.has(addr)); -} - -function extractTokenFromSubject(subject = '') { - const m = subject.match(/\[TaskPing\s+#([A-Za-z0-9_-]+)\]/); - return m ? m[1] : null; -} - -function stripReply(text = '') { - // 优先识别 ``` 块 - const codeBlock = text.match(/```[\s\S]*?```/); - if (codeBlock) { - return codeBlock[0].replace(/```/g, '').trim(); - } - // 取首行或以 CMD: 前缀 - const firstLine = text.split(/\r?\n/).find(l => l.trim().length > 0) || ''; - const cmdPrefix = firstLine.match(/^CMD:\s*(.+)$/i); - const candidate = (cmdPrefix ? cmdPrefix[1] : firstLine).trim(); - - // 去除引用与签名 - return candidate - .replace(/^>.*$/gm, '') - .replace(/^--\s+[\s\S]*$/m, '') - .slice(0, 8192) // 长度限制 - .trim(); -} - -async function handleMailMessage(source) { - const parsed = await simpleParser(source); - if (!isAllowed(parsed.from)) { - log.warn({ from: parsed.from?.text }, 'sender not allowed'); - return; - } - - const subject = parsed.subject || ''; - const token = extractTokenFromSubject(subject); - if (!token) { - log.warn({ subject }, 'no token in subject'); - return; - } - - const sessions = loadSessions(); - const sess = sessions[token]; - if (!sess || sess.expiresAt * 1000 < Date.now()) { - log.warn({ token }, 'session not found or expired'); - return; - } - if (sess.type !== 'pty' || !sess.ptyId) { - log.error({ token }, 'session is not pty type'); - return; - } - - const cmd = stripReply(parsed.text || parsed.html || ''); - if (!cmd) { - log.warn({ token }, 'empty command after strip'); - return; - } - - // 取得 pty 实例:这里演示为“按需重建/保持单例”两种策略之一 - const pty = getOrRestorePty(sess); - log.info({ token, cmd: cmd.slice(0, 120) }, 'inject command'); - pty.write(cmd + '\r'); -} - -const PTY_POOL = new Map(); -function getOrRestorePty(sess) { - if (PTY_POOL.has(sess.ptyId)) return PTY_POOL.get(sess.ptyId); - - // 如果需要重建,会话应当保存启动参数;这里演示简化为新起一个 claude - const shell = 'claude'; // 或绝对路径 - const pty = spawnPty(shell, [], { - name: 'xterm-color', - cols: 120, - rows: 32 - }); - PTY_POOL.set(sess.ptyId, pty); - pty.onData(d => process.stdout.write(d)); // 可替换为日志/回传 - pty.onExit(() => PTY_POOL.delete(sess.ptyId)); - return pty; -} - -async function startImap() { - const client = new ImapFlow({ - host: process.env.IMAP_HOST, - port: Number(process.env.IMAP_PORT || 993), - secure: process.env.IMAP_SECURE === 'true', - auth: { user: process.env.IMAP_USER, pass: process.env.IMAP_PASS } - }); - await client.connect(); - const lock = await client.getMailboxLock('INBOX'); - try { - for await (const msg of client.monitor()) { - if (msg.type === 'exists') { - const { uid } = msg; - const { source } = await client.download('INBOX', uid); - const chunks = []; - for await (const c of source) chunks.push(c); - await handleMailMessage(Buffer.concat(chunks)); - } - } - } finally { - lock.release(); - } -} - -if (process.env.INJECTION_MODE === 'pty') { - startImap().catch(err => { - console.error(err); - process.exit(1); - }); -} -``` - -> 说明:生产化时,建议在**发送提醒**一侧创建会话并持久化 `ptyId` 与参数;这里演示了“按需复原”的简化路径。 - -### 4.4 启动 -```bash -node relay-pty.js -``` - -### 4.5 验收清单 -- [ ] 未授权邮箱无法触发注入; -- [ ] 合法邮件写入后,Claude Code 能立即进入下一步; -- [ ] 重复转发的同一邮件不会二次注入; -- [ ] 过期会话拒绝注入; -- [ ] 大段/HTML/带签名的邮件能正确抽取命令。 - ---- - -## 5. 方案二:tmux 模式(简单稳妥) - -### 5.1 适用场景 -- 你已经在 `tmux` 里运行 Claude Code; -- 希望由外部进程向**指定 pane** 注入按键,不改变现有启动流程。 - -### 5.2 实现思路 -- 为每个 token 创建/记录一个 `tmux` session 与 pane:`session = taskping-`; -- Claude Code 在该 pane 里运行; -- 收到邮件 → 定位到 pane → `tmux send-keys -t "" Enter`。 - -### 5.3 管理脚本(`tmux-utils.sh`) -```bash -#!/usr/bin/env bash -set -euo pipefail - -SESSION="$1" # 例如 taskping-ABC123 -CMD="${2:-}" # 可选:一次性注入命令 - -if ! tmux has-session -t "${SESSION}" 2>/dev/null; then - tmux new-session -d -s "${SESSION}" - tmux rename-window -t "${SESSION}:0" "claude" - tmux send-keys -t "${SESSION}:0" "claude" C-m - sleep 0.5 -fi - -if [ -n "${CMD}" ]; then - tmux send-keys -t "${SESSION}:0" "${CMD}" C-m -fi - -# 输出 pane 目标名,供上层程序记录 -echo "${SESSION}.0" -``` - -### 5.4 注入程序(`relay-tmux.js`) -```js -import { ImapFlow } from 'imapflow'; -import { simpleParser } from 'mailparser'; -import pino from 'pino'; -import { readFileSync, writeFileSync, existsSync } from 'fs'; -import dotenv from 'dotenv'; -import { execa } from 'execa'; - -dotenv.config(); -const log = pino({ level: 'info' }); -const SESS_PATH = process.env.SESSION_MAP_PATH; - -function loadSessions() { - if (!existsSync(SESS_PATH)) return {}; - return JSON.parse(readFileSync(SESS_PATH, 'utf8')); -} - -function extractTokenFromSubject(subject = '') { - const m = subject.match(/\[TaskPing\s+#([A-Za-z0-9_-]+)\]/); - return m ? m[1] : null; -} - -function stripReply(text = '') { - const codeBlock = text.match(/```[\s\S]*?```/); - if (codeBlock) return codeBlock[0].replace(/```/g, '').trim(); - const firstLine = text.split(/\r?\n/).find(l => l.trim().length > 0) || ''; - const cmdPrefix = firstLine.match(/^CMD:\s*(.+)$/i); - return (cmdPrefix ? cmdPrefix[1] : firstLine).trim().slice(0, 8192); -} - -async function injectToTmux(sessionName, cmd) { - const utils = new URL('./tmux-utils.sh', import.meta.url).pathname; - const { stdout } = await execa('bash', [utils, sessionName, cmd], { stdio: 'pipe' }); - return stdout.trim(); // 返回 pane 目标名 -} - -async function startImap() { - const client = new ImapFlow({ - host: process.env.IMAP_HOST, - port: Number(process.env.IMAP_PORT || 993), - secure: process.env.IMAP_SECURE === 'true', - auth: { user: process.env.IMAP_USER, pass: process.env.IMAP_PASS } - }); - await client.connect(); - await client.mailboxOpen('INBOX'); - - for await (const msg of client.monitor()) { - if (msg.type !== 'exists') continue; - const { uid } = msg; - const { source } = await client.download('INBOX', uid); - const chunks = []; - for await (const c of source) chunks.push(c); - const parsed = await simpleParser(Buffer.concat(chunks)); - - const token = extractTokenFromSubject(parsed.subject || ''); - if (!token) continue; - - const cmd = stripReply(parsed.text || parsed.html || ''); - if (!cmd) continue; - - const sessionName = `${process.env.TMUX_SESSION_PREFIX || 'taskping'}-${token}`; - const pane = await injectToTmux(sessionName, cmd); - console.log(`[inject] ${sessionName} -> ${pane} :: ${cmd.slice(0, 120)}`); - } -} - -if (process.env.INJECTION_MODE === 'tmux') { - startImap().catch(err => { - console.error(err); - process.exit(1); - }); -} -``` - -### 5.5 启动 -```bash -chmod +x tmux-utils.sh -INJECTION_MODE=tmux node relay-tmux.js -``` - -### 5.6 验收清单 -- [ ] `tmux` 中能看到 Claude 进程始终在运行; -- [ ] 邮件回复注入后,当前 pane 光标位置正确、命令无截断; -- [ ] 会话名与 token 一一对应,过期会话自动清理; -- [ ] `tmux` 重启或 SSH 断连后自动恢复。 - ---- - -## 6. 选择建议与权衡 - -| 维度 | 子进程/PTY | tmux | -|---|---|---| -| 复杂度 | 中 | 低 | -| 稳定性 | 高(直连伪终端) | 高(依赖 tmux 自愈) | -| 多会话并发 | 简单(多 PTY) | 简单(多 pane/session) | -| 恢复/重连 | 需自管 | `tmux` 自带 | -| 可移植性 | 受 pty/Windows 影响 | Linux/macOS 友好 | -| 外部可观测 | 需自行实现 | `tmux capture-pane` 可用 | - -**建议**:桌面或服务器长期常驻,优先 **PTY 模式**;已广泛使用 `tmux` 的团队,直接 **tmux 模式**。 - ---- - -## 7. 测试用例(AI 执行) - -1. **路由正确性**:构造两个 token(A、B),分别回复不同命令,验证注入不串台。 -2. **HTML 邮件**:从 iOS/Android 客户端发送富文本,确认能抽取纯文本命令。 -3. **代码块优先**:正文包含代码块和普通文本时,优先选用代码块内容。 -4. **幂等**:同一封邮件重复投递三次,仅执行一次。 -5. **过期处理**:将 `expiresAt` 回退到过去,验证拒绝注入。 -6. **白名单**:非授权邮箱发送,日志报警且不注入。 -7. **超长命令**:超过 8KB 截断并拒绝,返回提示。 - ---- - -## 8. 运行与守护 - -- 建议用 `pm2`/`systemd` 管理 `relay-pty.js` 或 `relay-tmux.js`; -- 每 20 分钟向 IMAP 发送 NOOP(ImapFlow monitor 已处理),断线自动重连; -- 定时清理过期会话与孤儿 `tmux` session / PTY。 - ---- - -## 9. 故障排查 - -- **注入后无响应**:检查会话路由是否匹配(token、pane/pty);确认 Claude 是否在**等待输入**的状态。 -- **乱码/截断**:调大 pty 列数;tmux 保证 pane 有焦点且无行编辑冲突。 -- **重复执行**:确认以 `Message-ID` 去重;避免多客户端造成“已读后又转发”。 -- **权限问题**:`tmux` 由同一用户启动;Node 进程需与 `tmux` 同一用户。 - ---- - -## 10. 清单(AI 请按序执行) - -1. 安装依赖与 `tmux`(如需)。 -2. 写入 `.env`(IMAP/白名单/模式)。 -3. 准备并加载 `SESSION_MAP_PATH`(由发送提醒侧写入)。 -4. 选择 **INJECTION_MODE=pty** 或 **tmux**,启动对应 `relay-*.js`。 -5. 发送带 `[TaskPing #TOKEN]` 的提醒邮件,收到后**回复命令**进行回归测试。 -6. 通过测试清单后,接入 hooks,开启常驻与告警监控。 diff --git a/send-test-reply.js b/send-test-reply.js new file mode 100644 index 0000000..aed9031 --- /dev/null +++ b/send-test-reply.js @@ -0,0 +1,49 @@ +/** + * 发送测试邮件回复到relay服务 + */ + +const nodemailer = require('nodemailer'); +require('dotenv').config(); + +async function sendTestReply() { + console.log('📧 发送测试邮件回复...\n'); + + // 创建测试用的SMTP传输器(使用Gmail) + const transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: 'jiaxicui446@gmail.com', + pass: process.env.GMAIL_APP_PASSWORD || 'your-app-password' + } + }); + + // 使用最新的token + const testToken = 'V5UPZ1UE'; // 来自session-map.json的最新token + + const mailOptions = { + from: 'jiaxicui446@gmail.com', + to: 'noreply@pandalla.ai', + subject: `Re: [TaskPing #${testToken}] Claude Code 任务完成 - TaskPing`, + text: '请解释一下量子计算的基本原理', + replyTo: 'jiaxicui446@gmail.com' + }; + + try { + const info = await transporter.sendMail(mailOptions); + console.log('✅ 测试邮件发送成功!'); + console.log(`📧 Message ID: ${info.messageId}`); + console.log(`📋 Token: ${testToken}`); + console.log(`💬 Command: ${mailOptions.text}`); + console.log('\n🔍 现在监控relay服务日志...'); + + // 等待几秒让邮件被处理 + setTimeout(() => { + console.log('\n📋 请检查relay-debug.log文件查看处理日志'); + }, 5000); + + } catch (error) { + console.error('❌ 邮件发送失败:', error.message); + } +} + +sendTestReply().catch(console.error); \ No newline at end of file diff --git a/setup-gmail-app-password.js b/setup-gmail-app-password.js deleted file mode 100755 index 9ed4946..0000000 --- a/setup-gmail-app-password.js +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env node - -/** - * Gmail 应用专用密码设置指南 - * 帮助用户配置 Gmail 的应用专用密码 - */ - -const fs = require('fs'); -const readline = require('readline'); - -function displayInstructions() { - console.log('╔═══════════════════════════════════════════════════════════════╗'); - console.log('║ Gmail 应用专用密码配置指南 ║'); - console.log('╚═══════════════════════════════════════════════════════════════╝\n'); - - console.log('📋 配置步骤:\n'); - - console.log('1️⃣ 登录 Gmail 账号'); - console.log(' 🌐 访问: https://myaccount.google.com/'); - console.log(' 👤 使用您的账号: jiaxicui446@gmail.com\n'); - - console.log('2️⃣ 开启两步验证(如果未开启)'); - console.log(' 🌐 访问: https://myaccount.google.com/security'); - console.log(' 🔐 找到"两步验证"并开启'); - console.log(' 📱 可以使用手机短信或认证器应用\n'); - - console.log('3️⃣ 生成应用专用密码'); - console.log(' 🌐 访问: https://myaccount.google.com/apppasswords'); - console.log(' 📧 选择应用: "邮件"'); - console.log(' 💻 选择设备: "其他(自定义名称)"'); - console.log(' ✏️ 输入名称: "TaskPing Email Relay"'); - console.log(' 🔑 点击"生成"'); - console.log(' 📋 复制 16 位密码(格式类似:abcd efgh ijkl mnop)\n'); - - console.log('4️⃣ 启用 IMAP(如果未启用)'); - console.log(' 🌐 访问: https://mail.google.com/mail/u/0/#settings/fwdandpop'); - console.log(' 📩 找到"IMAP 访问"'); - console.log(' ✅ 选择"启用 IMAP"'); - console.log(' 💾 保存更改\n'); - - console.log('⚠️ 重要提示:'); - console.log(' • 应用专用密码只显示一次,请妥善保存'); - console.log(' • 不要使用您的 Google 账号密码'); - console.log(' • 密码格式是 16 位,包含空格(请保留空格)'); - console.log(' • 如果忘记密码,需要删除后重新生成\n'); -} - -async function promptForPassword() { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); - - return new Promise((resolve) => { - console.log('📝 请输入您生成的应用专用密码:'); - console.log(' (格式: abcd efgh ijkl mnop)'); - rl.question('密码: ', (password) => { - rl.close(); - resolve(password.trim()); - }); - }); -} - -function validatePassword(password) { - // Gmail 应用专用密码格式:16位字符,可能包含空格 - const cleanPassword = password.replace(/\s/g, ''); - - if (cleanPassword.length !== 16) { - return { - valid: false, - message: '密码长度应为16位字符' - }; - } - - if (!/^[a-zA-Z0-9]+$/.test(cleanPassword)) { - return { - valid: false, - message: '密码只能包含字母和数字' - }; - } - - return { - valid: true, - cleanPassword - }; -} - -function updateEnvFile(password) { - const envPath = '.env'; - - if (!fs.existsSync(envPath)) { - console.error('❌ 未找到 .env 文件'); - return false; - } - - try { - let content = fs.readFileSync(envPath, 'utf8'); - - // 替换 IMAP_PASS 行 - const updated = content.replace( - /IMAP_PASS=.*/, - `IMAP_PASS=${password}` - ); - - fs.writeFileSync(envPath, updated); - return true; - } catch (error) { - console.error('❌ 更新 .env 文件失败:', error.message); - return false; - } -} - -async function testConnection(password) { - console.log('\n🔍 测试 Gmail IMAP 连接...'); - - // 临时设置环境变量 - process.env.IMAP_PASS = password; - - try { - // 动态导入 ImapFlow - const { ImapFlow } = require('imapflow'); - - const client = new ImapFlow({ - host: 'imap.gmail.com', - port: 993, - secure: true, - auth: { - user: 'jiaxicui446@gmail.com', - pass: password - }, - logger: false - }); - - await client.connect(); - console.log('✅ Gmail IMAP 连接成功!'); - - const status = await client.status('INBOX', { messages: true, unseen: true }); - console.log(`📧 收件箱状态: ${status.messages} 封邮件,${status.unseen} 封未读`); - - await client.logout(); - return true; - - } catch (error) { - console.error('❌ 连接失败:', error.message); - - if (error.code === 'EAUTH') { - console.log('\n可能的原因:'); - console.log('• 应用专用密码错误'); - console.log('• 两步验证未开启'); - console.log('• IMAP 未启用'); - } - - return false; - } -} - -async function main() { - displayInstructions(); - - console.log('═'.repeat(65)); - console.log('现在开始配置应用专用密码\n'); - - // 检查是否已经完成网页配置 - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); - - const ready = await new Promise((resolve) => { - rl.question('已完成上述步骤并获得应用专用密码?(y/n): ', (answer) => { - rl.close(); - resolve(answer.toLowerCase() === 'y'); - }); - }); - - if (!ready) { - console.log('\n请先完成上述配置步骤,然后重新运行此脚本'); - return; - } - - // 获取密码 - const password = await promptForPassword(); - - if (!password) { - console.log('❌ 未输入密码'); - return; - } - - // 验证密码格式 - const validation = validatePassword(password); - if (!validation.valid) { - console.log(`❌ 密码格式错误: ${validation.message}`); - return; - } - - const cleanPassword = validation.cleanPassword; - console.log('✅ 密码格式正确'); - - // 测试连接 - const connected = await testConnection(cleanPassword); - - if (!connected) { - console.log('\n请检查配置后重试'); - return; - } - - // 更新 .env 文件 - const updated = updateEnvFile(cleanPassword); - - if (updated) { - console.log('\n✅ 配置已保存到 .env 文件'); - console.log('\n🚀 下一步操作:'); - console.log(' 1. 运行: npm run email:config'); - console.log(' 2. 运行: npm run email:test'); - console.log(' 3. 运行: npm run relay:pty'); - console.log('\n🎉 Gmail 应用专用密码配置完成!'); - } else { - console.log('\n❌ 保存配置失败,请手动编辑 .env 文件'); - console.log(` IMAP_PASS=${cleanPassword}`); - } -} - -// 运行 -main().catch(console.error); \ No newline at end of file diff --git a/setup-permissions.js b/setup-permissions.js deleted file mode 100755 index fa3d09d..0000000 --- a/setup-permissions.js +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env node - -/** - * macOS 权限设置助手 - * 帮助用户设置必要的系统权限 - */ - -const { spawn } = require('child_process'); - -class PermissionSetup { - constructor() { - this.requiredPermissions = [ - 'Accessibility', - 'Automation' - ]; - } - - async checkAndSetup() { - console.log('🔒 macOS 权限设置助手\n'); - - if (process.platform !== 'darwin') { - console.log('ℹ️ 此工具仅适用于 macOS 系统'); - return; - } - - console.log('TaskPing 需要以下权限才能自动粘贴邮件回复到 Claude Code:\n'); - - console.log('1. 🖱️ 辅助功能权限 (Accessibility)'); - console.log(' - 允许控制其他应用程序'); - console.log(' - 自动输入和点击'); - - console.log('\n2. 🤖 自动化权限 (Automation)'); - console.log(' - 允许发送 Apple Events'); - console.log(' - 控制应用程序行为\n'); - - // 检查当前权限状态 - await this.checkCurrentPermissions(); - - console.log('📋 设置步骤:\n'); - - console.log('步骤 1: 打开系统偏好设置'); - console.log(' → 苹果菜单 > 系统偏好设置 > 安全性与隐私 > 隐私\n'); - - console.log('步骤 2: 设置辅助功能权限'); - console.log(' → 点击左侧 "辅助功能"'); - console.log(' → 点击锁图标并输入密码'); - console.log(' → 添加以下应用:'); - console.log(' • Terminal (如果你在 Terminal 中运行 TaskPing)'); - console.log(' • iTerm2 (如果使用 iTerm2)'); - console.log(' • Visual Studio Code (如果在 VS Code 中运行)'); - console.log(' • 或者你当前使用的终端应用\n'); - - console.log('步骤 3: 设置自动化权限'); - console.log(' → 点击左侧 "自动化"'); - console.log(' → 在你的终端应用下勾选:'); - console.log(' • System Events'); - console.log(' • Claude Code (如果有的话)'); - console.log(' • Terminal\n'); - - console.log('🚀 快速打开系统偏好设置?'); - const readline = require('readline'); - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); - - const answer = await this.question(rl, '是否现在打开系统偏好设置?(y/n): '); - - if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') { - await this.openSystemPreferences(); - } - - rl.close(); - - console.log('\n✅ 设置完成后,重新运行以下命令测试:'); - console.log(' node taskping.js test-paste\n'); - } - - async checkCurrentPermissions() { - console.log('🔍 检查当前权限状态...\n'); - - try { - // 测试基本的 AppleScript 执行 - const result = await this.runAppleScript('tell application "System Events" to return name of first process'); - if (result) { - console.log('✅ 基本 AppleScript 权限:正常'); - } - } catch (error) { - console.log('❌ 基本 AppleScript 权限:需要设置'); - } - - try { - // 测试辅助功能权限 - const result = await this.runAppleScript(` - tell application "System Events" - try - return name of first application process whose frontmost is true - on error - return "permission_denied" - end try - end tell - `); - - if (result && result !== 'permission_denied') { - console.log('✅ 辅助功能权限:已授权'); - } else { - console.log('❌ 辅助功能权限:需要授权'); - } - } catch (error) { - console.log('❌ 辅助功能权限:需要授权'); - } - - console.log(''); - } - - async runAppleScript(script) { - return new Promise((resolve, reject) => { - const osascript = spawn('osascript', ['-e', script]); - let output = ''; - let error = ''; - - osascript.stdout.on('data', (data) => { - output += data.toString(); - }); - - osascript.stderr.on('data', (data) => { - error += data.toString(); - }); - - osascript.on('close', (code) => { - if (code === 0) { - resolve(output.trim()); - } else { - reject(new Error(error || 'AppleScript execution failed')); - } - }); - }); - } - - async openSystemPreferences() { - try { - console.log('\n🔧 正在打开系统偏好设置...'); - - // 直接打开安全性与隐私 > 隐私 > 辅助功能 - const script = ` - tell application "System Preferences" - activate - set current pane to pane "com.apple.preference.security" - delay 1 - tell application "System Events" - tell window 1 of application process "System Preferences" - click tab "Privacy" - delay 0.5 - tell outline 1 of scroll area 1 - select row "Accessibility" - end tell - end tell - end tell - end tell - `; - - await this.runAppleScript(script); - console.log('✅ 已打开辅助功能设置页面'); - - } catch (error) { - console.log('⚠️ 无法自动打开,请手动打开系统偏好设置'); - console.log('💡 你也可以运行:open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"'); - } - } - - question(rl, prompt) { - return new Promise(resolve => { - rl.question(prompt, resolve); - }); - } -} - -// 运行权限设置助手 -if (require.main === module) { - const setup = new PermissionSetup(); - setup.checkAndSetup().catch(console.error); -} - -module.exports = PermissionSetup; \ No newline at end of file diff --git a/simple-start.js b/simple-start.js deleted file mode 100755 index 52b3e04..0000000 --- a/simple-start.js +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env node - -/** - * TaskPing 简化启动脚本 - * 专注解决核心问题,减少复杂性 - */ - -const fs = require('fs'); -const path = require('path'); -const SimplifiedEmailAutomation = require('./src/simplified/email-automation'); - -class SimpleTaskPing { - constructor() { - this.configPath = path.join(__dirname, 'config/channels.json'); - this.automation = null; - } - - async start() { - console.log('🚀 TaskPing 简化版启动中...\n'); - - try { - // 加载配置 - const config = this.loadConfig(); - if (!config) { - console.log('❌ 配置加载失败,请先配置邮件'); - console.log('💡 运行: npm run config'); - process.exit(1); - } - - // 验证配置 - if (!this.validateConfig(config)) { - console.log('❌ 邮件配置不完整,请重新配置'); - console.log('💡 运行: npm run config'); - process.exit(1); - } - - console.log('✅ 配置验证通过'); - console.log(`📧 邮箱: ${config.email.config.imap.auth.user}`); - console.log(`📬 通知发送到: ${config.email.config.to}\n`); - - // 启动简化的邮件自动化 - this.automation = new SimplifiedEmailAutomation(config.email.config); - - // 设置事件监听 - this.automation.on('commandExecuted', (data) => { - console.log(`\n🎉 成功处理邮件命令 (邮件 ${data.emailSeq})`); - console.log('💡 命令已复制到剪贴板,请在 Claude Code 中粘贴'); - }); - - this.automation.on('commandFailed', (data) => { - console.log(`\n❌ 处理命令失败 (邮件 ${data.emailSeq}): ${data.error.message}`); - }); - - // 启动服务 - await this.automation.start(); - - console.log('\n🎯 TaskPing 简化版运行中...'); - console.log('💌 现在你可以回复 TaskPing 邮件来发送命令了'); - console.log('📋 命令会自动复制到剪贴板,你只需要在 Claude Code 中粘贴'); - console.log('\n按 Ctrl+C 停止服务\n'); - - // 处理优雅关闭 - process.on('SIGINT', async () => { - console.log('\n🛑 正在停止 TaskPing...'); - if (this.automation) { - await this.automation.stop(); - } - console.log('✅ 服务已停止'); - process.exit(0); - }); - - // 保持进程运行 - process.stdin.resume(); - - } catch (error) { - console.error('❌ 启动失败:', error.message); - process.exit(1); - } - } - - loadConfig() { - try { - if (!fs.existsSync(this.configPath)) { - console.log('❌ 配置文件不存在'); - return null; - } - - const data = fs.readFileSync(this.configPath, 'utf8'); - return JSON.parse(data); - } catch (error) { - console.error('❌ 配置文件读取失败:', error.message); - return null; - } - } - - validateConfig(config) { - if (!config.email || !config.email.enabled) { - console.log('❌ 邮件功能未启用'); - return false; - } - - const emailConfig = config.email.config; - - // 检查必需字段 - const required = [ - 'smtp.host', - 'smtp.auth.user', - 'smtp.auth.pass', - 'imap.host', - 'imap.auth.user', - 'imap.auth.pass', - 'to' - ]; - - for (const field of required) { - if (!this.getNestedValue(emailConfig, field)) { - console.log(`❌ 缺少必需配置: ${field}`); - return false; - } - } - - return true; - } - - getNestedValue(obj, path) { - return path.split('.').reduce((current, key) => { - return current && current[key] !== undefined ? current[key] : undefined; - }, obj); - } - - async status() { - if (!this.automation) { - console.log('❌ 服务未运行'); - return; - } - - const status = this.automation.getStatus(); - console.log('📊 TaskPing 简化版状态:'); - console.log(` 运行状态: ${status.running ? '✅ 运行中' : '❌ 已停止'}`); - console.log(` IMAP 连接: ${status.connected ? '✅ 已连接' : '❌ 未连接'}`); - console.log(` 命令文件: ${status.commandFile}`); - console.log(` 最新命令: ${status.lastCommandExists ? '✅ 有' : '❌ 无'}`); - } - - showHelp() { - console.log(` -🚀 TaskPing 简化版 - -用法: node simple-start.js [命令] - -命令: - start 启动邮件监听服务 (默认) - status 显示服务状态 - help 显示帮助信息 - -配置: - npm run config 交互式配置邮件 - -特点: - • 🎯 专注核心功能,减少复杂性 - • 📧 稳定的邮件监听和解析 - • 📋 自动复制命令到剪贴板 - • 🔔 友好的通知提醒 - • 🛡️ 降级方案,减少权限依赖 - -使用流程: - 1. 配置邮箱: npm run config - 2. 启动服务: node simple-start.js - 3. 回复 TaskPing 邮件发送命令 - 4. 在 Claude Code 中粘贴命令 (Cmd+V) - `); - } -} - -// 处理命令行参数 -const args = process.argv.slice(2); -const command = args[0] || 'start'; - -const taskping = new SimpleTaskPing(); - -switch (command) { - case 'start': - taskping.start(); - break; - case 'status': - taskping.status(); - break; - case 'help': - case '--help': - case '-h': - taskping.showHelp(); - break; - default: - console.log(`未知命令: ${command}`); - taskping.showHelp(); - process.exit(1); -} \ No newline at end of file diff --git a/src/channels/email/smtp.js b/src/channels/email/smtp.js index 2b0b8f9..ff6c64c 100644 --- a/src/channels/email/smtp.js +++ b/src/channels/email/smtp.js @@ -8,6 +8,8 @@ const nodemailer = require('nodemailer'); const { v4: uuidv4 } = require('uuid'); const path = require('path'); const fs = require('fs'); +const TmuxMonitor = require('../../utils/tmux-monitor'); +const { execSync } = require('child_process'); class EmailChannel extends NotificationChannel { constructor(config = {}) { @@ -15,6 +17,7 @@ class EmailChannel extends NotificationChannel { this.transporter = null; this.sessionsDir = path.join(__dirname, '../../data/sessions'); this.templatesDir = path.join(__dirname, '../../assets/email-templates'); + this.tmuxMonitor = new TmuxMonitor(); this._ensureDirectories(); this._initializeTransporter(); @@ -29,6 +32,16 @@ class EmailChannel extends NotificationChannel { } } + _generateToken() { + // 生成简短的Token (大写字母+数字,8位) + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + let token = ''; + for (let i = 0; i < 8; i++) { + token += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return token; + } + _initializeTransporter() { if (!this.config.smtp) { this.logger.warn('SMTP configuration not found'); @@ -56,6 +69,21 @@ class EmailChannel extends NotificationChannel { } } + _getCurrentTmuxSession() { + try { + // Try to get current tmux session + const tmuxSession = execSync('tmux display-message -p "#S"', { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'] + }).trim(); + + return tmuxSession || null; + } catch (error) { + // Not in a tmux session or tmux not available + return null; + } + } + async _sendImpl(notification) { if (!this.transporter) { throw new Error('Email transporter not initialized'); @@ -65,14 +93,26 @@ class EmailChannel extends NotificationChannel { throw new Error('Email recipient not configured'); } - // 生成会话ID + // 生成会话ID和Token const sessionId = uuidv4(); + const token = this._generateToken(); + + // 获取当前tmux会话和对话内容 + const tmuxSession = this._getCurrentTmuxSession(); + if (tmuxSession && !notification.metadata) { + const conversation = this.tmuxMonitor.getRecentConversation(tmuxSession); + notification.metadata = { + userQuestion: conversation.userQuestion || notification.message, + claudeResponse: conversation.claudeResponse || notification.message, + tmuxSession: tmuxSession + }; + } // 创建会话记录 - await this._createSession(sessionId, notification); + await this._createSession(sessionId, notification, token); // 生成邮件内容 - const emailContent = this._generateEmailContent(notification, sessionId); + const emailContent = this._generateEmailContent(notification, sessionId, token); const mailOptions = { from: this.config.from || this.config.smtp.auth.user, @@ -99,11 +139,16 @@ class EmailChannel extends NotificationChannel { } } - async _createSession(sessionId, notification) { + async _createSession(sessionId, notification, token) { const session = { id: sessionId, + token: token, + type: 'pty', created: new Date().toISOString(), expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // 24小时后过期 + createdAt: Math.floor(Date.now() / 1000), + expiresAt: Math.floor((Date.now() + 24 * 60 * 60 * 1000) / 1000), + cwd: process.cwd(), notification: { type: notification.type, project: notification.project, @@ -117,7 +162,39 @@ class EmailChannel extends NotificationChannel { const sessionFile = path.join(this.sessionsDir, `${sessionId}.json`); fs.writeFileSync(sessionFile, JSON.stringify(session, null, 2)); - this.logger.debug(`Session created: ${sessionId}`); + // 同时保存到PTY映射格式 + const sessionMapPath = process.env.SESSION_MAP_PATH || path.join(__dirname, '../../data/session-map.json'); + let sessionMap = {}; + if (fs.existsSync(sessionMapPath)) { + try { + sessionMap = JSON.parse(fs.readFileSync(sessionMapPath, 'utf8')); + } catch (e) { + sessionMap = {}; + } + } + + // 使用传入的tmux会话名称或检测当前会话 + let tmuxSession = notification.metadata?.tmuxSession || this._getCurrentTmuxSession() || 'claude-taskping'; + + sessionMap[token] = { + type: 'pty', + createdAt: Math.floor(Date.now() / 1000), + expiresAt: Math.floor((Date.now() + 24 * 60 * 60 * 1000) / 1000), + cwd: process.cwd(), + sessionId: sessionId, + tmuxSession: tmuxSession, + description: `${notification.type} - ${notification.project}` + }; + + // 确保目录存在 + const mapDir = path.dirname(sessionMapPath); + if (!fs.existsSync(mapDir)) { + fs.mkdirSync(mapDir, { recursive: true }); + } + + fs.writeFileSync(sessionMapPath, JSON.stringify(sessionMap, null, 2)); + + this.logger.debug(`Session created: ${sessionId}, Token: ${token}`); } async _removeSession(sessionId) { @@ -128,20 +205,50 @@ class EmailChannel extends NotificationChannel { } } - _generateEmailContent(notification, sessionId) { + _generateEmailContent(notification, sessionId, token) { const template = this._getTemplate(notification.type); const timestamp = new Date().toLocaleString('zh-CN'); + // 获取项目目录名(最后一级目录) + const projectDir = path.basename(process.cwd()); + + // 提取用户问题(从notification.metadata中获取,如果有的话) + let userQuestion = ''; + let claudeResponse = ''; + + if (notification.metadata) { + userQuestion = notification.metadata.userQuestion || ''; + claudeResponse = notification.metadata.claudeResponse || ''; + } + + // 限制用户问题长度用于标题 + const maxQuestionLength = 30; + const shortQuestion = userQuestion.length > maxQuestionLength ? + userQuestion.substring(0, maxQuestionLength) + '...' : userQuestion; + + // 生成更具辨识度的标题 + let enhancedSubject = template.subject; + if (shortQuestion) { + enhancedSubject = enhancedSubject.replace('{{project}}', `${projectDir} | ${shortQuestion}`); + } else { + enhancedSubject = enhancedSubject.replace('{{project}}', projectDir); + } + // 模板变量替换 const variables = { - project: notification.project, + project: projectDir, message: notification.message, timestamp: timestamp, sessionId: sessionId, - type: notification.type === 'completed' ? '任务完成' : '等待输入' + token: token, + type: notification.type === 'completed' ? '任务完成' : '等待输入', + userQuestion: userQuestion || '未指定任务', + claudeResponse: claudeResponse || notification.message, + projectDir: projectDir, + shortQuestion: shortQuestion || '无具体问题' }; - let subject = template.subject; + let subject = enhancedSubject; let html = template.html; let text = template.text; @@ -160,7 +267,7 @@ class EmailChannel extends NotificationChannel { // 默认模板 const templates = { completed: { - subject: '[TaskPing] Claude Code 任务完成 - {{project}}', + subject: '[TaskPing #{{token}}] Claude Code 任务完成 - {{project}}', html: `
@@ -170,14 +277,20 @@ class EmailChannel extends NotificationChannel {

- 项目: {{project}}
+ 项目: {{projectDir}}
时间: {{timestamp}}
状态: {{type}}

+
+

📝 您的问题

+

{{userQuestion}}

+
+
-

{{message}}

+

🤖 Claude 的回复

+

{{claudeResponse}}

@@ -202,13 +315,17 @@ class EmailChannel extends NotificationChannel {
`, text: ` -[TaskPing] Claude Code 任务完成 - {{project}} +[TaskPing #{{token}}] Claude Code 任务完成 - {{projectDir}} | {{shortQuestion}} -项目: {{project}} +项目: {{projectDir}} 时间: {{timestamp}} 状态: {{type}} -消息: {{message}} +📝 您的问题: +{{userQuestion}} + +🤖 Claude 的回复: +{{claudeResponse}} 如何继续对话: 要继续与 Claude Code 对话,请直接回复此邮件,在邮件正文中输入您的指令。 @@ -223,7 +340,7 @@ class EmailChannel extends NotificationChannel { ` }, waiting: { - subject: '[TaskPing] Claude Code 等待输入 - {{project}}', + subject: '[TaskPing #{{token}}] Claude Code 等待输入 - {{project}}', html: `
@@ -233,13 +350,14 @@ class EmailChannel extends NotificationChannel {

- 项目: {{project}}
+ 项目: {{projectDir}}
时间: {{timestamp}}
状态: {{type}}

+

⏳ 等待处理

{{message}}

@@ -259,13 +377,13 @@ class EmailChannel extends NotificationChannel {
`, text: ` -[TaskPing] Claude Code 等待输入 - {{project}} +[TaskPing #{{token}}] Claude Code 等待输入 - {{projectDir}} -项目: {{project}} +项目: {{projectDir}} 时间: {{timestamp}} 状态: {{type}} -消息: {{message}} +⏳ 等待处理: {{message}} Claude 需要您的进一步指导。请回复此邮件告诉 Claude 下一步应该做什么。 diff --git a/src/data/processed-messages.json b/src/data/processed-messages.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/src/data/processed-messages.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/data/relay-state.json b/src/data/relay-state.json deleted file mode 100644 index 6456524..0000000 --- a/src/data/relay-state.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "commandQueue": [], - "lastSaved": "2025-07-12T22:26:32.914Z" -} \ No newline at end of file diff --git a/src/data/session-map.json b/src/data/session-map.json index 2e23cf5..9e26dfe 100644 --- a/src/data/session-map.json +++ b/src/data/session-map.json @@ -1,23 +1 @@ -{ - "TESTMDKLS3MK": { - "type": "pty", - "createdAt": 1753555687, - "expiresAt": 1753559287, - "cwd": "/Users/jessytsui/dev/TaskPing", - "description": "测试会话" - }, - "TESTMDKM0Q3M": { - "type": "pty", - "createdAt": 1753556089, - "expiresAt": 1753559689, - "cwd": "/Users/jessytsui/dev/TaskPing", - "description": "测试会话" - }, - "TESTMDKMI9XE": { - "type": "pty", - "createdAt": 1753556908, - "expiresAt": 1753560508, - "cwd": "/Users/jessytsui/dev/TaskPing", - "description": "测试会话" - } -} \ No newline at end of file +{} \ No newline at end of file diff --git a/src/data/sessions/01a2841c-a865-4465-b462-32ddcfb7111f.json b/src/data/sessions/01a2841c-a865-4465-b462-32ddcfb7111f.json deleted file mode 100644 index 4e1dc54..0000000 --- a/src/data/sessions/01a2841c-a865-4465-b462-32ddcfb7111f.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "01a2841c-a865-4465-b462-32ddcfb7111f", - "created": "2025-07-18T10:19:27.379Z", - "expires": "2025-07-19T10:19:27.379Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/01eaaf76-ff26-4bec-a488-cdc8508d9779.json b/src/data/sessions/01eaaf76-ff26-4bec-a488-cdc8508d9779.json deleted file mode 100644 index 3a0774a..0000000 --- a/src/data/sessions/01eaaf76-ff26-4bec-a488-cdc8508d9779.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "01eaaf76-ff26-4bec-a488-cdc8508d9779", - "created": "2025-07-13T17:38:51.925Z", - "expires": "2025-07-14T17:38:51.925Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/0232a3b7-37ca-413a-be8d-77bf9e88f898.json b/src/data/sessions/0232a3b7-37ca-413a-be8d-77bf9e88f898.json deleted file mode 100644 index f05ed9e..0000000 --- a/src/data/sessions/0232a3b7-37ca-413a-be8d-77bf9e88f898.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "0232a3b7-37ca-413a-be8d-77bf9e88f898", - "created": "2025-07-23T12:20:51.786Z", - "expires": "2025-07-24T12:20:51.786Z", - "notification": { - "type": "completed", - "project": "video_tag", - "message": "[video_tag] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/0268b1c3-9be8-4c7f-b7f6-aec0baca70df.json b/src/data/sessions/0268b1c3-9be8-4c7f-b7f6-aec0baca70df.json deleted file mode 100644 index 88c6292..0000000 --- a/src/data/sessions/0268b1c3-9be8-4c7f-b7f6-aec0baca70df.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "0268b1c3-9be8-4c7f-b7f6-aec0baca70df", - "created": "2025-07-23T22:15:19.656Z", - "expires": "2025-07-24T22:15:19.656Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/035c7c3d-8dcc-4f13-9e3b-cd5885e1a27e.json b/src/data/sessions/035c7c3d-8dcc-4f13-9e3b-cd5885e1a27e.json deleted file mode 100644 index 0c7e059..0000000 --- a/src/data/sessions/035c7c3d-8dcc-4f13-9e3b-cd5885e1a27e.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "035c7c3d-8dcc-4f13-9e3b-cd5885e1a27e", - "created": "2025-07-18T12:36:48.562Z", - "expires": "2025-07-19T12:36:48.562Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/047e6c5b-b403-43dd-89c2-f3a154b386d0.json b/src/data/sessions/047e6c5b-b403-43dd-89c2-f3a154b386d0.json deleted file mode 100644 index 94278a8..0000000 --- a/src/data/sessions/047e6c5b-b403-43dd-89c2-f3a154b386d0.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "047e6c5b-b403-43dd-89c2-f3a154b386d0", - "created": "2025-07-15T18:21:56.989Z", - "expires": "2025-07-16T18:21:56.989Z", - "notification": { - "type": "completed", - "project": "coverr_video", - "message": "[coverr_video] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/05ce5d20-928d-4cb6-b196-c298659dfae5.json b/src/data/sessions/05ce5d20-928d-4cb6-b196-c298659dfae5.json deleted file mode 100644 index 6e826ff..0000000 --- a/src/data/sessions/05ce5d20-928d-4cb6-b196-c298659dfae5.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "05ce5d20-928d-4cb6-b196-c298659dfae5", - "created": "2025-07-13T21:25:56.713Z", - "expires": "2025-07-14T21:25:56.713Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/07db2a97-0224-44e8-8b41-d352cfb5df37.json b/src/data/sessions/07db2a97-0224-44e8-8b41-d352cfb5df37.json deleted file mode 100644 index 04e24f3..0000000 --- a/src/data/sessions/07db2a97-0224-44e8-8b41-d352cfb5df37.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "07db2a97-0224-44e8-8b41-d352cfb5df37", - "created": "2025-07-12T23:29:10.943Z", - "expires": "2025-07-13T23:29:10.943Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/0ded7ffb-02bf-4c99-bef3-3ba7bb4cc15b.json b/src/data/sessions/0ded7ffb-02bf-4c99-bef3-3ba7bb4cc15b.json deleted file mode 100644 index 5019a37..0000000 --- a/src/data/sessions/0ded7ffb-02bf-4c99-bef3-3ba7bb4cc15b.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "0ded7ffb-02bf-4c99-bef3-3ba7bb4cc15b", - "created": "2025-07-12T22:49:17.851Z", - "expires": "2025-07-13T22:49:17.851Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/0e8ce4ea-d146-4721-8730-6d440341b12b.json b/src/data/sessions/0e8ce4ea-d146-4721-8730-6d440341b12b.json deleted file mode 100644 index ddd891c..0000000 --- a/src/data/sessions/0e8ce4ea-d146-4721-8730-6d440341b12b.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "0e8ce4ea-d146-4721-8730-6d440341b12b", - "created": "2025-07-18T13:08:23.027Z", - "expires": "2025-07-19T13:08:23.027Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/11356ad8-74d8-46dd-9729-708c7da03245.json b/src/data/sessions/11356ad8-74d8-46dd-9729-708c7da03245.json deleted file mode 100644 index 8b471e3..0000000 --- a/src/data/sessions/11356ad8-74d8-46dd-9729-708c7da03245.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "11356ad8-74d8-46dd-9729-708c7da03245", - "created": "2025-07-12T22:28:05.462Z", - "expires": "2025-07-13T22:28:05.462Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/116a5568-47e4-47ab-8e57-e94adf84fc87.json b/src/data/sessions/116a5568-47e4-47ab-8e57-e94adf84fc87.json deleted file mode 100644 index 0e6c97f..0000000 --- a/src/data/sessions/116a5568-47e4-47ab-8e57-e94adf84fc87.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "116a5568-47e4-47ab-8e57-e94adf84fc87", - "created": "2025-07-18T15:47:26.181Z", - "expires": "2025-07-19T15:47:26.181Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/11c13096-f65f-490d-82b6-92e813c92d5f.json b/src/data/sessions/11c13096-f65f-490d-82b6-92e813c92d5f.json deleted file mode 100644 index 304a1fe..0000000 --- a/src/data/sessions/11c13096-f65f-490d-82b6-92e813c92d5f.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "11c13096-f65f-490d-82b6-92e813c92d5f", - "created": "2025-07-18T13:47:59.853Z", - "expires": "2025-07-19T13:47:59.853Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/141614ee-d5fe-4a21-8384-c11d382d6b2a.json b/src/data/sessions/141614ee-d5fe-4a21-8384-c11d382d6b2a.json deleted file mode 100644 index d77e5f8..0000000 --- a/src/data/sessions/141614ee-d5fe-4a21-8384-c11d382d6b2a.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "141614ee-d5fe-4a21-8384-c11d382d6b2a", - "created": "2025-07-23T11:54:30.146Z", - "expires": "2025-07-24T11:54:30.146Z", - "notification": { - "type": "completed", - "project": "video_tag", - "message": "[video_tag] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/17b08d21-4e1a-4991-93b9-b227d430227e.json b/src/data/sessions/17b08d21-4e1a-4991-93b9-b227d430227e.json deleted file mode 100644 index 95688a4..0000000 --- a/src/data/sessions/17b08d21-4e1a-4991-93b9-b227d430227e.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "17b08d21-4e1a-4991-93b9-b227d430227e", - "created": "2025-07-23T11:49:03.622Z", - "expires": "2025-07-24T11:49:03.622Z", - "notification": { - "type": "completed", - "project": "video_tag", - "message": "[video_tag] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/17cddd72-6761-4c87-a6f3-535dffd46847.json b/src/data/sessions/17cddd72-6761-4c87-a6f3-535dffd46847.json deleted file mode 100644 index 9e2eb26..0000000 --- a/src/data/sessions/17cddd72-6761-4c87-a6f3-535dffd46847.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "17cddd72-6761-4c87-a6f3-535dffd46847", - "created": "2025-07-13T17:49:21.732Z", - "expires": "2025-07-14T17:49:21.732Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/19168db8-122b-40d4-930e-eaa7f442dc5e.json b/src/data/sessions/19168db8-122b-40d4-930e-eaa7f442dc5e.json deleted file mode 100644 index 1d3e92d..0000000 --- a/src/data/sessions/19168db8-122b-40d4-930e-eaa7f442dc5e.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "19168db8-122b-40d4-930e-eaa7f442dc5e", - "created": "2025-07-14T10:25:17.872Z", - "expires": "2025-07-15T10:25:17.872Z", - "notification": { - "type": "waiting", - "project": "pandalla_api", - "message": "[pandalla_api] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/197a28ea-945e-4cb1-87d5-6e240eb66c7f.json b/src/data/sessions/197a28ea-945e-4cb1-87d5-6e240eb66c7f.json deleted file mode 100644 index c6618be..0000000 --- a/src/data/sessions/197a28ea-945e-4cb1-87d5-6e240eb66c7f.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "197a28ea-945e-4cb1-87d5-6e240eb66c7f", - "created": "2025-07-14T08:35:53.805Z", - "expires": "2025-07-15T08:35:53.805Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/19bb6891-1db6-4dbc-92a9-aa078dbaa49e.json b/src/data/sessions/19bb6891-1db6-4dbc-92a9-aa078dbaa49e.json deleted file mode 100644 index 182b7f0..0000000 --- a/src/data/sessions/19bb6891-1db6-4dbc-92a9-aa078dbaa49e.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "19bb6891-1db6-4dbc-92a9-aa078dbaa49e", - "created": "2025-07-18T11:52:29.822Z", - "expires": "2025-07-19T11:52:29.822Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/19cc38c0-5be4-4656-8982-89f75c1cd3b1.json b/src/data/sessions/19cc38c0-5be4-4656-8982-89f75c1cd3b1.json deleted file mode 100644 index f951e69..0000000 --- a/src/data/sessions/19cc38c0-5be4-4656-8982-89f75c1cd3b1.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "19cc38c0-5be4-4656-8982-89f75c1cd3b1", - "created": "2025-07-24T01:06:31.410Z", - "expires": "2025-07-25T01:06:31.410Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/1b507e97-135d-4fe5-99d3-b654ad68557c.json b/src/data/sessions/1b507e97-135d-4fe5-99d3-b654ad68557c.json deleted file mode 100644 index 1c0fb77..0000000 --- a/src/data/sessions/1b507e97-135d-4fe5-99d3-b654ad68557c.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "1b507e97-135d-4fe5-99d3-b654ad68557c", - "created": "2025-07-13T17:24:45.125Z", - "expires": "2025-07-14T17:24:45.125Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/1cabf8e7-3d60-41b1-877e-1c3633dbca38.json b/src/data/sessions/1cabf8e7-3d60-41b1-877e-1c3633dbca38.json deleted file mode 100644 index 632a405..0000000 --- a/src/data/sessions/1cabf8e7-3d60-41b1-877e-1c3633dbca38.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "1cabf8e7-3d60-41b1-877e-1c3633dbca38", - "created": "2025-07-13T13:10:52.253Z", - "expires": "2025-07-14T13:10:52.253Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/205bb739-9a12-44ff-a7fa-3d564a5503d0.json b/src/data/sessions/205bb739-9a12-44ff-a7fa-3d564a5503d0.json deleted file mode 100644 index 7be0916..0000000 --- a/src/data/sessions/205bb739-9a12-44ff-a7fa-3d564a5503d0.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "205bb739-9a12-44ff-a7fa-3d564a5503d0", - "created": "2025-07-14T09:44:29.946Z", - "expires": "2025-07-15T09:44:29.946Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/231c3c61-3d7a-4576-83c7-73b52267843f.json b/src/data/sessions/231c3c61-3d7a-4576-83c7-73b52267843f.json deleted file mode 100644 index 743a03d..0000000 --- a/src/data/sessions/231c3c61-3d7a-4576-83c7-73b52267843f.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "231c3c61-3d7a-4576-83c7-73b52267843f", - "created": "2025-07-18T12:43:18.785Z", - "expires": "2025-07-19T12:43:18.785Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/2391dc73-fca1-41b1-bdc2-5a354bce4c1b.json b/src/data/sessions/2391dc73-fca1-41b1-bdc2-5a354bce4c1b.json deleted file mode 100644 index 8c11b61..0000000 --- a/src/data/sessions/2391dc73-fca1-41b1-bdc2-5a354bce4c1b.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "2391dc73-fca1-41b1-bdc2-5a354bce4c1b", - "created": "2025-07-12T22:44:10.848Z", - "expires": "2025-07-13T22:44:10.848Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/25713a2c-a3df-4e38-92bd-c4c74b97805c.json b/src/data/sessions/25713a2c-a3df-4e38-92bd-c4c74b97805c.json deleted file mode 100644 index 61aa8b2..0000000 --- a/src/data/sessions/25713a2c-a3df-4e38-92bd-c4c74b97805c.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "25713a2c-a3df-4e38-92bd-c4c74b97805c", - "created": "2025-07-12T22:47:15.254Z", - "expires": "2025-07-13T22:47:15.254Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/257faefb-02c0-4016-8314-2abbecf6979b.json b/src/data/sessions/257faefb-02c0-4016-8314-2abbecf6979b.json deleted file mode 100644 index d2f77a2..0000000 --- a/src/data/sessions/257faefb-02c0-4016-8314-2abbecf6979b.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "257faefb-02c0-4016-8314-2abbecf6979b", - "created": "2025-07-13T13:15:51.472Z", - "expires": "2025-07-14T13:15:51.472Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/28efcda0-58a3-4bd0-8421-f7375fd513b6.json b/src/data/sessions/28efcda0-58a3-4bd0-8421-f7375fd513b6.json deleted file mode 100644 index 4650f28..0000000 --- a/src/data/sessions/28efcda0-58a3-4bd0-8421-f7375fd513b6.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "28efcda0-58a3-4bd0-8421-f7375fd513b6", - "created": "2025-07-18T12:56:58.947Z", - "expires": "2025-07-19T12:56:58.947Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/2addd4be-c563-42bc-a6c3-3479e81c80e1.json b/src/data/sessions/2addd4be-c563-42bc-a6c3-3479e81c80e1.json deleted file mode 100644 index b18b5bb..0000000 --- a/src/data/sessions/2addd4be-c563-42bc-a6c3-3479e81c80e1.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "2addd4be-c563-42bc-a6c3-3479e81c80e1", - "created": "2025-07-18T21:00:15.789Z", - "expires": "2025-07-19T21:00:15.789Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/2c21f2ba-3f72-40bd-8231-a5604e300057.json b/src/data/sessions/2c21f2ba-3f72-40bd-8231-a5604e300057.json deleted file mode 100644 index c877542..0000000 --- a/src/data/sessions/2c21f2ba-3f72-40bd-8231-a5604e300057.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "2c21f2ba-3f72-40bd-8231-a5604e300057", - "created": "2025-07-14T09:25:46.676Z", - "expires": "2025-07-15T09:25:46.676Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/2cb3cb02-50e4-4e95-944f-c371f7863eaa.json b/src/data/sessions/2cb3cb02-50e4-4e95-944f-c371f7863eaa.json deleted file mode 100644 index 90bb06a..0000000 --- a/src/data/sessions/2cb3cb02-50e4-4e95-944f-c371f7863eaa.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "2cb3cb02-50e4-4e95-944f-c371f7863eaa", - "created": "2025-07-24T07:03:49.922Z", - "expires": "2025-07-25T07:03:49.922Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/2d1c0454-ac79-48bf-858d-6a7b19d5619a.json b/src/data/sessions/2d1c0454-ac79-48bf-858d-6a7b19d5619a.json deleted file mode 100644 index e21c598..0000000 --- a/src/data/sessions/2d1c0454-ac79-48bf-858d-6a7b19d5619a.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "2d1c0454-ac79-48bf-858d-6a7b19d5619a", - "created": "2025-07-12T22:28:33.723Z", - "expires": "2025-07-13T22:28:33.723Z", - "notification": { - "type": "waiting", - "project": "TaskPing", - "message": "[TaskPing] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/2d9168b2-0c0b-4a37-92c9-c34138ee9196.json b/src/data/sessions/2d9168b2-0c0b-4a37-92c9-c34138ee9196.json deleted file mode 100644 index b429a8e..0000000 --- a/src/data/sessions/2d9168b2-0c0b-4a37-92c9-c34138ee9196.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "2d9168b2-0c0b-4a37-92c9-c34138ee9196", - "created": "2025-07-14T09:33:43.775Z", - "expires": "2025-07-15T09:33:43.775Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/2f2c7670-4b82-4dba-8595-f53fad37ba2f.json b/src/data/sessions/2f2c7670-4b82-4dba-8595-f53fad37ba2f.json deleted file mode 100644 index 69100b9..0000000 --- a/src/data/sessions/2f2c7670-4b82-4dba-8595-f53fad37ba2f.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "2f2c7670-4b82-4dba-8595-f53fad37ba2f", - "created": "2025-07-18T11:22:22.962Z", - "expires": "2025-07-19T11:22:22.962Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/31fa7938-ae5f-4079-a8f5-af083bae0f03.json b/src/data/sessions/31fa7938-ae5f-4079-a8f5-af083bae0f03.json deleted file mode 100644 index 0bce058..0000000 --- a/src/data/sessions/31fa7938-ae5f-4079-a8f5-af083bae0f03.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "31fa7938-ae5f-4079-a8f5-af083bae0f03", - "created": "2025-07-18T11:37:00.528Z", - "expires": "2025-07-19T11:37:00.528Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/3556e5c1-ae72-4009-9062-6e94df22bc6c.json b/src/data/sessions/3556e5c1-ae72-4009-9062-6e94df22bc6c.json deleted file mode 100644 index 260a7a7..0000000 --- a/src/data/sessions/3556e5c1-ae72-4009-9062-6e94df22bc6c.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "3556e5c1-ae72-4009-9062-6e94df22bc6c", - "created": "2025-07-18T21:06:25.623Z", - "expires": "2025-07-19T21:06:25.623Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/374cc8b9-a894-43f6-9bd6-7ee37c878800.json b/src/data/sessions/374cc8b9-a894-43f6-9bd6-7ee37c878800.json deleted file mode 100644 index 651fb8c..0000000 --- a/src/data/sessions/374cc8b9-a894-43f6-9bd6-7ee37c878800.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "374cc8b9-a894-43f6-9bd6-7ee37c878800", - "created": "2025-07-24T00:01:43.501Z", - "expires": "2025-07-25T00:01:43.501Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/394f897f-af05-4931-b193-6e423ddcd5ae.json b/src/data/sessions/394f897f-af05-4931-b193-6e423ddcd5ae.json deleted file mode 100644 index 8541516..0000000 --- a/src/data/sessions/394f897f-af05-4931-b193-6e423ddcd5ae.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "394f897f-af05-4931-b193-6e423ddcd5ae", - "created": "2025-07-12T22:38:15.434Z", - "expires": "2025-07-13T22:38:15.434Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/3afbafe7-a999-4055-8761-a182cf1c5ea0.json b/src/data/sessions/3afbafe7-a999-4055-8761-a182cf1c5ea0.json deleted file mode 100644 index 9cc91f3..0000000 --- a/src/data/sessions/3afbafe7-a999-4055-8761-a182cf1c5ea0.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "3afbafe7-a999-4055-8761-a182cf1c5ea0", - "created": "2025-07-18T17:13:24.018Z", - "expires": "2025-07-19T17:13:24.018Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/3b03b7e6-7ffe-41b5-83b2-3e54de65258f.json b/src/data/sessions/3b03b7e6-7ffe-41b5-83b2-3e54de65258f.json deleted file mode 100644 index 0770cd0..0000000 --- a/src/data/sessions/3b03b7e6-7ffe-41b5-83b2-3e54de65258f.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "3b03b7e6-7ffe-41b5-83b2-3e54de65258f", - "created": "2025-07-18T12:52:33.189Z", - "expires": "2025-07-19T12:52:33.189Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/3bd55fff-83d3-4435-86a1-86f0762ac52d.json b/src/data/sessions/3bd55fff-83d3-4435-86a1-86f0762ac52d.json deleted file mode 100644 index 44b0acc..0000000 --- a/src/data/sessions/3bd55fff-83d3-4435-86a1-86f0762ac52d.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "3bd55fff-83d3-4435-86a1-86f0762ac52d", - "created": "2025-07-23T12:41:33.713Z", - "expires": "2025-07-24T12:41:33.713Z", - "notification": { - "type": "completed", - "project": "video_tag", - "message": "[video_tag] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/3ca02587-e55f-42bd-8d6b-d4075e0abe45.json b/src/data/sessions/3ca02587-e55f-42bd-8d6b-d4075e0abe45.json deleted file mode 100644 index b43261b..0000000 --- a/src/data/sessions/3ca02587-e55f-42bd-8d6b-d4075e0abe45.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "3ca02587-e55f-42bd-8d6b-d4075e0abe45", - "created": "2025-07-13T13:18:56.254Z", - "expires": "2025-07-14T13:18:56.254Z", - "notification": { - "type": "waiting", - "project": "pandalla_api", - "message": "[pandalla_api] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/3ead9678-b6bd-49fb-ae64-7303a4c6c740.json b/src/data/sessions/3ead9678-b6bd-49fb-ae64-7303a4c6c740.json deleted file mode 100644 index ea5733d..0000000 --- a/src/data/sessions/3ead9678-b6bd-49fb-ae64-7303a4c6c740.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "3ead9678-b6bd-49fb-ae64-7303a4c6c740", - "created": "2025-07-23T12:39:08.805Z", - "expires": "2025-07-24T12:39:08.805Z", - "notification": { - "type": "completed", - "project": "video_tag", - "message": "[video_tag] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/3f156d12-c0e6-4489-8bf9-1c7b53ecf165.json b/src/data/sessions/3f156d12-c0e6-4489-8bf9-1c7b53ecf165.json deleted file mode 100644 index b499c6b..0000000 --- a/src/data/sessions/3f156d12-c0e6-4489-8bf9-1c7b53ecf165.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "3f156d12-c0e6-4489-8bf9-1c7b53ecf165", - "created": "2025-07-23T20:28:32.489Z", - "expires": "2025-07-24T20:28:32.489Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/409d0107-1d7f-4892-a456-04d0f3c5300d.json b/src/data/sessions/409d0107-1d7f-4892-a456-04d0f3c5300d.json deleted file mode 100644 index e32c6c4..0000000 --- a/src/data/sessions/409d0107-1d7f-4892-a456-04d0f3c5300d.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "409d0107-1d7f-4892-a456-04d0f3c5300d", - "created": "2025-07-26T18:34:54.528Z", - "expires": "2025-07-27T18:34:54.528Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/412531dd-cd61-4f27-b330-07b5196ef64f.json b/src/data/sessions/412531dd-cd61-4f27-b330-07b5196ef64f.json deleted file mode 100644 index b5e73c3..0000000 --- a/src/data/sessions/412531dd-cd61-4f27-b330-07b5196ef64f.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "412531dd-cd61-4f27-b330-07b5196ef64f", - "created": "2025-07-13T13:14:14.490Z", - "expires": "2025-07-14T13:14:14.490Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/44b11a87-a008-47bd-affa-72e7daff37d5.json b/src/data/sessions/44b11a87-a008-47bd-affa-72e7daff37d5.json deleted file mode 100644 index 774f006..0000000 --- a/src/data/sessions/44b11a87-a008-47bd-affa-72e7daff37d5.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "44b11a87-a008-47bd-affa-72e7daff37d5", - "created": "2025-07-13T17:16:58.667Z", - "expires": "2025-07-14T17:16:58.667Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/4543a139-f0a2-427a-bcf3-229c998d50c1.json b/src/data/sessions/4543a139-f0a2-427a-bcf3-229c998d50c1.json deleted file mode 100644 index 88d100f..0000000 --- a/src/data/sessions/4543a139-f0a2-427a-bcf3-229c998d50c1.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "4543a139-f0a2-427a-bcf3-229c998d50c1", - "created": "2025-07-18T16:58:38.999Z", - "expires": "2025-07-19T16:58:38.999Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/48ad0b9e-6646-47ac-a691-6d2407203484.json b/src/data/sessions/48ad0b9e-6646-47ac-a691-6d2407203484.json deleted file mode 100644 index b3a7b36..0000000 --- a/src/data/sessions/48ad0b9e-6646-47ac-a691-6d2407203484.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "48ad0b9e-6646-47ac-a691-6d2407203484", - "created": "2025-07-18T21:09:38.626Z", - "expires": "2025-07-19T21:09:38.626Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/490021fb-856e-4176-b889-af8a9041e44f.json b/src/data/sessions/490021fb-856e-4176-b889-af8a9041e44f.json deleted file mode 100644 index 8619f85..0000000 --- a/src/data/sessions/490021fb-856e-4176-b889-af8a9041e44f.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "490021fb-856e-4176-b889-af8a9041e44f", - "created": "2025-07-15T18:07:33.555Z", - "expires": "2025-07-16T18:07:33.555Z", - "notification": { - "type": "completed", - "project": "coverr_video", - "message": "[coverr_video] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/4ad8d491-776e-4901-9210-84fa76c09b69.json b/src/data/sessions/4ad8d491-776e-4901-9210-84fa76c09b69.json deleted file mode 100644 index 2242550..0000000 --- a/src/data/sessions/4ad8d491-776e-4901-9210-84fa76c09b69.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "4ad8d491-776e-4901-9210-84fa76c09b69", - "created": "2025-07-23T23:02:23.225Z", - "expires": "2025-07-24T23:02:23.225Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/4be36d8d-ab24-47fa-8580-a5a145055ae0.json b/src/data/sessions/4be36d8d-ab24-47fa-8580-a5a145055ae0.json deleted file mode 100644 index 4a08531..0000000 --- a/src/data/sessions/4be36d8d-ab24-47fa-8580-a5a145055ae0.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "4be36d8d-ab24-47fa-8580-a5a145055ae0", - "created": "2025-07-23T12:19:49.293Z", - "expires": "2025-07-24T12:19:49.293Z", - "notification": { - "type": "completed", - "project": "video_tag", - "message": "[video_tag] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/4e5150dc-adfd-439a-8a50-1319f5956efd.json b/src/data/sessions/4e5150dc-adfd-439a-8a50-1319f5956efd.json deleted file mode 100644 index 085d18f..0000000 --- a/src/data/sessions/4e5150dc-adfd-439a-8a50-1319f5956efd.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "4e5150dc-adfd-439a-8a50-1319f5956efd", - "created": "2025-07-17T05:16:42.315Z", - "expires": "2025-07-18T05:16:42.315Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/4e85e5f7-f538-4e86-bd2a-7097e2bc8f16.json b/src/data/sessions/4e85e5f7-f538-4e86-bd2a-7097e2bc8f16.json deleted file mode 100644 index 9a2fca5..0000000 --- a/src/data/sessions/4e85e5f7-f538-4e86-bd2a-7097e2bc8f16.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "4e85e5f7-f538-4e86-bd2a-7097e2bc8f16", - "created": "2025-07-12T22:30:43.403Z", - "expires": "2025-07-13T22:30:43.403Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/4ee3f0b1-7d23-4eef-a660-9d4fa63d7a2e.json b/src/data/sessions/4ee3f0b1-7d23-4eef-a660-9d4fa63d7a2e.json deleted file mode 100644 index 9c48ea2..0000000 --- a/src/data/sessions/4ee3f0b1-7d23-4eef-a660-9d4fa63d7a2e.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "4ee3f0b1-7d23-4eef-a660-9d4fa63d7a2e", - "created": "2025-07-18T13:37:08.299Z", - "expires": "2025-07-19T13:37:08.299Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/535a51d2-480e-46ac-988a-a0012babe13f.json b/src/data/sessions/535a51d2-480e-46ac-988a-a0012babe13f.json deleted file mode 100644 index 441b709..0000000 --- a/src/data/sessions/535a51d2-480e-46ac-988a-a0012babe13f.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "535a51d2-480e-46ac-988a-a0012babe13f", - "created": "2025-07-18T11:56:52.666Z", - "expires": "2025-07-19T11:56:52.666Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/53b27eaa-89cf-4a64-a62a-510a3fa3f9b9.json b/src/data/sessions/53b27eaa-89cf-4a64-a62a-510a3fa3f9b9.json deleted file mode 100644 index 8d1e4ae..0000000 --- a/src/data/sessions/53b27eaa-89cf-4a64-a62a-510a3fa3f9b9.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "53b27eaa-89cf-4a64-a62a-510a3fa3f9b9", - "created": "2025-07-18T11:18:14.937Z", - "expires": "2025-07-19T11:18:14.937Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/56a35209-9174-4a75-b7f5-43b564e972cd.json b/src/data/sessions/56a35209-9174-4a75-b7f5-43b564e972cd.json deleted file mode 100644 index 7bb36d9..0000000 --- a/src/data/sessions/56a35209-9174-4a75-b7f5-43b564e972cd.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "56a35209-9174-4a75-b7f5-43b564e972cd", - "created": "2025-07-12T22:45:31.091Z", - "expires": "2025-07-13T22:45:31.091Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/590cc9b0-d985-488d-97b4-478076ce0d67.json b/src/data/sessions/590cc9b0-d985-488d-97b4-478076ce0d67.json deleted file mode 100644 index af8239a..0000000 --- a/src/data/sessions/590cc9b0-d985-488d-97b4-478076ce0d67.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "590cc9b0-d985-488d-97b4-478076ce0d67", - "created": "2025-07-13T13:07:29.360Z", - "expires": "2025-07-14T13:07:29.360Z", - "notification": { - "type": "waiting", - "project": "pandalla_api", - "message": "[pandalla_api] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/602070fc-f0a9-42f3-b305-6b5149d9da60.json b/src/data/sessions/602070fc-f0a9-42f3-b305-6b5149d9da60.json deleted file mode 100644 index 18a4a05..0000000 --- a/src/data/sessions/602070fc-f0a9-42f3-b305-6b5149d9da60.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "602070fc-f0a9-42f3-b305-6b5149d9da60", - "created": "2025-07-12T22:50:13.065Z", - "expires": "2025-07-13T22:50:13.065Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/60281214-9e5a-4a6a-b3bd-b9a849b7ee9c.json b/src/data/sessions/60281214-9e5a-4a6a-b3bd-b9a849b7ee9c.json deleted file mode 100644 index ae56128..0000000 --- a/src/data/sessions/60281214-9e5a-4a6a-b3bd-b9a849b7ee9c.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "60281214-9e5a-4a6a-b3bd-b9a849b7ee9c", - "created": "2025-07-18T13:04:16.855Z", - "expires": "2025-07-19T13:04:16.855Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/615e56ff-f4ce-4220-b3b1-24aefcba386f.json b/src/data/sessions/615e56ff-f4ce-4220-b3b1-24aefcba386f.json deleted file mode 100644 index 023f23c..0000000 --- a/src/data/sessions/615e56ff-f4ce-4220-b3b1-24aefcba386f.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "615e56ff-f4ce-4220-b3b1-24aefcba386f", - "created": "2025-07-18T12:32:15.171Z", - "expires": "2025-07-19T12:32:15.171Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/628c6e13-e109-4bf0-a98a-8db0005ac55f.json b/src/data/sessions/628c6e13-e109-4bf0-a98a-8db0005ac55f.json deleted file mode 100644 index 437ad31..0000000 --- a/src/data/sessions/628c6e13-e109-4bf0-a98a-8db0005ac55f.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "628c6e13-e109-4bf0-a98a-8db0005ac55f", - "created": "2025-07-18T13:33:08.127Z", - "expires": "2025-07-19T13:33:08.127Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/6344f960-c6b6-40e3-9626-7b92c40d148b.json b/src/data/sessions/6344f960-c6b6-40e3-9626-7b92c40d148b.json deleted file mode 100644 index 2522088..0000000 --- a/src/data/sessions/6344f960-c6b6-40e3-9626-7b92c40d148b.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "6344f960-c6b6-40e3-9626-7b92c40d148b", - "created": "2025-07-18T17:06:22.270Z", - "expires": "2025-07-19T17:06:22.270Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/63d537b7-63e3-4c23-8358-b63d5c28c72d.json b/src/data/sessions/63d537b7-63e3-4c23-8358-b63d5c28c72d.json deleted file mode 100644 index 686b74b..0000000 --- a/src/data/sessions/63d537b7-63e3-4c23-8358-b63d5c28c72d.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "63d537b7-63e3-4c23-8358-b63d5c28c72d", - "created": "2025-07-23T22:50:41.568Z", - "expires": "2025-07-24T22:50:41.568Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/667e76f3-fdd8-407e-89c7-c120311d1e98.json b/src/data/sessions/667e76f3-fdd8-407e-89c7-c120311d1e98.json deleted file mode 100644 index 9f7ad7e..0000000 --- a/src/data/sessions/667e76f3-fdd8-407e-89c7-c120311d1e98.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "667e76f3-fdd8-407e-89c7-c120311d1e98", - "created": "2025-07-23T21:45:03.924Z", - "expires": "2025-07-24T21:45:03.924Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/67a7a308-29b1-4236-835e-784f87e24632.json b/src/data/sessions/67a7a308-29b1-4236-835e-784f87e24632.json deleted file mode 100644 index 658bad4..0000000 --- a/src/data/sessions/67a7a308-29b1-4236-835e-784f87e24632.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "67a7a308-29b1-4236-835e-784f87e24632", - "created": "2025-07-23T12:49:11.463Z", - "expires": "2025-07-24T12:49:11.463Z", - "notification": { - "type": "completed", - "project": "video_tag", - "message": "[video_tag] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/68ff37be-a0c6-4709-bc95-c38d02f5c7e5.json b/src/data/sessions/68ff37be-a0c6-4709-bc95-c38d02f5c7e5.json deleted file mode 100644 index 1b9396c..0000000 --- a/src/data/sessions/68ff37be-a0c6-4709-bc95-c38d02f5c7e5.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "68ff37be-a0c6-4709-bc95-c38d02f5c7e5", - "created": "2025-07-14T10:28:56.812Z", - "expires": "2025-07-15T10:28:56.812Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/694590e6-e5ce-492a-a289-4967108342e9.json b/src/data/sessions/694590e6-e5ce-492a-a289-4967108342e9.json deleted file mode 100644 index bb7a731..0000000 --- a/src/data/sessions/694590e6-e5ce-492a-a289-4967108342e9.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "694590e6-e5ce-492a-a289-4967108342e9", - "created": "2025-07-18T12:12:01.327Z", - "expires": "2025-07-19T12:12:01.327Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/6a00efc7-b169-4517-bc6b-03d9c7296e6b.json b/src/data/sessions/6a00efc7-b169-4517-bc6b-03d9c7296e6b.json deleted file mode 100644 index e358674..0000000 --- a/src/data/sessions/6a00efc7-b169-4517-bc6b-03d9c7296e6b.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "6a00efc7-b169-4517-bc6b-03d9c7296e6b", - "created": "2025-07-12T22:30:53.659Z", - "expires": "2025-07-13T22:30:53.659Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/6d115776-ab6b-48fc-ad91-fcff5d512085.json b/src/data/sessions/6d115776-ab6b-48fc-ad91-fcff5d512085.json deleted file mode 100644 index 301bd73..0000000 --- a/src/data/sessions/6d115776-ab6b-48fc-ad91-fcff5d512085.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "6d115776-ab6b-48fc-ad91-fcff5d512085", - "created": "2025-07-13T13:08:07.069Z", - "expires": "2025-07-14T13:08:07.069Z", - "notification": { - "type": "waiting", - "project": "TaskPing", - "message": "[TaskPing] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/6e85295a-e56f-4d44-a537-5be3a026f109.json b/src/data/sessions/6e85295a-e56f-4d44-a537-5be3a026f109.json deleted file mode 100644 index 1ac1ad1..0000000 --- a/src/data/sessions/6e85295a-e56f-4d44-a537-5be3a026f109.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "6e85295a-e56f-4d44-a537-5be3a026f109", - "created": "2025-07-14T14:33:33.979Z", - "expires": "2025-07-15T14:33:33.979Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/708b349a-6fac-441f-98c0-acd0cc7e175c.json b/src/data/sessions/708b349a-6fac-441f-98c0-acd0cc7e175c.json deleted file mode 100644 index 9ea7ded..0000000 --- a/src/data/sessions/708b349a-6fac-441f-98c0-acd0cc7e175c.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "708b349a-6fac-441f-98c0-acd0cc7e175c", - "created": "2025-07-13T12:58:56.851Z", - "expires": "2025-07-14T12:58:56.851Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/70f4e977-cd61-4b63-87cc-41dc94276e73.json b/src/data/sessions/70f4e977-cd61-4b63-87cc-41dc94276e73.json deleted file mode 100644 index 481f7b6..0000000 --- a/src/data/sessions/70f4e977-cd61-4b63-87cc-41dc94276e73.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "70f4e977-cd61-4b63-87cc-41dc94276e73", - "created": "2025-07-18T21:03:11.363Z", - "expires": "2025-07-19T21:03:11.363Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/71d878dc-e75b-4c53-9b26-fb21676120de.json b/src/data/sessions/71d878dc-e75b-4c53-9b26-fb21676120de.json deleted file mode 100644 index cc20b5b..0000000 --- a/src/data/sessions/71d878dc-e75b-4c53-9b26-fb21676120de.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "71d878dc-e75b-4c53-9b26-fb21676120de", - "created": "2025-07-23T12:36:25.228Z", - "expires": "2025-07-24T12:36:25.228Z", - "notification": { - "type": "completed", - "project": "video_tag", - "message": "[video_tag] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/72f1784b-19fb-4ce0-8bf9-ac4a26049020.json b/src/data/sessions/72f1784b-19fb-4ce0-8bf9-ac4a26049020.json deleted file mode 100644 index 79b43f0..0000000 --- a/src/data/sessions/72f1784b-19fb-4ce0-8bf9-ac4a26049020.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "72f1784b-19fb-4ce0-8bf9-ac4a26049020", - "created": "2025-07-14T09:36:42.867Z", - "expires": "2025-07-15T09:36:42.867Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/7357bf2d-f2f3-4246-b95c-eb53b41090f2.json b/src/data/sessions/7357bf2d-f2f3-4246-b95c-eb53b41090f2.json deleted file mode 100644 index b6b989b..0000000 --- a/src/data/sessions/7357bf2d-f2f3-4246-b95c-eb53b41090f2.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "7357bf2d-f2f3-4246-b95c-eb53b41090f2", - "created": "2025-07-18T13:29:24.730Z", - "expires": "2025-07-19T13:29:24.730Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/75ed2ecd-04b6-4f50-9f00-419319410eb9.json b/src/data/sessions/75ed2ecd-04b6-4f50-9f00-419319410eb9.json deleted file mode 100644 index 05b894c..0000000 --- a/src/data/sessions/75ed2ecd-04b6-4f50-9f00-419319410eb9.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "75ed2ecd-04b6-4f50-9f00-419319410eb9", - "created": "2025-07-24T00:01:16.660Z", - "expires": "2025-07-25T00:01:16.660Z", - "notification": { - "type": "waiting", - "project": "pandalla.ai", - "message": "[pandalla.ai] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/77fd9ef3-2633-46b0-aba0-8cf92e8382e3.json b/src/data/sessions/77fd9ef3-2633-46b0-aba0-8cf92e8382e3.json deleted file mode 100644 index 1d2491e..0000000 --- a/src/data/sessions/77fd9ef3-2633-46b0-aba0-8cf92e8382e3.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "77fd9ef3-2633-46b0-aba0-8cf92e8382e3", - "created": "2025-07-12T22:35:18.401Z", - "expires": "2025-07-13T22:35:18.401Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/783a9f43-a016-4ce4-ae53-b50e55d68bc0.json b/src/data/sessions/783a9f43-a016-4ce4-ae53-b50e55d68bc0.json deleted file mode 100644 index 392b984..0000000 --- a/src/data/sessions/783a9f43-a016-4ce4-ae53-b50e55d68bc0.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "783a9f43-a016-4ce4-ae53-b50e55d68bc0", - "created": "2025-07-13T17:44:58.245Z", - "expires": "2025-07-14T17:44:58.245Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/79a75dd9-2d81-4e1a-a52b-d648e0fc2d1f.json b/src/data/sessions/79a75dd9-2d81-4e1a-a52b-d648e0fc2d1f.json deleted file mode 100644 index 5abcd21..0000000 --- a/src/data/sessions/79a75dd9-2d81-4e1a-a52b-d648e0fc2d1f.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "79a75dd9-2d81-4e1a-a52b-d648e0fc2d1f", - "created": "2025-07-13T16:56:58.351Z", - "expires": "2025-07-14T16:56:58.351Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/7a047613-8892-4483-9553-3866ce3aba0e.json b/src/data/sessions/7a047613-8892-4483-9553-3866ce3aba0e.json deleted file mode 100644 index 8bc43eb..0000000 --- a/src/data/sessions/7a047613-8892-4483-9553-3866ce3aba0e.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "7a047613-8892-4483-9553-3866ce3aba0e", - "created": "2025-07-12T22:57:09.397Z", - "expires": "2025-07-13T22:57:09.397Z", - "notification": { - "type": "waiting", - "project": "pandalla_api", - "message": "[pandalla_api] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/7ba59d33-febd-4533-9534-b24674f49ab1.json b/src/data/sessions/7ba59d33-febd-4533-9534-b24674f49ab1.json deleted file mode 100644 index c4485bb..0000000 --- a/src/data/sessions/7ba59d33-febd-4533-9534-b24674f49ab1.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "7ba59d33-febd-4533-9534-b24674f49ab1", - "created": "2025-07-13T13:29:27.538Z", - "expires": "2025-07-14T13:29:27.538Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/7d646653-8da9-4a05-b95d-cbf4dccef64e.json b/src/data/sessions/7d646653-8da9-4a05-b95d-cbf4dccef64e.json deleted file mode 100644 index 0463508..0000000 --- a/src/data/sessions/7d646653-8da9-4a05-b95d-cbf4dccef64e.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "7d646653-8da9-4a05-b95d-cbf4dccef64e", - "created": "2025-07-24T06:57:57.603Z", - "expires": "2025-07-25T06:57:57.603Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/7dbd15c9-f9dd-425a-bfa9-1f493b0aea06.json b/src/data/sessions/7dbd15c9-f9dd-425a-bfa9-1f493b0aea06.json deleted file mode 100644 index 9f8b527..0000000 --- a/src/data/sessions/7dbd15c9-f9dd-425a-bfa9-1f493b0aea06.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "7dbd15c9-f9dd-425a-bfa9-1f493b0aea06", - "created": "2025-07-14T09:32:17.232Z", - "expires": "2025-07-15T09:32:17.232Z", - "notification": { - "type": "waiting", - "project": "pandalla_api", - "message": "[pandalla_api] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/7f2221ae-92c7-4e53-bea2-1a670e53b2fa.json b/src/data/sessions/7f2221ae-92c7-4e53-bea2-1a670e53b2fa.json deleted file mode 100644 index ebb1bdc..0000000 --- a/src/data/sessions/7f2221ae-92c7-4e53-bea2-1a670e53b2fa.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "7f2221ae-92c7-4e53-bea2-1a670e53b2fa", - "created": "2025-07-18T13:20:39.822Z", - "expires": "2025-07-19T13:20:39.822Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/7fa6cf40-8073-4f09-b898-7aa9df8f18da.json b/src/data/sessions/7fa6cf40-8073-4f09-b898-7aa9df8f18da.json deleted file mode 100644 index ed5c5c9..0000000 --- a/src/data/sessions/7fa6cf40-8073-4f09-b898-7aa9df8f18da.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "7fa6cf40-8073-4f09-b898-7aa9df8f18da", - "created": "2025-07-23T12:20:56.079Z", - "expires": "2025-07-24T12:20:56.079Z", - "notification": { - "type": "completed", - "project": "video_tag", - "message": "[video_tag] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/8003453f-8d95-48c1-992f-9d4fab001c85.json b/src/data/sessions/8003453f-8d95-48c1-992f-9d4fab001c85.json deleted file mode 100644 index efe440b..0000000 --- a/src/data/sessions/8003453f-8d95-48c1-992f-9d4fab001c85.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "8003453f-8d95-48c1-992f-9d4fab001c85", - "created": "2025-07-26T18:22:53.439Z", - "expires": "2025-07-27T18:22:53.439Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/83a59c83-57a5-45f9-b60f-de5e64805516.json b/src/data/sessions/83a59c83-57a5-45f9-b60f-de5e64805516.json deleted file mode 100644 index ae6fa1e..0000000 --- a/src/data/sessions/83a59c83-57a5-45f9-b60f-de5e64805516.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "83a59c83-57a5-45f9-b60f-de5e64805516", - "created": "2025-07-18T13:45:19.036Z", - "expires": "2025-07-19T13:45:19.036Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/86dc8fcb-34a5-4f0f-ab7a-4153b5fae8c6.json b/src/data/sessions/86dc8fcb-34a5-4f0f-ab7a-4153b5fae8c6.json deleted file mode 100644 index 31b2468..0000000 --- a/src/data/sessions/86dc8fcb-34a5-4f0f-ab7a-4153b5fae8c6.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "86dc8fcb-34a5-4f0f-ab7a-4153b5fae8c6", - "created": "2025-07-23T23:37:49.309Z", - "expires": "2025-07-24T23:37:49.309Z", - "notification": { - "type": "completed", - "project": "video_tag", - "message": "[video_tag] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/881b6505-ecb2-4f76-95a5-ffc7f8d8d769.json b/src/data/sessions/881b6505-ecb2-4f76-95a5-ffc7f8d8d769.json deleted file mode 100644 index 0347be3..0000000 --- a/src/data/sessions/881b6505-ecb2-4f76-95a5-ffc7f8d8d769.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "881b6505-ecb2-4f76-95a5-ffc7f8d8d769", - "created": "2025-07-12T23:11:20.140Z", - "expires": "2025-07-13T23:11:20.140Z", - "notification": { - "type": "waiting", - "project": "pandalla_api", - "message": "[pandalla_api] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/8841268c-2781-4966-b6f1-b6bbd68b3f5f.json b/src/data/sessions/8841268c-2781-4966-b6f1-b6bbd68b3f5f.json deleted file mode 100644 index 38a3e83..0000000 --- a/src/data/sessions/8841268c-2781-4966-b6f1-b6bbd68b3f5f.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "8841268c-2781-4966-b6f1-b6bbd68b3f5f", - "created": "2025-07-23T23:59:12.060Z", - "expires": "2025-07-24T23:59:12.060Z", - "notification": { - "type": "waiting", - "project": "pandalla.ai", - "message": "[pandalla.ai] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/8b56de5e-3af9-4f62-a178-e205107effa8.json b/src/data/sessions/8b56de5e-3af9-4f62-a178-e205107effa8.json deleted file mode 100644 index ed3b3f8..0000000 --- a/src/data/sessions/8b56de5e-3af9-4f62-a178-e205107effa8.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "8b56de5e-3af9-4f62-a178-e205107effa8", - "created": "2025-07-23T23:05:43.845Z", - "expires": "2025-07-24T23:05:43.845Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/8d2cd1e5-1ade-4e58-b7e0-354c3b048ae3.json b/src/data/sessions/8d2cd1e5-1ade-4e58-b7e0-354c3b048ae3.json deleted file mode 100644 index 914efc3..0000000 --- a/src/data/sessions/8d2cd1e5-1ade-4e58-b7e0-354c3b048ae3.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "8d2cd1e5-1ade-4e58-b7e0-354c3b048ae3", - "created": "2025-07-12T23:12:02.275Z", - "expires": "2025-07-13T23:12:02.275Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/91bf99e1-93a7-4c3d-8b31-ffbb2bda4e5a.json b/src/data/sessions/91bf99e1-93a7-4c3d-8b31-ffbb2bda4e5a.json deleted file mode 100644 index afe7dca..0000000 --- a/src/data/sessions/91bf99e1-93a7-4c3d-8b31-ffbb2bda4e5a.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "91bf99e1-93a7-4c3d-8b31-ffbb2bda4e5a", - "created": "2025-07-18T12:21:42.450Z", - "expires": "2025-07-19T12:21:42.450Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/936051d7-fac6-4d3f-8743-20f9492dc17e.json b/src/data/sessions/936051d7-fac6-4d3f-8743-20f9492dc17e.json deleted file mode 100644 index e71f4a8..0000000 --- a/src/data/sessions/936051d7-fac6-4d3f-8743-20f9492dc17e.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "936051d7-fac6-4d3f-8743-20f9492dc17e", - "created": "2025-07-18T21:18:29.084Z", - "expires": "2025-07-19T21:18:29.084Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/9757efc0-55f2-4e51-a546-fc8602248b9d.json b/src/data/sessions/9757efc0-55f2-4e51-a546-fc8602248b9d.json deleted file mode 100644 index 3242cdb..0000000 --- a/src/data/sessions/9757efc0-55f2-4e51-a546-fc8602248b9d.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "9757efc0-55f2-4e51-a546-fc8602248b9d", - "created": "2025-07-24T00:11:31.889Z", - "expires": "2025-07-25T00:11:31.889Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/99d7499e-20e6-4104-9d21-e5a0ce96ac4e.json b/src/data/sessions/99d7499e-20e6-4104-9d21-e5a0ce96ac4e.json deleted file mode 100644 index 546ba11..0000000 --- a/src/data/sessions/99d7499e-20e6-4104-9d21-e5a0ce96ac4e.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "99d7499e-20e6-4104-9d21-e5a0ce96ac4e", - "created": "2025-07-23T12:07:44.610Z", - "expires": "2025-07-24T12:07:44.610Z", - "notification": { - "type": "completed", - "project": "video_tag", - "message": "[video_tag] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/9c1e952e-8883-4a3d-8d8d-c7632c5d73e0.json b/src/data/sessions/9c1e952e-8883-4a3d-8d8d-c7632c5d73e0.json deleted file mode 100644 index 3c9b3db..0000000 --- a/src/data/sessions/9c1e952e-8883-4a3d-8d8d-c7632c5d73e0.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "9c1e952e-8883-4a3d-8d8d-c7632c5d73e0", - "created": "2025-07-12T22:40:15.010Z", - "expires": "2025-07-13T22:40:15.010Z", - "notification": { - "type": "waiting", - "project": "pandalla_api", - "message": "[pandalla_api] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/9d303a61-c307-4c15-810c-13c9a958a4a4.json b/src/data/sessions/9d303a61-c307-4c15-810c-13c9a958a4a4.json deleted file mode 100644 index f57f336..0000000 --- a/src/data/sessions/9d303a61-c307-4c15-810c-13c9a958a4a4.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "9d303a61-c307-4c15-810c-13c9a958a4a4", - "created": "2025-07-12T23:22:35.971Z", - "expires": "2025-07-13T23:22:35.971Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/9e948c0e-4e47-4a26-931a-30151f3fd4f6.json b/src/data/sessions/9e948c0e-4e47-4a26-931a-30151f3fd4f6.json deleted file mode 100644 index c6ca42c..0000000 --- a/src/data/sessions/9e948c0e-4e47-4a26-931a-30151f3fd4f6.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "9e948c0e-4e47-4a26-931a-30151f3fd4f6", - "created": "2025-07-14T09:23:28.488Z", - "expires": "2025-07-15T09:23:28.488Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/9f27e639-fca2-436b-a20c-8a39f0b3ef70.json b/src/data/sessions/9f27e639-fca2-436b-a20c-8a39f0b3ef70.json deleted file mode 100644 index 18096cd..0000000 --- a/src/data/sessions/9f27e639-fca2-436b-a20c-8a39f0b3ef70.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "9f27e639-fca2-436b-a20c-8a39f0b3ef70", - "created": "2025-07-26T12:35:10.184Z", - "expires": "2025-07-27T12:35:10.184Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/a3a18160-3e5e-4199-8b5e-4f046f4c7c0d.json b/src/data/sessions/a3a18160-3e5e-4199-8b5e-4f046f4c7c0d.json deleted file mode 100644 index 6715ad2..0000000 --- a/src/data/sessions/a3a18160-3e5e-4199-8b5e-4f046f4c7c0d.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "a3a18160-3e5e-4199-8b5e-4f046f4c7c0d", - "created": "2025-07-13T21:41:05.285Z", - "expires": "2025-07-14T21:41:05.285Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/a5344dfe-ffde-4f6c-9c5a-fadafd044a32.json b/src/data/sessions/a5344dfe-ffde-4f6c-9c5a-fadafd044a32.json deleted file mode 100644 index c4d92e1..0000000 --- a/src/data/sessions/a5344dfe-ffde-4f6c-9c5a-fadafd044a32.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "a5344dfe-ffde-4f6c-9c5a-fadafd044a32", - "created": "2025-07-23T22:19:52.121Z", - "expires": "2025-07-24T22:19:52.121Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/a543782e-c92d-44ba-ae9d-264f9184f8a4.json b/src/data/sessions/a543782e-c92d-44ba-ae9d-264f9184f8a4.json deleted file mode 100644 index a988bd8..0000000 --- a/src/data/sessions/a543782e-c92d-44ba-ae9d-264f9184f8a4.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "a543782e-c92d-44ba-ae9d-264f9184f8a4", - "created": "2025-07-13T17:39:13.757Z", - "expires": "2025-07-14T17:39:13.757Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/a6042e37-ed3f-4173-b23a-fe42fd0c4d35.json b/src/data/sessions/a6042e37-ed3f-4173-b23a-fe42fd0c4d35.json deleted file mode 100644 index daf5ea5..0000000 --- a/src/data/sessions/a6042e37-ed3f-4173-b23a-fe42fd0c4d35.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "a6042e37-ed3f-4173-b23a-fe42fd0c4d35", - "created": "2025-07-23T12:19:26.327Z", - "expires": "2025-07-24T12:19:26.327Z", - "notification": { - "type": "completed", - "project": "video_tag", - "message": "[video_tag] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/a9503913-5a84-4b83-93a7-132d5551e892.json b/src/data/sessions/a9503913-5a84-4b83-93a7-132d5551e892.json deleted file mode 100644 index dd78077..0000000 --- a/src/data/sessions/a9503913-5a84-4b83-93a7-132d5551e892.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "a9503913-5a84-4b83-93a7-132d5551e892", - "created": "2025-07-13T13:23:24.882Z", - "expires": "2025-07-14T13:23:24.882Z", - "notification": { - "type": "waiting", - "project": "pandalla_api", - "message": "[pandalla_api] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/ab697267-7e75-44fc-aac8-155453d3abcc.json b/src/data/sessions/ab697267-7e75-44fc-aac8-155453d3abcc.json deleted file mode 100644 index 6449427..0000000 --- a/src/data/sessions/ab697267-7e75-44fc-aac8-155453d3abcc.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "ab697267-7e75-44fc-aac8-155453d3abcc", - "created": "2025-07-14T09:19:41.807Z", - "expires": "2025-07-15T09:19:41.807Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/ace02d65-154a-4c95-9616-cb6e66ad06ec.json b/src/data/sessions/ace02d65-154a-4c95-9616-cb6e66ad06ec.json deleted file mode 100644 index 9db2073..0000000 --- a/src/data/sessions/ace02d65-154a-4c95-9616-cb6e66ad06ec.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "ace02d65-154a-4c95-9616-cb6e66ad06ec", - "created": "2025-07-26T18:49:27.369Z", - "expires": "2025-07-27T18:49:27.369Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/adeb1011-cd41-4c88-8ce9-2f86fd7a22bb.json b/src/data/sessions/adeb1011-cd41-4c88-8ce9-2f86fd7a22bb.json deleted file mode 100644 index 3816665..0000000 --- a/src/data/sessions/adeb1011-cd41-4c88-8ce9-2f86fd7a22bb.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "adeb1011-cd41-4c88-8ce9-2f86fd7a22bb", - "created": "2025-07-15T12:29:15.330Z", - "expires": "2025-07-16T12:29:15.330Z", - "notification": { - "type": "completed", - "project": "PandaRank", - "message": "[PandaRank] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/af6551d4-b61c-4fd9-ba7e-cfb4bc96a730.json b/src/data/sessions/af6551d4-b61c-4fd9-ba7e-cfb4bc96a730.json deleted file mode 100644 index af8d714..0000000 --- a/src/data/sessions/af6551d4-b61c-4fd9-ba7e-cfb4bc96a730.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "af6551d4-b61c-4fd9-ba7e-cfb4bc96a730", - "created": "2025-07-18T13:16:01.559Z", - "expires": "2025-07-19T13:16:01.559Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/af9972fd-8e95-4873-a700-2216fcd6e3ab.json b/src/data/sessions/af9972fd-8e95-4873-a700-2216fcd6e3ab.json deleted file mode 100644 index 26ae9ff..0000000 --- a/src/data/sessions/af9972fd-8e95-4873-a700-2216fcd6e3ab.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "af9972fd-8e95-4873-a700-2216fcd6e3ab", - "created": "2025-07-23T22:00:46.554Z", - "expires": "2025-07-24T22:00:46.554Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/afae20d9-2dc0-44d7-925b-de5eb98d65c8.json b/src/data/sessions/afae20d9-2dc0-44d7-925b-de5eb98d65c8.json deleted file mode 100644 index 24da44b..0000000 --- a/src/data/sessions/afae20d9-2dc0-44d7-925b-de5eb98d65c8.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "afae20d9-2dc0-44d7-925b-de5eb98d65c8", - "created": "2025-07-23T21:34:11.244Z", - "expires": "2025-07-24T21:34:11.244Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/b095fa44-7c8b-4283-a3ab-5eb39df19043.json b/src/data/sessions/b095fa44-7c8b-4283-a3ab-5eb39df19043.json deleted file mode 100644 index a1dcf1f..0000000 --- a/src/data/sessions/b095fa44-7c8b-4283-a3ab-5eb39df19043.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "b095fa44-7c8b-4283-a3ab-5eb39df19043", - "created": "2025-07-14T14:21:10.412Z", - "expires": "2025-07-15T14:21:10.412Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/b09f0aab-c797-45be-9ee6-35d5abf98a8a.json b/src/data/sessions/b09f0aab-c797-45be-9ee6-35d5abf98a8a.json deleted file mode 100644 index a36bdec..0000000 --- a/src/data/sessions/b09f0aab-c797-45be-9ee6-35d5abf98a8a.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "b09f0aab-c797-45be-9ee6-35d5abf98a8a", - "created": "2025-07-18T11:03:41.609Z", - "expires": "2025-07-19T11:03:41.609Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/b54374c2-af8e-4389-8e54-f93177317d1b.json b/src/data/sessions/b54374c2-af8e-4389-8e54-f93177317d1b.json deleted file mode 100644 index ed0b5c5..0000000 --- a/src/data/sessions/b54374c2-af8e-4389-8e54-f93177317d1b.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "b54374c2-af8e-4389-8e54-f93177317d1b", - "created": "2025-07-13T17:30:32.643Z", - "expires": "2025-07-14T17:30:32.643Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/b5c49341-a764-4dcc-ad8d-c0efac856500.json b/src/data/sessions/b5c49341-a764-4dcc-ad8d-c0efac856500.json deleted file mode 100644 index c2d0e5d..0000000 --- a/src/data/sessions/b5c49341-a764-4dcc-ad8d-c0efac856500.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "b5c49341-a764-4dcc-ad8d-c0efac856500", - "created": "2025-07-12T22:28:31.823Z", - "expires": "2025-07-13T22:28:31.823Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/b8b28027-d80d-4dad-b78a-eceb85c022ed.json b/src/data/sessions/b8b28027-d80d-4dad-b78a-eceb85c022ed.json deleted file mode 100644 index 6d1b4a2..0000000 --- a/src/data/sessions/b8b28027-d80d-4dad-b78a-eceb85c022ed.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "b8b28027-d80d-4dad-b78a-eceb85c022ed", - "created": "2025-07-18T11:25:42.162Z", - "expires": "2025-07-19T11:25:42.162Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/bcd403a2-f757-48c2-bdb8-a369d6e53a08.json b/src/data/sessions/bcd403a2-f757-48c2-bdb8-a369d6e53a08.json deleted file mode 100644 index 9cc5a48..0000000 --- a/src/data/sessions/bcd403a2-f757-48c2-bdb8-a369d6e53a08.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "bcd403a2-f757-48c2-bdb8-a369d6e53a08", - "created": "2025-07-18T12:40:05.075Z", - "expires": "2025-07-19T12:40:05.075Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/bdf0e984-43f9-4479-a60c-551a5725eaf4.json b/src/data/sessions/bdf0e984-43f9-4479-a60c-551a5725eaf4.json deleted file mode 100644 index 8e63264..0000000 --- a/src/data/sessions/bdf0e984-43f9-4479-a60c-551a5725eaf4.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "bdf0e984-43f9-4479-a60c-551a5725eaf4", - "created": "2025-07-12T22:54:59.612Z", - "expires": "2025-07-13T22:54:59.612Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/bf66f380-a2da-4f9a-be6c-001dd0ea5d3d.json b/src/data/sessions/bf66f380-a2da-4f9a-be6c-001dd0ea5d3d.json deleted file mode 100644 index c371d7e..0000000 --- a/src/data/sessions/bf66f380-a2da-4f9a-be6c-001dd0ea5d3d.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "bf66f380-a2da-4f9a-be6c-001dd0ea5d3d", - "created": "2025-07-17T05:16:22.479Z", - "expires": "2025-07-18T05:16:22.479Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/c279add4-8ae5-4c7d-958a-2453d2ac6f21.json b/src/data/sessions/c279add4-8ae5-4c7d-958a-2453d2ac6f21.json deleted file mode 100644 index bc23270..0000000 --- a/src/data/sessions/c279add4-8ae5-4c7d-958a-2453d2ac6f21.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "c279add4-8ae5-4c7d-958a-2453d2ac6f21", - "created": "2025-07-26T18:59:12.862Z", - "expires": "2025-07-27T18:59:12.862Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/ca96998d-f46c-41f1-999d-0b6a1537b58e.json b/src/data/sessions/ca96998d-f46c-41f1-999d-0b6a1537b58e.json deleted file mode 100644 index 78bff7c..0000000 --- a/src/data/sessions/ca96998d-f46c-41f1-999d-0b6a1537b58e.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "ca96998d-f46c-41f1-999d-0b6a1537b58e", - "created": "2025-07-12T22:37:10.322Z", - "expires": "2025-07-13T22:37:10.322Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/cb11eea6-c700-403d-abcf-7dc2d8d7bcf1.json b/src/data/sessions/cb11eea6-c700-403d-abcf-7dc2d8d7bcf1.json deleted file mode 100644 index 7e56668..0000000 --- a/src/data/sessions/cb11eea6-c700-403d-abcf-7dc2d8d7bcf1.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "cb11eea6-c700-403d-abcf-7dc2d8d7bcf1", - "created": "2025-07-12T23:28:29.122Z", - "expires": "2025-07-13T23:28:29.122Z", - "notification": { - "type": "waiting", - "project": "pandalla_api", - "message": "[pandalla_api] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/d3c89698-d823-429e-8858-98744d713313.json b/src/data/sessions/d3c89698-d823-429e-8858-98744d713313.json deleted file mode 100644 index 0697bb6..0000000 --- a/src/data/sessions/d3c89698-d823-429e-8858-98744d713313.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "d3c89698-d823-429e-8858-98744d713313", - "created": "2025-07-13T12:04:24.834Z", - "expires": "2025-07-14T12:04:24.834Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/d42c602e-9890-4789-adfd-7738b0208858.json b/src/data/sessions/d42c602e-9890-4789-adfd-7738b0208858.json deleted file mode 100644 index 0c31d56..0000000 --- a/src/data/sessions/d42c602e-9890-4789-adfd-7738b0208858.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "d42c602e-9890-4789-adfd-7738b0208858", - "created": "2025-07-14T09:44:10.727Z", - "expires": "2025-07-15T09:44:10.727Z", - "notification": { - "type": "waiting", - "project": "pandalla_api", - "message": "[pandalla_api] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/d5842899-7765-4e4d-a172-37f7be3f0fcc.json b/src/data/sessions/d5842899-7765-4e4d-a172-37f7be3f0fcc.json deleted file mode 100644 index 7f4cb84..0000000 --- a/src/data/sessions/d5842899-7765-4e4d-a172-37f7be3f0fcc.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "d5842899-7765-4e4d-a172-37f7be3f0fcc", - "created": "2025-07-13T17:26:13.794Z", - "expires": "2025-07-14T17:26:13.794Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/da66ab5c-a85d-40da-92f1-7f353976356e.json b/src/data/sessions/da66ab5c-a85d-40da-92f1-7f353976356e.json deleted file mode 100644 index 2bc1a9d..0000000 --- a/src/data/sessions/da66ab5c-a85d-40da-92f1-7f353976356e.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "da66ab5c-a85d-40da-92f1-7f353976356e", - "created": "2025-07-24T07:08:07.332Z", - "expires": "2025-07-25T07:08:07.332Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/db0c7af9-b1fa-4826-983d-d580f851aee3.json b/src/data/sessions/db0c7af9-b1fa-4826-983d-d580f851aee3.json deleted file mode 100644 index 14648eb..0000000 --- a/src/data/sessions/db0c7af9-b1fa-4826-983d-d580f851aee3.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "db0c7af9-b1fa-4826-983d-d580f851aee3", - "created": "2025-07-18T15:44:18.125Z", - "expires": "2025-07-19T15:44:18.125Z", - "notification": { - "type": "waiting", - "project": "JobShield", - "message": "[JobShield] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/dc3965b8-4188-4d23-a780-4e6b25f65167.json b/src/data/sessions/dc3965b8-4188-4d23-a780-4e6b25f65167.json deleted file mode 100644 index bdab64e..0000000 --- a/src/data/sessions/dc3965b8-4188-4d23-a780-4e6b25f65167.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "dc3965b8-4188-4d23-a780-4e6b25f65167", - "created": "2025-07-23T22:09:56.764Z", - "expires": "2025-07-24T22:09:56.764Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/dc765fa5-9e78-4fca-b337-2ab13958ce31.json b/src/data/sessions/dc765fa5-9e78-4fca-b337-2ab13958ce31.json deleted file mode 100644 index 73eb32e..0000000 --- a/src/data/sessions/dc765fa5-9e78-4fca-b337-2ab13958ce31.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "dc765fa5-9e78-4fca-b337-2ab13958ce31", - "created": "2025-07-14T10:19:59.412Z", - "expires": "2025-07-15T10:19:59.412Z", - "notification": { - "type": "waiting", - "project": "pandalla_api", - "message": "[pandalla_api] Claude需要您的进一步指导" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/e347d063-1d52-497e-a2be-01cb35acf985.json b/src/data/sessions/e347d063-1d52-497e-a2be-01cb35acf985.json deleted file mode 100644 index bfece86..0000000 --- a/src/data/sessions/e347d063-1d52-497e-a2be-01cb35acf985.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "e347d063-1d52-497e-a2be-01cb35acf985", - "created": "2025-07-23T11:57:59.324Z", - "expires": "2025-07-24T11:57:59.324Z", - "notification": { - "type": "completed", - "project": "video_tag", - "message": "[video_tag] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/e92d7906-174b-4041-bcce-6d35f1e5d0ce.json b/src/data/sessions/e92d7906-174b-4041-bcce-6d35f1e5d0ce.json deleted file mode 100644 index 7f2e15d..0000000 --- a/src/data/sessions/e92d7906-174b-4041-bcce-6d35f1e5d0ce.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "e92d7906-174b-4041-bcce-6d35f1e5d0ce", - "created": "2025-07-23T21:27:14.081Z", - "expires": "2025-07-24T21:27:14.081Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/ec2a3e14-5a67-4c82-8f79-cfd47dc749e0.json b/src/data/sessions/ec2a3e14-5a67-4c82-8f79-cfd47dc749e0.json deleted file mode 100644 index 496d705..0000000 --- a/src/data/sessions/ec2a3e14-5a67-4c82-8f79-cfd47dc749e0.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "ec2a3e14-5a67-4c82-8f79-cfd47dc749e0", - "created": "2025-07-14T09:16:51.780Z", - "expires": "2025-07-15T09:16:51.780Z", - "notification": { - "type": "completed", - "project": "pandalla_api", - "message": "[pandalla_api] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/eeb90ce8-a724-46bb-be51-0924f5260f36.json b/src/data/sessions/eeb90ce8-a724-46bb-be51-0924f5260f36.json deleted file mode 100644 index 775df45..0000000 --- a/src/data/sessions/eeb90ce8-a724-46bb-be51-0924f5260f36.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "eeb90ce8-a724-46bb-be51-0924f5260f36", - "created": "2025-07-18T11:46:45.421Z", - "expires": "2025-07-19T11:46:45.421Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/f365d443-694c-4e4f-9843-dab6a578d61a.json b/src/data/sessions/f365d443-694c-4e4f-9843-dab6a578d61a.json deleted file mode 100644 index af59d64..0000000 --- a/src/data/sessions/f365d443-694c-4e4f-9843-dab6a578d61a.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "f365d443-694c-4e4f-9843-dab6a578d61a", - "created": "2025-07-23T20:56:09.678Z", - "expires": "2025-07-24T20:56:09.678Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/f42db288-d410-4e3b-bf54-8f766a817613.json b/src/data/sessions/f42db288-d410-4e3b-bf54-8f766a817613.json deleted file mode 100644 index 9c2c60c..0000000 --- a/src/data/sessions/f42db288-d410-4e3b-bf54-8f766a817613.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "f42db288-d410-4e3b-bf54-8f766a817613", - "created": "2025-07-13T17:38:11.270Z", - "expires": "2025-07-14T17:38:11.270Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/f44f853d-d936-4b74-87bd-b63d0cca2885.json b/src/data/sessions/f44f853d-d936-4b74-87bd-b63d0cca2885.json deleted file mode 100644 index a551721..0000000 --- a/src/data/sessions/f44f853d-d936-4b74-87bd-b63d0cca2885.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "f44f853d-d936-4b74-87bd-b63d0cca2885", - "created": "2025-07-18T11:54:24.717Z", - "expires": "2025-07-19T11:54:24.717Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/f4601ed9-b556-41bc-8abb-691679403a2d.json b/src/data/sessions/f4601ed9-b556-41bc-8abb-691679403a2d.json deleted file mode 100644 index 8aa67c3..0000000 --- a/src/data/sessions/f4601ed9-b556-41bc-8abb-691679403a2d.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "f4601ed9-b556-41bc-8abb-691679403a2d", - "created": "2025-07-13T17:48:17.334Z", - "expires": "2025-07-14T17:48:17.334Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/f812dabb-bce1-45a2-b818-d3d05f21468b.json b/src/data/sessions/f812dabb-bce1-45a2-b818-d3d05f21468b.json deleted file mode 100644 index 975898a..0000000 --- a/src/data/sessions/f812dabb-bce1-45a2-b818-d3d05f21468b.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "f812dabb-bce1-45a2-b818-d3d05f21468b", - "created": "2025-07-18T13:56:12.911Z", - "expires": "2025-07-19T13:56:12.911Z", - "notification": { - "type": "completed", - "project": "JobShield", - "message": "[JobShield] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/f87ccea0-5c1c-4257-91cf-7fa8ac0f7f07.json b/src/data/sessions/f87ccea0-5c1c-4257-91cf-7fa8ac0f7f07.json deleted file mode 100644 index c36d463..0000000 --- a/src/data/sessions/f87ccea0-5c1c-4257-91cf-7fa8ac0f7f07.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "f87ccea0-5c1c-4257-91cf-7fa8ac0f7f07", - "created": "2025-07-12T22:27:57.091Z", - "expires": "2025-07-13T22:27:57.091Z", - "notification": { - "type": "completed", - "project": "TaskPing-Test", - "message": "这是一封测试邮件,用于验证邮件通知功能是否正常工作。" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/fb2e5275-a6d5-4517-b485-29884747f0de.json b/src/data/sessions/fb2e5275-a6d5-4517-b485-29884747f0de.json deleted file mode 100644 index aa32f93..0000000 --- a/src/data/sessions/fb2e5275-a6d5-4517-b485-29884747f0de.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "fb2e5275-a6d5-4517-b485-29884747f0de", - "created": "2025-07-23T21:09:53.860Z", - "expires": "2025-07-24T21:09:53.860Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/fb9c0090-dad7-4482-9885-e0b84daaa4e4.json b/src/data/sessions/fb9c0090-dad7-4482-9885-e0b84daaa4e4.json deleted file mode 100644 index 23be2c4..0000000 --- a/src/data/sessions/fb9c0090-dad7-4482-9885-e0b84daaa4e4.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "fb9c0090-dad7-4482-9885-e0b84daaa4e4", - "created": "2025-07-26T19:05:11.062Z", - "expires": "2025-07-27T19:05:11.062Z", - "notification": { - "type": "completed", - "project": "TaskPing", - "message": "[TaskPing] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/data/sessions/febf9d85-1579-41c4-aca1-91ddf782b961.json b/src/data/sessions/febf9d85-1579-41c4-aca1-91ddf782b961.json deleted file mode 100644 index e1e6472..0000000 --- a/src/data/sessions/febf9d85-1579-41c4-aca1-91ddf782b961.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "febf9d85-1579-41c4-aca1-91ddf782b961", - "created": "2025-07-23T20:44:25.533Z", - "expires": "2025-07-24T20:44:25.533Z", - "notification": { - "type": "completed", - "project": "pandalla.ai", - "message": "[pandalla.ai] 任务已完成,Claude正在等待下一步指令" - }, - "status": "waiting", - "commandCount": 0, - "maxCommands": 10 -} \ No newline at end of file diff --git a/src/relay/relay-pty.js b/src/relay/relay-pty.js index a391ddc..3e39c39 100644 --- a/src/relay/relay-pty.js +++ b/src/relay/relay-pty.js @@ -1,40 +1,75 @@ +#!/usr/bin/env node + /** - * Relay PTY Service - * 使用 node-pty 管理 Claude Code 进程并注入邮件命令 + * relay-pty.js - 修复版本 + * 使用 node-imap 替代 ImapFlow 来解决飞书邮箱兼容性问题 */ -const { ImapFlow } = require('imapflow'); +require('dotenv').config(); +const Imap = require('node-imap'); const { simpleParser } = require('mailparser'); -const pino = require('pino'); -const { readFileSync, writeFileSync, existsSync } = require('fs'); -const { spawn: spawnPty } = require('node-pty'); -const dotenv = require('dotenv'); +const { spawn } = require('node-pty'); +const { existsSync, readFileSync, writeFileSync } = require('fs'); const path = require('path'); +const pino = require('pino'); -// 加载环境变量 -dotenv.config(); - -// 初始化日志 -const log = pino({ +// 配置日志 +const log = pino({ level: process.env.LOG_LEVEL || 'info', transport: { target: 'pino-pretty', options: { colorize: true, - translateTime: 'HH:MM:ss.l', - ignore: 'pid,hostname' + translateTime: 'HH:MM:ss' } } }); -// 会话映射文件路径 +// 全局配置 const SESS_PATH = process.env.SESSION_MAP_PATH || path.join(__dirname, '../data/session-map.json'); - -// PTY 池,管理活跃的 Claude Code 进程 +const PROCESSED_PATH = path.join(__dirname, '../data/processed-messages.json'); +const ALLOWED_SENDERS = (process.env.ALLOWED_SENDERS || '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean); const PTY_POOL = new Map(); +let PROCESSED_MESSAGES = new Set(); -// 已处理的邮件ID集合,用于去重 -const PROCESSED_MESSAGES = new Set(); +// 加载已处理消息 +function loadProcessedMessages() { + if (existsSync(PROCESSED_PATH)) { + try { + const data = JSON.parse(readFileSync(PROCESSED_PATH, 'utf8')); + const now = Date.now(); + // 只保留7天内的记录 + const validMessages = data.filter(item => (now - item.timestamp) < 7 * 24 * 60 * 60 * 1000); + PROCESSED_MESSAGES = new Set(validMessages.map(item => item.id)); + // 更新文件,移除过期记录 + saveProcessedMessages(); + } catch (error) { + log.error({ error }, 'Failed to load processed messages'); + PROCESSED_MESSAGES = new Set(); + } + } +} + +// 保存已处理消息 +function saveProcessedMessages() { + try { + const now = Date.now(); + const data = Array.from(PROCESSED_MESSAGES).map(id => ({ + id, + timestamp: now + })); + + // 确保目录存在 + const dir = path.dirname(PROCESSED_PATH); + if (!existsSync(dir)) { + require('fs').mkdirSync(dir, { recursive: true }); + } + + writeFileSync(PROCESSED_PATH, JSON.stringify(data, null, 2)); + } catch (error) { + log.error({ error }, 'Failed to save processed messages'); + } +} // 加载会话映射 function loadSessions() { @@ -47,41 +82,15 @@ function loadSessions() { } } -// 保存会话映射 -function saveSessions(map) { - try { - const dir = path.dirname(SESS_PATH); - if (!existsSync(dir)) { - require('fs').mkdirSync(dir, { recursive: true }); - } - writeFileSync(SESS_PATH, JSON.stringify(map, null, 2)); - } catch (error) { - log.error({ error }, 'Failed to save session map'); - } -} - -// 标准化允许的发件人列表 -function normalizeAllowlist() { - return (process.env.ALLOWED_SENDERS || '') - .split(',') - .map(s => s.trim().toLowerCase()) - .filter(Boolean); -} - -const ALLOW = new Set(normalizeAllowlist()); - // 检查发件人是否在白名单中 -function isAllowed(addressObj) { - if (!addressObj) return false; - const list = [] - .concat(addressObj.value || []) - .map(a => (a.address || '').toLowerCase()); - return list.some(addr => ALLOW.has(addr)); +function isAllowed(fromAddress) { + if (!fromAddress) return false; + const addr = fromAddress.toLowerCase(); + return ALLOWED_SENDERS.some(allowed => addr.includes(allowed)); } // 从主题中提取 TaskPing token function extractTokenFromSubject(subject = '') { - // 支持多种格式: [TaskPing #TOKEN], [TaskPing TOKEN], TaskPing: TOKEN const patterns = [ /\[TaskPing\s+#([A-Za-z0-9_-]+)\]/, /\[TaskPing\s+([A-Za-z0-9_-]+)\]/, @@ -96,499 +105,596 @@ function extractTokenFromSubject(subject = '') { return null; } -// 从会话ID中提取 token (支持向后兼容) -function extractTokenFromSessionId(sessionId) { - if (!sessionId) return null; - - // 如果sessionId本身就是token格式 - if (/^[A-Za-z0-9_-]+$/.test(sessionId)) { - return sessionId; - } - - // 从UUID格式的sessionId中提取token(如果有映射) - const sessions = loadSessions(); - for (const [token, session] of Object.entries(sessions)) { - if (session.sessionId === sessionId) { - return token; - } - } - - return null; -} - -// 清理邮件回复内容,提取命令 -function stripReply(text = '') { - // 优先识别 ``` 代码块 - const codeBlock = text.match(/```[\s\S]*?```/); - if (codeBlock) { - return codeBlock[0].replace(/```/g, '').trim(); - } - - // 查找 CMD: 前缀的命令 - const cmdMatch = text.match(/^CMD:\s*(.+)$/im); - if (cmdMatch) { - return cmdMatch[1].trim(); - } - - // 清理邮件内容,移除引用和签名 +// 清理邮件正文 +function cleanEmailText(text = '') { const lines = text.split(/\r?\n/); const cleanLines = []; for (const line of lines) { - // 检测邮件引用开始标记 - if (line.match(/^>+\s*/) || // 引用标记 - line.includes('-----Original Message-----') || + // 检测引用内容(更全面的检测) + if (line.includes('-----Original Message-----') || line.includes('--- Original Message ---') || line.includes('在') && line.includes('写道:') || line.includes('On') && line.includes('wrote:') || line.includes('会话ID:') || - line.includes('Session ID:')) { + line.includes('Session ID:') || + line.includes('') || + line.includes('TaskPing 通知系统') || + line.includes('于2025年') && line.includes('写道:') || + line.match(/^>.*/) || // 引用行以 > 开头 + line.includes('From:') && line.includes('@') || + line.includes('To:') && line.includes('@') || + line.includes('Subject:') || + line.includes('Sent:') || + line.includes('Date:')) { break; } // 检测邮件签名 if (line.match(/^--\s*$/) || line.includes('Sent from') || - line.includes('发自我的')) { + line.includes('发自我的') || + line.includes('Best regards') || + line.includes('此致敬礼')) { break; } cleanLines.push(line); } - // 获取有效内容的第一行 + // 获取有效内容 const cleanText = cleanLines.join('\n').trim(); - const firstLine = cleanText.split(/\r?\n/).find(l => l.trim().length > 0) || ''; - // 长度限制 - return firstLine.slice(0, 8192).trim(); + // 查找实际的命令内容(跳过问候语等) + const contentLines = cleanText.split(/\r?\n/).filter(l => l.trim().length > 0); + + // 查找命令行(通常是包含实际命令的行) + for (const line of contentLines) { + const trimmedLine = line.trim(); + // 跳过常见的问候语 + if (trimmedLine.match(/^(hi|hello|谢谢|thanks|好的|ok|是的|yes)/i)) { + continue; + } + // 跳过纯中文问候 + if (trimmedLine.match(/^(这是|请|帮我|您好)/)) { + continue; + } + // 跳过邮件引用残留 + if (trimmedLine.includes('TaskPing 通知系统') || + trimmedLine.includes('') || + trimmedLine.includes('于2025年')) { + continue; + } + // 如果找到疑似命令的行,检查并去重 + if (trimmedLine.length > 3) { + const command = trimmedLine.slice(0, 8192); + // 检查命令是否重复(如:"喝可乐好吗喝可乐好吗") + const deduplicatedCommand = deduplicateCommand(command); + return deduplicatedCommand; + } + } + + // 如果没有找到明显的命令,返回第一行非空内容(并去重) + const firstLine = contentLines[0] || ''; + const command = firstLine.slice(0, 8192).trim(); + return deduplicateCommand(command); +} + +// 去重复的命令文本(处理如:"喝可乐好吗喝可乐好吗" -> "喝可乐好吗") +function deduplicateCommand(command) { + if (!command || command.length === 0) { + return command; + } + + // 检查命令是否是自己重复的 + const length = command.length; + for (let i = 1; i <= Math.floor(length / 2); i++) { + const firstPart = command.substring(0, i); + const remaining = command.substring(i); + + // 检查剩余部分是否完全重复第一部分 + if (remaining === firstPart.repeat(Math.floor(remaining.length / firstPart.length))) { + // 找到重复模式,返回第一部分 + log.debug({ + originalCommand: command, + deduplicatedCommand: firstPart, + pattern: firstPart + }, 'Detected and removed command duplication'); + return firstPart; + } + } + + // 没有检测到重复,返回原命令 + return command; +} + +// 无人值守远程命令注入 - tmux优先,智能备用 +async function injectCommandRemote(token, command) { + const sessions = loadSessions(); + const session = sessions[token]; + + if (!session) { + log.warn({ token }, 'Session not found'); + return false; + } + + // 检查会话是否过期 + const now = Math.floor(Date.now() / 1000); + if (session.expiresAt && session.expiresAt < now) { + log.warn({ token }, 'Session expired'); + return false; + } + + try { + log.info({ token, command }, 'Starting remote command injection'); + + // 方法1: 优先使用tmux无人值守注入 + const TmuxInjector = require('./tmux-injector'); + const tmuxSessionName = session.tmuxSession || 'claude-taskping'; + const tmuxInjector = new TmuxInjector(log, tmuxSessionName); + + const tmuxResult = await tmuxInjector.injectCommandFull(token, command); + + if (tmuxResult.success) { + log.info({ token, session: tmuxResult.session }, 'Tmux remote injection successful'); + return true; + } else { + log.warn({ token, error: tmuxResult.error }, 'Tmux injection failed, trying smart fallback'); + + // 方法2: 回退到智能注入器 + const SmartInjector = require('./smart-injector'); + const smartInjector = new SmartInjector(log); + + const smartResult = await smartInjector.injectCommand(token, command); + + if (smartResult) { + log.info({ token }, 'Smart injection fallback successful'); + return true; + } else { + log.error({ token }, 'All remote injection methods failed'); + return false; + } + } + + } catch (error) { + log.error({ error, token }, 'Failed to inject command remotely'); + return false; + } +} + +// 尝试自动粘贴到活跃窗口 +async function tryAutoPaste(command) { + return new Promise((resolve) => { + // 先复制命令到剪贴板 + const { spawn } = require('child_process'); + const pbcopy = spawn('pbcopy'); + pbcopy.stdin.write(command); + pbcopy.stdin.end(); + + pbcopy.on('close', (code) => { + if (code !== 0) { + resolve({ success: false, error: 'clipboard_copy_failed' }); + return; + } + + // 执行AppleScript自动粘贴 + const autoScript = ` + tell application "System Events" + set claudeApps to {"Claude", "Claude Code", "Terminal", "iTerm2", "iTerm"} + set targetApp to null + set targetName to "" + + repeat with appName in claudeApps + try + if application process appName exists then + set targetApp to application process appName + set targetName to appName + exit repeat + end if + end try + end repeat + + if targetApp is not null then + set frontmost of targetApp to true + delay 0.8 + + repeat 10 times + if frontmost of targetApp then exit repeat + delay 0.1 + end repeat + + if targetName is in {"Terminal", "iTerm2", "iTerm"} then + keystroke "${command.replace(/"/g, '\\"')}" + delay 0.3 + keystroke return + return "terminal_typed" + else + keystroke "a" using command down + delay 0.2 + keystroke "v" using command down + delay 0.5 + keystroke return + return "claude_pasted" + end if + else + return "no_target_found" + end if + end tell + `; + + const { exec } = require('child_process'); + exec(`osascript -e '${autoScript}'`, (error, stdout, stderr) => { + if (error) { + resolve({ success: false, error: error.message }); + return; + } + + const result = stdout.trim(); + + switch(result) { + case 'terminal_typed': + resolve({ success: true, method: '终端直接输入' }); + break; + case 'claude_pasted': + resolve({ success: true, method: 'Claude应用粘贴' }); + break; + case 'no_target_found': + resolve({ success: false, error: 'no_target_application' }); + break; + default: + resolve({ success: false, error: `unknown_result: ${result}` }); + } + }); + }); + }); +} + +// 回退到剪贴板+强提醒 +async function fallbackToClipboard(command) { + return new Promise((resolve) => { + // 复制到剪贴板 + const { spawn } = require('child_process'); + const pbcopy = spawn('pbcopy'); + pbcopy.stdin.write(command); + pbcopy.stdin.end(); + + pbcopy.on('close', (code) => { + if (code !== 0) { + resolve(false); + return; + } + + // 发送强提醒通知 + const shortCommand = command.length > 30 ? command.substring(0, 30) + '...' : command; + const notificationScript = ` + display notification "🚨 邮件命令已自动复制!请立即在Claude Code中粘贴执行 (Cmd+V)" with title "TaskPing 自动注入" subtitle "${shortCommand.replace(/"/g, '\\"')}" sound name "Basso" + `; + + const { exec } = require('child_process'); + exec(`osascript -e '${notificationScript}'`, (error) => { + if (error) { + log.warn({ error: error.message }, 'Failed to send notification'); + } else { + log.info('Strong reminder notification sent'); + } + resolve(true); + }); + }); + }); } // 处理邮件消息 -async function handleMailMessage(source, uid, parsedEmail = null) { +async function handleMailMessage(parsed) { try { - const parsed = parsedEmail || await simpleParser(source); - - // 检查是否已处理过 + log.debug({ uid: parsed.uid, messageId: parsed.messageId }, 'handleMailMessage called'); + // 简化的重复检测(UID已在前面检查过) + const uid = parsed.uid; const messageId = parsed.messageId; - if (messageId && PROCESSED_MESSAGES.has(messageId)) { - log.debug({ messageId }, 'Message already processed, skipping'); - return; + + // 仅对没有UID的邮件进行额外检查 + if (!uid) { + const identifier = messageId; + if (identifier && PROCESSED_MESSAGES.has(identifier)) { + log.debug({ messageId, identifier }, 'Message already processed by messageId, skipping'); + return; + } + + // 内容哈希去重(作为最后手段) + const emailSubject = parsed.subject || ''; + const emailDate = parsed.date || new Date(); + const contentHash = `${emailSubject}_${emailDate.getTime()}`; + + if (PROCESSED_MESSAGES.has(contentHash)) { + log.debug({ subject: emailSubject, date: emailDate, contentHash }, 'Message already processed by content hash, skipping'); + return; + } } // 验证发件人 - if (!isAllowed(parsed.from)) { + if (!isAllowed(parsed.from?.text || '')) { log.warn({ from: parsed.from?.text }, 'Sender not allowed'); return; } - // 提取 token + // 提取token const subject = parsed.subject || ''; - let token = extractTokenFromSubject(subject); - - // 如果主题中没有token,尝试从邮件头或正文中提取 - if (!token) { - // 检查自定义邮件头 - const sessionIdHeader = parsed.headers?.get('x-taskping-session-id'); - if (sessionIdHeader) { - token = extractTokenFromSessionId(sessionIdHeader); - } - - // 从正文中查找会话ID - if (!token) { - const bodyText = parsed.text || ''; - const sessionMatch = bodyText.match(/会话ID:\s*([a-f0-9-]{36})/i); - if (sessionMatch) { - token = extractTokenFromSessionId(sessionMatch[1]); - } - } - } + const token = extractTokenFromSubject(subject); if (!token) { log.warn({ subject }, 'No token found in email'); return; } - // 加载会话信息 - const sessions = loadSessions(); - const sess = sessions[token]; - - if (!sess) { - log.warn({ token }, 'Session not found'); - return; - } - - // 检查会话是否过期 - if (sess.expiresAt && sess.expiresAt * 1000 < Date.now()) { - log.warn({ token }, 'Session expired'); - // 清理过期会话 - delete sessions[token]; - saveSessions(sessions); - return; - } - - // 提取命令 - const cmd = stripReply(parsed.text || parsed.html || ''); - if (!cmd) { - log.warn({ token }, 'Empty command after stripping'); - return; - } - - // 获取或创建 PTY - const pty = await getOrCreatePty(token, sess); - if (!pty) { - log.error({ token }, 'Failed to get PTY'); - return; - } - - // 注入命令 - log.info({ + // 提取命令 - 添加详细调试 + log.debug({ token, - command: cmd.slice(0, 120), - from: parsed.from?.text - }, 'Injecting command to Claude Code'); + rawEmailText: parsed.text?.substring(0, 500), + emailSubject: parsed.subject + }, 'Raw email content before cleaning'); - // 发送命令并按回车 - pty.write(cmd + '\r'); + const command = cleanEmailText(parsed.text); - // 标记邮件为已处理 - if (messageId) { - PROCESSED_MESSAGES.add(messageId); + log.debug({ + token, + cleanedCommand: command, + commandLength: command?.length + }, 'Email content after cleaning'); + + if (!command) { + log.warn({ token }, 'No command found in email'); + return; } - // 更新会话状态 - sess.lastCommand = cmd; - sess.lastCommandAt = Date.now(); - sess.commandCount = (sess.commandCount || 0) + 1; - sessions[token] = sess; - saveSessions(sessions); + log.info({ token, command }, 'Processing email command'); + + // 无人值守远程命令注入(tmux优先,智能备用) + const success = await injectCommandRemote(token, command); + + if (!success) { + log.warn({ token }, 'Could not inject command'); + return; + } + + // 标记为已处理(只在成功处理后标记) + if (uid) { + // 标记UID为已处理 + PROCESSED_MESSAGES.add(uid); + log.debug({ uid }, 'Marked message UID as processed'); + } else { + // 没有UID的邮件,使用messageId和内容哈希 + if (messageId) { + PROCESSED_MESSAGES.add(messageId); + log.debug({ messageId }, 'Marked message as processed by messageId'); + } + + // 内容哈希标记 + const emailSubject = parsed.subject || ''; + const emailDate = parsed.date || new Date(); + const contentHash = `${emailSubject}_${emailDate.getTime()}`; + PROCESSED_MESSAGES.add(contentHash); + log.debug({ contentHash }, 'Marked message as processed by content hash'); + } + + // 持久化已处理消息 + saveProcessedMessages(); + + log.info({ token }, 'Command injected successfully via remote method'); } catch (error) { - log.error({ error, uid }, 'Error handling mail message'); + log.error({ error }, 'Failed to handle email message'); } } -// 获取或创建 PTY 实例 -async function getOrCreatePty(token, session) { - // 检查是否已有 PTY 实例 - if (PTY_POOL.has(token)) { - const pty = PTY_POOL.get(token); - // 检查 PTY 是否还活着 - try { - // node-pty 没有直接的 isAlive 方法,但我们可以尝试写入空字符来测试 - pty.write(''); - return pty; - } catch (error) { - log.warn({ token }, 'PTY is dead, removing from pool'); - PTY_POOL.delete(token); - } - } +// 启动IMAP监听 +function startImap() { + // 首先加载已处理消息 + loadProcessedMessages(); - // 创建新的 PTY 实例 - try { - const shell = process.env.CLAUDE_CLI_PATH || 'claude'; - const args = []; - - // 如果会话有特定的工作目录,设置它 - const cwd = session.cwd || process.cwd(); - - log.info({ token, shell, cwd }, 'Spawning new Claude Code PTY'); - - const pty = spawnPty(shell, args, { - name: 'xterm-256color', - cols: 120, - rows: 40, - cwd: cwd, - env: process.env - }); - - // 存储 PTY 实例 - PTY_POOL.set(token, pty); - - // 设置输出处理(可选:记录到文件或发送通知) - pty.onData((data) => { - // 可以在这里添加输出日志或通知逻辑 - if (process.env.PTY_OUTPUT_LOG === 'true') { - log.debug({ token, output: data.slice(0, 200) }, 'PTY output'); - } - }); - - // 处理 PTY 退出 - pty.onExit(({ exitCode, signal }) => { - log.info({ token, exitCode, signal }, 'PTY exited'); - PTY_POOL.delete(token); - - // 更新会话状态 - const sessions = loadSessions(); - if (sessions[token]) { - sessions[token].ptyExited = true; - sessions[token].ptyExitedAt = Date.now(); - saveSessions(sessions); - } - }); - - // 等待 Claude Code 初始化 - await new Promise(resolve => setTimeout(resolve, 1000)); - - return pty; - - } catch (error) { - log.error({ error, token }, 'Failed to spawn PTY'); - return null; - } -} - -// 启动 IMAP 监听 -async function startImap() { - const client = new ImapFlow({ - host: process.env.IMAP_HOST, - port: Number(process.env.IMAP_PORT || 993), - secure: process.env.IMAP_SECURE === 'true', - auth: { - user: process.env.IMAP_USER, - pass: process.env.IMAP_PASS - }, - logger: false // 禁用 ImapFlow 的内置日志 - }); - - try { - await client.connect(); - log.info('Connected to IMAP server'); - - // 打开收件箱 - const lock = await client.getMailboxLock('INBOX'); - - try { - // 获取最近的未读邮件 - const messages = await client.search({ seen: false }); - if (messages.length > 0) { - log.info(`Found ${messages.length} unread messages`); - - for (const uid of messages) { - try { - log.debug({ uid }, 'Downloading message'); - const message = await client.fetchOne(uid, { - bodyText: true, - bodyStructure: true, - envelope: true - }, { uid: true }); - - if (!message) { - log.warn({ uid }, 'Could not fetch message'); - continue; - } - - // 使用简单的方式获取邮件内容 - const emailText = message.bodyText?.value || ''; - const envelope = message.envelope; - - // 构造简单的邮件对象用于解析 - const simpleEmail = { - from: envelope.from?.[0] ? { - text: `${envelope.from[0].name || ''} <${envelope.from[0].address}>`, - value: envelope.from - } : null, - subject: envelope.subject, - text: emailText, - date: envelope.date, - messageId: envelope.messageId, - headers: new Map() - }; - - await handleMailMessage(null, uid, simpleEmail); - - // 标记为已读 - await client.messageFlagsAdd(uid, ['\\Seen'], { uid: true }); - } catch (downloadError) { - log.error({ - uid, - error: downloadError.message, - code: downloadError.code - }, 'Failed to download message'); - } - } - } - - // 使用定期检查替代IDLE监听(更稳定) - log.info('Starting periodic email check...'); - - setInterval(async () => { - try { - const newMessages = await client.search({ seen: false }); - if (newMessages.length > 0) { - log.debug({ count: newMessages.length }, 'Found new messages'); - - for (const uid of newMessages) { - // 检查是否已处理过这个UID - if (PROCESSED_MESSAGES.has(`uid_${uid}`)) { - continue; - } - - try { - log.debug({ uid }, 'Processing new message'); - const message = await client.fetchOne(uid, { - bodyText: true, - bodyStructure: true, - envelope: true - }, { uid: true }); - - if (!message) { - log.warn({ uid }, 'Could not fetch new message'); - continue; - } - - // 使用简单的方式获取邮件内容 - const emailText = message.bodyText?.value || ''; - const envelope = message.envelope; - - // 构造简单的邮件对象用于解析 - const simpleEmail = { - from: envelope.from?.[0] ? { - text: `${envelope.from[0].name || ''} <${envelope.from[0].address}>`, - value: envelope.from - } : null, - subject: envelope.subject, - text: emailText, - date: envelope.date, - messageId: envelope.messageId, - headers: new Map() - }; - - await handleMailMessage(null, uid, simpleEmail); - - // 标记为已处理 - PROCESSED_MESSAGES.add(`uid_${uid}`); - - // 标记为已读 - await client.messageFlagsAdd(uid, ['\\Seen'], { uid: true }); - } catch (processError) { - log.error({ - uid, - error: processError.message, - code: processError.code - }, 'Failed to process new message'); - } - } - } - } catch (checkError) { - log.error({ error: checkError.message }, 'Error during periodic check'); - } - }, 10000); // 每10秒检查一次 - - // 保持连接活跃 - log.info('Email monitoring active, checking every 10 seconds...'); - - // 无限等待(保持进程运行) - await new Promise(resolve => { - // 进程会一直运行直到被终止 - }); - } finally { - lock.release(); - } - } catch (error) { - log.error({ - error: error.message, - code: error.code, - stack: error.stack - }, 'IMAP error'); - throw error; - } finally { - await client.logout(); - } -} - -// 清理过期会话和孤儿 PTY -function cleanupSessions() { - const sessions = loadSessions(); - const now = Date.now(); - let cleaned = 0; - - for (const [token, session] of Object.entries(sessions)) { - // 清理过期会话 - if (session.expiresAt && session.expiresAt * 1000 < now) { - delete sessions[token]; - cleaned++; - - // 终止相关的 PTY - if (PTY_POOL.has(token)) { - const pty = PTY_POOL.get(token); - try { - pty.kill(); - } catch (error) { - log.warn({ token, error }, 'Failed to kill PTY'); - } - PTY_POOL.delete(token); - } - } - } - - if (cleaned > 0) { - log.info({ cleaned }, 'Cleaned up expired sessions'); - saveSessions(sessions); - } -} - -// 主函数 -async function main() { - // 验证配置 - const required = ['IMAP_HOST', 'IMAP_USER', 'IMAP_PASS']; - const missing = required.filter(key => !process.env[key]); - - if (missing.length > 0) { - log.error({ missing }, 'Missing required environment variables'); - process.exit(1); - } - - // 显示配置信息 - log.info({ + log.info('Starting relay-pty service', { mode: 'pty', imapHost: process.env.IMAP_HOST, imapUser: process.env.IMAP_USER, - allowedSenders: Array.from(ALLOW), - sessionMapPath: SESS_PATH - }, 'Starting relay-pty service'); + allowedSenders: ALLOWED_SENDERS, + sessionMapPath: SESS_PATH, + processedCount: PROCESSED_MESSAGES.size + }); - // 定期清理 - setInterval(cleanupSessions, 5 * 60 * 1000); // 每5分钟清理一次 + const imap = new Imap({ + user: process.env.IMAP_USER, + password: process.env.IMAP_PASS, + host: process.env.IMAP_HOST, + port: parseInt(process.env.IMAP_PORT) || 993, + tls: process.env.IMAP_SECURE === 'true', + connTimeout: 60000, + authTimeout: 30000, + keepalive: true + }); - // 处理退出信号 - process.on('SIGINT', () => { - log.info('Shutting down...'); + imap.once('ready', function() { + log.info('Connected to IMAP server'); - // 终止所有 PTY - for (const [token, pty] of PTY_POOL.entries()) { - try { - pty.kill(); - } catch (error) { - log.warn({ token }, 'Failed to kill PTY on shutdown'); + imap.openBox('INBOX', false, function(err, box) { + if (err) { + log.error({ error: err.message }, 'Failed to open INBOX'); + return; + } + + log.info(`Mailbox opened: ${box.messages.total} total messages, ${box.messages.new} new`); + + // 只在启动时处理现有的未读邮件 + processExistingEmails(imap); + + // 监听新邮件(主要机制) + imap.on('mail', function(numNewMsgs) { + log.info({ newMessages: numNewMsgs }, 'New mail arrived'); + // 增加延迟,避免与现有邮件处理冲突 + setTimeout(() => { + processNewEmails(imap); + }, 1000); + }); + + // 定期检查新邮件(仅作为备用,延长间隔) + setInterval(() => { + log.debug('Periodic email check...'); + processNewEmails(imap); + }, 120000); // 每2分钟检查一次,减少频率 + }); + }); + + imap.once('error', function(err) { + log.error({ error: err.message }, 'IMAP error'); + // 重连机制 + setTimeout(() => { + log.info('Attempting to reconnect...'); + startImap(); + }, 10000); + }); + + imap.once('end', function() { + log.info('IMAP connection ended'); + }); + + imap.connect(); + + // 优雅关闭 + process.on('SIGINT', () => { + log.info('Shutting down gracefully...'); + imap.end(); + process.exit(0); + }); +} + +// 处理现有邮件 +function processExistingEmails(imap) { + // 搜索未读邮件 + imap.search(['UNSEEN'], function(err, results) { + if (err) { + log.error({ error: err.message }, 'Failed to search emails'); + return; + } + + if (results.length > 0) { + log.info(`Found ${results.length} unread messages`); + log.debug({ uids: results }, 'Unread message UIDs'); + fetchAndProcessEmails(imap, results); + } else { + log.debug('No unread messages found'); + } + }); +} + +// 处理新邮件 +function processNewEmails(imap) { + // 搜索最近5分钟的邮件 + const since = new Date(); + since.setMinutes(since.getMinutes() - 5); + const sinceStr = since.toISOString().split('T')[0]; // YYYY-MM-DD + + imap.search([['SINCE', sinceStr], 'UNSEEN'], function(err, results) { + if (err) { + log.error({ error: err.message }, 'Failed to search new emails'); + return; + } + + if (results.length > 0) { + log.info(`Found ${results.length} new messages`); + fetchAndProcessEmails(imap, results); + } + }); +} + +// 获取并处理邮件 +function fetchAndProcessEmails(imap, uids) { + log.debug({ uids }, 'Starting to fetch emails'); + const fetch = imap.fetch(uids, { + bodies: '', // 获取完整邮件 + markSeen: true // 标记为已读 + }); + + fetch.on('message', function(msg, seqno) { + let buffer = ''; + let messageUid = null; + let skipProcessing = false; + let bodyProcessed = false; + let attributesReceived = false; + + // 获取UID以防重复处理 + msg.once('attributes', function(attrs) { + messageUid = attrs.uid; + attributesReceived = true; + log.debug({ uid: messageUid, seqno }, 'Received attributes'); + + // 只检查是否已处理,不要立即标记 + if (messageUid && PROCESSED_MESSAGES.has(messageUid)) { + log.debug({ uid: messageUid, seqno }, 'Message UID already processed, skipping entire message'); + skipProcessing = true; + return; // 直接返回,不继续处理 + } + log.debug({ uid: messageUid, seqno }, 'Message UID ready for processing'); + + // 如果body已经处理完了,现在可以解析邮件 + if (bodyProcessed && !skipProcessing) { + processEmailBuffer(buffer, messageUid, seqno); + } + }); + + msg.on('body', function(stream, info) { + stream.on('data', function(chunk) { + buffer += chunk.toString('utf8'); + }); + + stream.once('end', function() { + bodyProcessed = true; + log.debug({ uid: messageUid, seqno, bufferLength: buffer.length, attributesReceived }, 'Body stream ended'); + + // 如果attributes已经收到且没有标记跳过,现在可以解析邮件 + if (attributesReceived && !skipProcessing) { + processEmailBuffer(buffer, messageUid, seqno); + } + }); + }); + + // 分离出的邮件处理函数 + function processEmailBuffer(buffer, uid, seqno) { + if (buffer.length > 0 && uid) { + log.debug({ uid, seqno }, 'Starting email parsing'); + simpleParser(buffer, function(err, parsed) { + if (err) { + log.error({ error: err.message, seqno, uid }, 'Failed to parse email'); + PROCESSED_MESSAGES.delete(uid); + } else { + log.debug({ uid, seqno }, 'Email parsed successfully, calling handleMailMessage'); + parsed.uid = uid; + handleMailMessage(parsed); + } + }); + } else { + log.debug({ uid, seqno, bufferLength: buffer.length }, 'Skipping email - no buffer or uid'); } } - process.exit(0); + msg.once('error', function(err) { + log.error({ error: err.message, seqno, uid: messageUid }, 'Error fetching message'); + }); }); - // 启动 IMAP 监听 - while (true) { - try { - await startImap(); - } catch (error) { - log.error({ error }, 'IMAP connection lost, retrying in 30s...'); - await new Promise(resolve => setTimeout(resolve, 30000)); - } - } + fetch.once('error', function(err) { + log.error({ error: err.message }, 'Error fetching emails'); + }); + + fetch.once('end', function() { + log.debug('Email fetch completed'); + }); } -// 仅在作为主模块运行时启动 +// 启动服务 if (require.main === module) { - main().catch(error => { - log.error({ error }, 'Fatal error'); - process.exit(1); - }); + startImap(); } module.exports = { + startImap, handleMailMessage, - getOrCreatePty, extractTokenFromSubject, - stripReply + cleanEmailText }; \ No newline at end of file diff --git a/src/relay/smart-injector.js b/src/relay/smart-injector.js new file mode 100644 index 0000000..fd67777 --- /dev/null +++ b/src/relay/smart-injector.js @@ -0,0 +1,251 @@ +#!/usr/bin/env node + +/** + * 智能命令注入器 - 多种方式确保命令能够到达Claude Code + */ + +const { exec, spawn } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +class SmartInjector { + constructor(logger) { + this.log = logger || console; + this.tempDir = path.join(__dirname, '../temp'); + this.ensureTempDir(); + } + + ensureTempDir() { + if (!fs.existsSync(this.tempDir)) { + fs.mkdirSync(this.tempDir, { recursive: true }); + } + } + + async injectCommand(token, command) { + this.log.info(`🎯 智能注入命令: ${command.slice(0, 50)}...`); + + const methods = [ + this.tryAppleScriptInjection.bind(this), + this.tryFileDropInjection.bind(this), + this.tryClipboardWithPersistentNotification.bind(this), + this.tryUrgentClipboard.bind(this) + ]; + + for (let i = 0; i < methods.length; i++) { + const methodName = ['AppleScript自动注入', '文件拖拽注入', '持久通知注入', '紧急剪贴板'][i]; + + try { + this.log.info(`🔄 尝试方法 ${i + 1}: ${methodName}`); + const result = await methods[i](token, command); + + if (result.success) { + this.log.info(`✅ ${methodName}成功: ${result.message}`); + return true; + } else { + this.log.warn(`⚠️ ${methodName}失败: ${result.error}`); + } + } catch (error) { + this.log.error(`❌ ${methodName}异常: ${error.message}`); + } + } + + this.log.error('🚨 所有注入方法都失败了'); + return false; + } + + // 方法1: AppleScript自动注入 + async tryAppleScriptInjection(token, command) { + return new Promise((resolve) => { + // 先复制到剪贴板 + this.copyToClipboard(command).then(() => { + const script = ` + tell application "System Events" + set targetApps to {"Claude", "Claude Code", "Terminal", "iTerm2", "iTerm"} + set targetApp to null + + repeat with appName in targetApps + try + if application process appName exists then + set targetApp to application process appName + exit repeat + end if + end try + end repeat + + if targetApp is not null then + set frontmost of targetApp to true + delay 0.5 + keystroke "v" using command down + delay 0.3 + keystroke return + return "success" + else + return "no_target" + end if + end tell + `; + + exec(`osascript -e '${script}'`, (error, stdout) => { + if (error) { + if (error.message.includes('1002') || error.message.includes('不允许')) { + resolve({ success: false, error: 'permission_denied' }); + } else { + resolve({ success: false, error: error.message }); + } + } else { + const result = stdout.trim(); + if (result === 'success') { + resolve({ success: true, message: '自动粘贴成功' }); + } else { + resolve({ success: false, error: result }); + } + } + }); + }); + }); + } + + // 方法2: 文件拖拽注入 + async tryFileDropInjection(token, command) { + return new Promise((resolve) => { + try { + // 创建临时命令文件 + const fileName = `taskping-command-${token}.txt`; + const filePath = path.join(this.tempDir, fileName); + + fs.writeFileSync(filePath, command); + + // 复制文件路径到剪贴板 + this.copyToClipboard(filePath).then(() => { + // 发送通知指导用户 + const notificationScript = ` + display notification "💡 命令文件已创建并复制路径到剪贴板!\\n1. 在Finder中按Cmd+G并粘贴路径\\n2. 将文件拖拽到Claude Code窗口" with title "TaskPing 文件注入" subtitle "拖拽文件: ${fileName}" sound name "Glass" + `; + + exec(`osascript -e '${notificationScript}'`, () => { + // 尝试自动打开Finder到目标目录 + exec(`open "${this.tempDir}"`, () => { + resolve({ success: true, message: '文件已创建,通知已发送' }); + }); + }); + }); + + } catch (error) { + resolve({ success: false, error: error.message }); + } + }); + } + + // 方法3: 持久通知注入 + async tryClipboardWithPersistentNotification(token, command) { + return new Promise((resolve) => { + this.copyToClipboard(command).then(() => { + // 发送多次通知确保用户看到 + const notifications = [ + { delay: 0, sound: 'Basso', message: '🚨 邮件命令已复制!请立即粘贴到Claude Code (Cmd+V)' }, + { delay: 3000, sound: 'Ping', message: '⏰ 提醒:命令仍在剪贴板中,请粘贴执行' }, + { delay: 8000, sound: 'Purr', message: '💡 最后提醒:在Claude Code中按Cmd+V粘贴命令' } + ]; + + let completedNotifications = 0; + + notifications.forEach((notif, index) => { + setTimeout(() => { + const script = ` + display notification "${notif.message}" with title "TaskPing 持久提醒 ${index + 1}/3" subtitle "${command.slice(0, 30)}..." sound name "${notif.sound}" + `; + + exec(`osascript -e '${script}'`, () => { + completedNotifications++; + if (completedNotifications === notifications.length) { + resolve({ success: true, message: '持久通知序列完成' }); + } + }); + }, notif.delay); + }); + + }).catch((error) => { + resolve({ success: false, error: error.message }); + }); + }); + } + + // 方法4: 紧急剪贴板(最后手段) + async tryUrgentClipboard(token, command) { + return new Promise((resolve) => { + this.copyToClipboard(command).then(() => { + // 创建桌面快捷文件 + const desktopPath = path.join(require('os').homedir(), 'Desktop'); + const shortcutContent = `#!/bin/bash +echo "TaskPing命令: ${command}" +echo "已复制到剪贴板,请在Claude Code中按Cmd+V粘贴" +echo "${command}" | pbcopy +echo "✅ 命令已刷新到剪贴板" +`; + + const shortcutPath = path.join(desktopPath, `TaskPing-${token}.command`); + + try { + fs.writeFileSync(shortcutPath, shortcutContent); + fs.chmodSync(shortcutPath, '755'); // 可执行权限 + + const script = ` + display notification "🆘 紧急模式:桌面已创建快捷文件 TaskPing-${token}.command\\n双击可重新复制命令到剪贴板" with title "TaskPing 紧急模式" subtitle "命令: ${command.slice(0, 20)}..." sound name "Sosumi" + `; + + exec(`osascript -e '${script}'`, () => { + resolve({ success: true, message: '紧急模式:桌面快捷文件已创建' }); + }); + + } catch (error) { + resolve({ success: false, error: error.message }); + } + + }).catch((error) => { + resolve({ success: false, error: error.message }); + }); + }); + } + + // 辅助方法:复制到剪贴板 + async copyToClipboard(text) { + return new Promise((resolve, reject) => { + const pbcopy = spawn('pbcopy'); + pbcopy.stdin.write(text); + pbcopy.stdin.end(); + + pbcopy.on('close', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`pbcopy failed with code ${code}`)); + } + }); + }); + } + + // 清理临时文件 + cleanup() { + try { + if (fs.existsSync(this.tempDir)) { + const files = fs.readdirSync(this.tempDir); + const now = Date.now(); + + files.forEach(file => { + const filePath = path.join(this.tempDir, file); + const stats = fs.statSync(filePath); + const age = now - stats.mtime.getTime(); + + // 删除超过1小时的临时文件 + if (age > 60 * 60 * 1000) { + fs.unlinkSync(filePath); + } + }); + } + } catch (error) { + this.log.warn(`清理临时文件失败: ${error.message}`); + } + } +} + +module.exports = SmartInjector; \ No newline at end of file diff --git a/src/relay/tmux-injector.js b/src/relay/tmux-injector.js new file mode 100644 index 0000000..3d7ced0 --- /dev/null +++ b/src/relay/tmux-injector.js @@ -0,0 +1,462 @@ +#!/usr/bin/env node + +/** + * tmux命令注入器 - 无人值守远程控制解决方案 + */ + +const { exec } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +class TmuxInjector { + constructor(logger, sessionName = null) { + this.log = logger || console; + this.sessionName = sessionName || 'claude-taskping'; + this.logFile = path.join(__dirname, '../logs/tmux-injection.log'); + this.ensureLogDir(); + } + + ensureLogDir() { + const logDir = path.dirname(this.logFile); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + } + + // 检查tmux是否安装 + async checkTmuxAvailable() { + return new Promise((resolve) => { + exec('which tmux', (error) => { + resolve(!error); + }); + }); + } + + // 检查Claude tmux会话是否存在 + async checkClaudeSession() { + return new Promise((resolve) => { + exec(`tmux has-session -t ${this.sessionName} 2>/dev/null`, (error) => { + resolve(!error); + }); + }); + } + + // 创建Claude tmux会话 + async createClaudeSession() { + return new Promise((resolve) => { + // 使用clauderun命令启动Claude(不预填充任何命令) + const command = `tmux new-session -d -s ${this.sessionName} -c "${process.cwd()}" clauderun`; + + this.log.info(`Creating tmux session with clauderun command: ${command}`); + + exec(command, (error, stdout, stderr) => { + if (error) { + this.log.warn(`Failed to create tmux session with clauderun: ${error.message}`); + // 如果clauderun失败,尝试使用完整路径命令 + this.log.info('Fallback to full path command...'); + const fallbackCommand = `tmux new-session -d -s ${this.sessionName} -c "${process.cwd()}" /Users/jessytsui/.nvm/versions/node/v18.17.0/bin/claude --dangerously-skip-permissions`; + + exec(fallbackCommand, (fallbackError) => { + if (fallbackError) { + this.log.error(`Failed to create tmux session with fallback: ${fallbackError.message}`); + resolve({ success: false, error: fallbackError.message }); + } else { + this.log.info('Tmux Claude session created successfully (full path)'); + setTimeout(() => { + resolve({ success: true }); + }, 3000); + } + }); + } else { + this.log.info('Tmux Claude session created successfully (clauderun)'); + // 等待Claude初始化 + setTimeout(() => { + resolve({ success: true }); + }, 3000); + } + }); + }); + } + + // 向tmux会话注入命令(智能处理Claude确认) + async injectCommand(command) { + return new Promise(async (resolve) => { + try { + // 1. 清空输入框 + const clearCommand = `tmux send-keys -t ${this.sessionName} C-u`; + + // 2. 发送命令 + const escapedCommand = command.replace(/'/g, "'\"'\"'"); + const sendCommand = `tmux send-keys -t ${this.sessionName} '${escapedCommand}'`; + + // 3. 发送回车 + const enterCommand = `tmux send-keys -t ${this.sessionName} C-m`; + + this.log.info(`Injecting command via tmux: ${command}`); + this.log.info(`Step 1 - Clear: ${clearCommand}`); + this.log.info(`Step 2 - Send: ${sendCommand}`); + this.log.info(`Step 3 - Enter: ${enterCommand}`); + + // 执行三个步骤 + exec(clearCommand, (clearError) => { + if (clearError) { + this.log.error(`Failed to clear input: ${clearError.message}`); + resolve({ success: false, error: clearError.message }); + return; + } + + // 短暂等待 + setTimeout(() => { + exec(sendCommand, (sendError) => { + if (sendError) { + this.log.error(`Failed to send command: ${sendError.message}`); + resolve({ success: false, error: sendError.message }); + return; + } + + // 短暂等待 + setTimeout(() => { + exec(enterCommand, async (enterError) => { + if (enterError) { + this.log.error(`Failed to send enter: ${enterError.message}`); + resolve({ success: false, error: enterError.message }); + return; + } + + this.log.info('Command sent successfully in 3 steps'); + + // 短暂等待命令发送 + await new Promise(r => setTimeout(r, 1000)); + + // 检查命令是否已在Claude中显示 + const capture = await this.getCaptureOutput(); + if (capture.success) { + this.log.info(`Claude state after injection: ${capture.output.slice(-200).replace(/\n/g, ' ')}`); + } + + // 等待并检查是否需要确认 + await this.handleConfirmations(); + + // 记录注入日志 + this.logInjection(command); + + resolve({ success: true }); + }); + }, 200); + }); + }, 200); + }); + + } catch (error) { + resolve({ success: false, error: error.message }); + } + }); + } + + // 自动处理Claude的确认对话框 + async handleConfirmations() { + const maxAttempts = 8; + let attempts = 0; + + while (attempts < maxAttempts) { + attempts++; + + // 等待Claude处理 + await new Promise(resolve => setTimeout(resolve, 1500)); + + // 获取当前屏幕内容 + const capture = await this.getCaptureOutput(); + + if (!capture.success) { + break; + } + + const output = capture.output; + this.log.info(`Confirmation check ${attempts}: ${output.slice(-200).replace(/\n/g, ' ')}`); + + // 检查是否有多选项确认对话框(优先处理) + if (output.includes('Do you want to proceed?') && + (output.includes('1. Yes') || output.includes('2. Yes, and don\'t ask again'))) { + + this.log.info(`Detected multi-option confirmation, selecting option 2 (attempt ${attempts})`); + + // 选择"2. Yes, and don't ask again"以避免未来的确认对话框 + await new Promise((resolve) => { + exec(`tmux send-keys -t ${this.sessionName} '2'`, (error) => { + if (error) { + this.log.warn('Failed to send option 2'); + } else { + this.log.info('Auto-confirmation sent (option 2)'); + // 发送Enter键 + setTimeout(() => { + exec(`tmux send-keys -t ${this.sessionName} 'Enter'`, (enterError) => { + if (enterError) { + this.log.warn('Failed to send Enter after option 2'); + } else { + this.log.info('Enter sent after option 2 - no future dialogs'); + } + resolve(); + }); + }, 300); + } + }); + }); + + // 等待确认生效 + await new Promise(resolve => setTimeout(resolve, 2000)); + continue; + } + + // 检查是否有单选项确认 + if (output.includes('❯ 1. Yes') || output.includes('▷ 1. Yes')) { + this.log.info(`Detected single option confirmation, selecting option 1 (attempt ${attempts})`); + + await new Promise((resolve) => { + exec(`tmux send-keys -t ${this.sessionName} '1'`, (error) => { + if (error) { + this.log.warn('Failed to send option 1'); + } else { + this.log.info('Auto-confirmation sent (option 1)'); + // 发送Enter键 + setTimeout(() => { + exec(`tmux send-keys -t ${this.sessionName} 'Enter'`, (enterError) => { + if (enterError) { + this.log.warn('Failed to send Enter after option 1'); + } else { + this.log.info('Enter sent after option 1'); + } + resolve(); + }); + }, 300); + } + }); + }); + + continue; + } + + // 检查是否有简单的Y/N确认 + if (output.includes('(y/n)') || output.includes('[Y/n]') || output.includes('[y/N]')) { + this.log.info(`Detected y/n prompt, sending 'y' (attempt ${attempts})`); + + await new Promise((resolve) => { + exec(`tmux send-keys -t ${this.sessionName} 'y'`, (error) => { + if (error) { + this.log.warn('Failed to send y'); + } else { + this.log.info('Auto-confirmation sent (y)'); + // 发送Enter键 + setTimeout(() => { + exec(`tmux send-keys -t ${this.sessionName} 'Enter'`, (enterError) => { + if (enterError) { + this.log.warn('Failed to send Enter after y'); + } else { + this.log.info('Enter sent after y'); + } + resolve(); + }); + }, 300); + } + }); + }); + + continue; + } + + // 检查是否有按Enter继续的提示 + if (output.includes('Press Enter to continue') || + output.includes('Enter to confirm') || + output.includes('Press Enter')) { + this.log.info(`Detected Enter prompt, sending Enter (attempt ${attempts})`); + + await new Promise((resolve) => { + exec(`tmux send-keys -t ${this.sessionName} 'Enter'`, (error) => { + if (error) { + this.log.warn('Failed to send Enter'); + } else { + this.log.info('Auto-Enter sent'); + } + resolve(); + }); + }); + + continue; + } + + // 检查是否命令正在执行 + if (output.includes('Clauding…') || + output.includes('Waiting…') || + output.includes('Processing…') || + output.includes('Working…')) { + this.log.info('Command appears to be executing, waiting...'); + continue; + } + + // 检查是否有新的空输入框(表示完成) + if ((output.includes('│ >') || output.includes('> ')) && + !output.includes('Do you want to proceed?') && + !output.includes('1. Yes') && + !output.includes('(y/n)')) { + this.log.info('New input prompt detected, command likely completed'); + break; + } + + // 检查是否有错误信息 + if (output.includes('Error:') || output.includes('error:') || output.includes('failed')) { + this.log.warn('Detected error in output, stopping confirmation attempts'); + break; + } + + // 如果什么都没检测到,等待更长时间再检查 + if (attempts < maxAttempts) { + this.log.info('No confirmation prompts detected, waiting longer...'); + await new Promise(resolve => setTimeout(resolve, 2000)); + } + } + + this.log.info(`Confirmation handling completed after ${attempts} attempts`); + + // 最终状态检查 + const finalCapture = await this.getCaptureOutput(); + if (finalCapture.success) { + this.log.info(`Final state: ${finalCapture.output.slice(-100).replace(/\n/g, ' ')}`); + } + } + + // 获取tmux会话输出 + async getCaptureOutput() { + return new Promise((resolve) => { + const command = `tmux capture-pane -t ${this.sessionName} -p`; + + exec(command, (error, stdout, stderr) => { + if (error) { + resolve({ success: false, error: error.message }); + } else { + resolve({ success: true, output: stdout }); + } + }); + }); + } + + // 重启Claude会话 + async restartClaudeSession() { + return new Promise(async (resolve) => { + this.log.info('Restarting Claude tmux session...'); + + // 杀死现有会话 + exec(`tmux kill-session -t ${this.sessionName} 2>/dev/null`, async () => { + // 等待一下 + await new Promise(r => setTimeout(r, 1000)); + + // 创建新会话 + const result = await this.createClaudeSession(); + resolve(result); + }); + }); + } + + // 完整的命令注入流程 + async injectCommandFull(token, command) { + try { + this.log.info(`🎯 开始tmux命令注入 (Token: ${token})`); + + // 1. 检查tmux是否可用 + const tmuxAvailable = await this.checkTmuxAvailable(); + if (!tmuxAvailable) { + return { success: false, error: 'tmux_not_installed', message: '需要安装tmux: brew install tmux' }; + } + + // 2. 检查Claude会话是否存在 + const sessionExists = await this.checkClaudeSession(); + + if (!sessionExists) { + this.log.warn('Claude tmux session not found, creating new session...'); + const createResult = await this.createClaudeSession(); + + if (!createResult.success) { + return { success: false, error: 'session_creation_failed', message: createResult.error }; + } + } + + // 3. 注入命令 + const injectResult = await this.injectCommand(command); + + if (injectResult.success) { + // 4. 发送成功通知 + await this.sendSuccessNotification(command); + + return { + success: true, + message: '命令已成功注入到Claude tmux会话', + session: this.sessionName + }; + } else { + return { + success: false, + error: 'injection_failed', + message: injectResult.error + }; + } + + } catch (error) { + this.log.error(`Tmux injection error: ${error.message}`); + return { success: false, error: 'unexpected_error', message: error.message }; + } + } + + // 发送成功通知 + async sendSuccessNotification(command) { + const shortCommand = command.length > 30 ? command.substring(0, 30) + '...' : command; + const notificationScript = ` + display notification "🎉 命令已自动注入到Claude!无需手动操作" with title "TaskPing 远程控制成功" subtitle "${shortCommand.replace(/"/g, '\\"')}" sound name "Glass" + `; + + exec(`osascript -e '${notificationScript}'`, (error) => { + if (error) { + this.log.warn('Failed to send success notification'); + } else { + this.log.info('Success notification sent'); + } + }); + } + + // 记录注入日志 + logInjection(command) { + const logEntry = { + timestamp: new Date().toISOString(), + command: command, + session: this.sessionName, + pid: process.pid + }; + + const logLine = JSON.stringify(logEntry) + '\n'; + + try { + fs.appendFileSync(this.logFile, logLine); + } catch (error) { + this.log.warn(`Failed to write injection log: ${error.message}`); + } + } + + // 获取会话状态信息 + async getSessionInfo() { + return new Promise((resolve) => { + const command = `tmux list-sessions | grep ${this.sessionName}`; + + exec(command, (error, stdout, stderr) => { + if (error) { + resolve({ exists: false }); + } else { + const sessionInfo = stdout.trim(); + resolve({ + exists: true, + info: sessionInfo, + name: this.sessionName + }); + } + }); + }); + } +} + +module.exports = TmuxInjector; \ No newline at end of file diff --git a/src/simplified/email-automation.js b/src/simplified/email-automation.js deleted file mode 100644 index 37e2f8d..0000000 --- a/src/simplified/email-automation.js +++ /dev/null @@ -1,406 +0,0 @@ -/** - * 简化版邮件自动化 - * 专注于解决核心问题,减少复杂性 - */ - -const Imap = require('node-imap'); -const { simpleParser } = require('mailparser'); -const { spawn } = require('child_process'); -const EventEmitter = require('events'); -const fs = require('fs'); -const path = require('path'); - -class SimplifiedEmailAutomation extends EventEmitter { - constructor(config) { - super(); - this.config = config; - this.imap = null; - this.isRunning = false; - this.sessionsDir = path.join(__dirname, '../data/sessions'); - this.commandFile = path.join(__dirname, '../data/latest_command.txt'); - - // 增加超时时间解决连接问题 - this.imapConfig = { - user: config.imap.auth.user, - password: config.imap.auth.pass, - host: config.imap.host, - port: config.imap.port, - tls: config.imap.secure, - connTimeout: 60000, // 增加到60秒 - authTimeout: 30000, // 增加到30秒 - keepalive: true, - debug: process.env.DEBUG_IMAP === 'true' - }; - - this._ensureDirectories(); - } - - _ensureDirectories() { - [this.sessionsDir, path.dirname(this.commandFile)].forEach(dir => { - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - }); - } - - async start() { - console.log('🚀 启动简化邮件自动化服务...'); - - if (this.isRunning) { - console.log('⚠️ 服务已在运行中'); - return; - } - - try { - await this._connectWithRetry(); - this._startListening(); - this.isRunning = true; - console.log('✅ 邮件监听服务启动成功'); - console.log(`📧 监听邮箱: ${this.config.imap.auth.user}`); - console.log('💡 现在可以回复 TaskPing 邮件来发送命令了'); - } catch (error) { - console.error('❌ 启动失败:', error.message); - throw error; - } - } - - async _connectWithRetry(maxRetries = 3) { - for (let i = 0; i < maxRetries; i++) { - try { - console.log(`🔄 尝试连接 IMAP (${i + 1}/${maxRetries})...`); - await this._connect(); - console.log('✅ IMAP 连接成功'); - return; - } catch (error) { - console.log(`❌ 连接失败 (${i + 1}/${maxRetries}): ${error.message}`); - if (i === maxRetries - 1) throw error; - await new Promise(resolve => setTimeout(resolve, 5000)); - } - } - } - - async _connect() { - return new Promise((resolve, reject) => { - this.imap = new Imap(this.imapConfig); - - this.imap.once('ready', () => { - console.log('📬 IMAP 连接就绪'); - resolve(); - }); - - this.imap.once('error', (error) => { - console.error('📬 IMAP 连接错误:', error.message); - reject(error); - }); - - this.imap.once('end', () => { - console.log('📬 IMAP 连接结束'); - if (this.isRunning) { - console.log('🔄 尝试重新连接...'); - setTimeout(() => this._connectWithRetry().catch(console.error), 10000); - } - }); - - this.imap.connect(); - }); - } - - _startListening() { - console.log('👂 开始监听新邮件...'); - - // 立即检查一次 - this._checkNewEmails(); - - // 定期检查(每30秒) - setInterval(() => { - if (this.isRunning) { - this._checkNewEmails(); - } - }, 30000); - } - - async _checkNewEmails() { - try { - await this._openInbox(); - - // 只查找最近1小时内的未读邮件 - const yesterday = new Date(); - yesterday.setHours(yesterday.getHours() - 1); - - const searchCriteria = [ - 'UNSEEN', - ['SINCE', yesterday] - ]; - - this.imap.search(searchCriteria, (err, results) => { - if (err) { - console.error('🔍 搜索邮件失败:', err.message); - return; - } - - if (results.length === 0) { - return; // 没有新邮件 - } - - console.log(`📧 发现 ${results.length} 封新邮件`); - this._processEmails(results); - }); - } catch (error) { - console.error('📧 检查邮件失败:', error.message); - } - } - - _openInbox() { - return new Promise((resolve, reject) => { - this.imap.openBox('INBOX', false, (err, box) => { - if (err) reject(err); - else resolve(box); - }); - }); - } - - _processEmails(emailUids) { - const fetch = this.imap.fetch(emailUids, { - bodies: '', - markSeen: true - }); - - fetch.on('message', (msg, seqno) => { - let buffer = ''; - - msg.on('body', (stream) => { - stream.on('data', (chunk) => { - buffer += chunk.toString('utf8'); - }); - - stream.once('end', async () => { - try { - const parsed = await simpleParser(buffer); - await this._handleEmail(parsed, seqno); - } catch (error) { - console.error(`❌ 处理邮件 ${seqno} 失败:`, error.message); - } - }); - }); - }); - - fetch.once('error', (err) => { - console.error('📧 获取邮件失败:', err.message); - }); - } - - async _handleEmail(email, seqno) { - console.log(`📨 处理邮件 ${seqno}: ${email.subject}`); - - // 简化的 TaskPing 邮件检查 - if (!this._isTaskPingReply(email)) { - console.log(`📨 邮件 ${seqno} 不是 TaskPing 回复,跳过`); - return; - } - - // 提取命令内容 - const command = this._extractCommand(email); - if (!command || command.trim().length === 0) { - console.log(`📨 邮件 ${seqno} 没有找到有效命令`); - return; - } - - console.log(`🎯 提取到命令: ${command.substring(0, 100)}${command.length > 100 ? '...' : ''}`); - - // 执行命令 - await this._executeCommand(command, seqno); - } - - _isTaskPingReply(email) { - const subject = email.subject || ''; - - // 检查是否是 TaskPing 相关邮件 - return subject.includes('[TaskPing]') || - subject.match(/^(Re:|RE:|回复:)/i); - } - - _extractCommand(email) { - let text = email.text || ''; - - // 清理邮件内容 - 移除引用和签名 - const lines = text.split('\n'); - const cleanLines = []; - - for (const line of lines) { - // 停止条件:遇到原始邮件标记 - if (line.includes('-----Original Message-----') || - line.includes('--- Original Message ---') || - line.includes('在') && line.includes('写道:') || - line.includes('On') && line.includes('wrote:') || - line.match(/^>\s*/) || - line.includes('会话ID:') || - line.includes('Session ID:')) { - break; - } - - // 跳过签名 - if (line.includes('--') || - line.includes('Sent from') || - line.includes('发自我的')) { - break; - } - - cleanLines.push(line); - } - - return cleanLines.join('\n').trim(); - } - - async _executeCommand(command, emailSeq) { - try { - console.log(`🚀 执行命令 (来自邮件 ${emailSeq})`); - - // 保存最新命令到文件 - await this._saveCommand(command, emailSeq); - - // 复制到剪贴板 - const clipboardSuccess = await this._copyToClipboard(command); - - // 发送通知 - const notificationSuccess = await this._sendNotification(command); - - // 尝试简单的自动粘贴 - const pasteSuccess = await this._attemptSimplePaste(); - - console.log('📊 执行结果:'); - console.log(` 📄 文件保存: ${await this._saveCommand(command, emailSeq) ? '✅' : '❌'}`); - console.log(` 📋 剪贴板: ${clipboardSuccess ? '✅' : '❌'}`); - console.log(` 🔔 通知: ${notificationSuccess ? '✅' : '❌'}`); - console.log(` 🤖 自动粘贴: ${pasteSuccess ? '✅' : '❌'}`); - - this.emit('commandExecuted', { - command, - emailSeq, - success: clipboardSuccess || notificationSuccess - }); - - } catch (error) { - console.error('❌ 命令执行失败:', error.message); - this.emit('commandFailed', { command, emailSeq, error }); - } - } - - async _saveCommand(command, emailSeq) { - try { - const content = `# TaskPing 邮件命令 (邮件 ${emailSeq}) -# 时间: ${new Date().toLocaleString('zh-CN')} -# -# 命令内容: -${command} - -# ============================== -# 说明: 这个命令来自邮件回复 -# 使用: 复制上面的命令到 Claude Code 中执行 -`; - fs.writeFileSync(this.commandFile, content, 'utf8'); - return true; - } catch (error) { - console.error('保存命令文件失败:', error.message); - return false; - } - } - - async _copyToClipboard(command) { - try { - const pbcopy = spawn('pbcopy'); - pbcopy.stdin.write(command); - pbcopy.stdin.end(); - - return new Promise((resolve) => { - pbcopy.on('close', (code) => resolve(code === 0)); - pbcopy.on('error', () => resolve(false)); - }); - } catch (error) { - return false; - } - } - - async _sendNotification(command) { - try { - const shortCommand = command.length > 60 ? command.substring(0, 60) + '...' : command; - - const script = ` - display notification "新邮件命令已复制到剪贴板!请到 Claude Code 中粘贴执行" with title "TaskPing" subtitle "命令: ${shortCommand.replace(/"/g, '\\"')}" sound name "default" - `; - - const osascript = spawn('osascript', ['-e', script]); - - return new Promise((resolve) => { - osascript.on('close', (code) => resolve(code === 0)); - osascript.on('error', () => resolve(false)); - }); - } catch (error) { - return false; - } - } - - async _attemptSimplePaste() { - try { - // 只在明确是开发环境的应用时尝试自动粘贴 - const script = ` - tell application "System Events" - try - set frontApp to name of first application process whose frontmost is true - - -- 只在特定应用中尝试自动粘贴 - if frontApp contains "Claude" or frontApp contains "Terminal" or frontApp contains "iTerm" then - delay 0.5 - keystroke "v" using command down - delay 0.2 - keystroke return - return "success" - else - return "skip" - end if - on error - return "failed" - end try - end tell - `; - - const osascript = spawn('osascript', ['-e', script]); - let output = ''; - - osascript.stdout.on('data', (data) => { - output += data.toString().trim(); - }); - - return new Promise((resolve) => { - osascript.on('close', (code) => { - resolve(code === 0 && output === 'success'); - }); - osascript.on('error', () => resolve(false)); - }); - } catch (error) { - return false; - } - } - - async stop() { - console.log('🛑 停止邮件监听服务...'); - this.isRunning = false; - - if (this.imap) { - this.imap.end(); - this.imap = null; - } - - console.log('✅ 服务已停止'); - } - - getStatus() { - return { - running: this.isRunning, - connected: this.imap && this.imap.state === 'authenticated', - commandFile: this.commandFile, - lastCommandExists: fs.existsSync(this.commandFile) - }; - } -} - -module.exports = SimplifiedEmailAutomation; \ No newline at end of file diff --git a/src/utils/conversation-tracker.js b/src/utils/conversation-tracker.js new file mode 100644 index 0000000..a05a3af --- /dev/null +++ b/src/utils/conversation-tracker.js @@ -0,0 +1,130 @@ +/** + * 对话追踪器 - 用于捕获用户问题和Claude回复 + */ + +const fs = require('fs'); +const path = require('path'); + +class ConversationTracker { + constructor() { + this.conversationPath = path.join(__dirname, '../data/conversations.json'); + this.ensureDataDir(); + } + + ensureDataDir() { + const dir = path.dirname(this.conversationPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + } + + // 记录用户问题 + recordUserMessage(sessionId, message) { + const conversations = this.loadConversations(); + if (!conversations[sessionId]) { + conversations[sessionId] = { + created: new Date().toISOString(), + messages: [] + }; + } + + conversations[sessionId].messages.push({ + type: 'user', + content: message, + timestamp: new Date().toISOString() + }); + + this.saveConversations(conversations); + } + + // 记录Claude回复 + recordClaudeResponse(sessionId, response) { + const conversations = this.loadConversations(); + if (!conversations[sessionId]) { + conversations[sessionId] = { + created: new Date().toISOString(), + messages: [] + }; + } + + conversations[sessionId].messages.push({ + type: 'claude', + content: response, + timestamp: new Date().toISOString() + }); + + this.saveConversations(conversations); + } + + // 获取最近的对话内容 + getRecentConversation(sessionId, limit = 2) { + const conversations = this.loadConversations(); + const session = conversations[sessionId]; + + if (!session || !session.messages.length) { + return { userQuestion: '', claudeResponse: '' }; + } + + const messages = session.messages.slice(-limit * 2); // 获取最近的用户-Claude对话 + let userQuestion = ''; + let claudeResponse = ''; + + // 从后往前找最近的用户问题和Claude回复 + for (let i = messages.length - 1; i >= 0; i--) { + const msg = messages[i]; + if (msg.type === 'claude' && !claudeResponse) { + claudeResponse = msg.content; + } else if (msg.type === 'user' && !userQuestion) { + userQuestion = msg.content; + } + + if (userQuestion && claudeResponse) break; + } + + return { + userQuestion: userQuestion || '未记录的用户问题', + claudeResponse: claudeResponse || '未记录的Claude回复' + }; + } + + // 清理过期对话(超过7天) + cleanupOldConversations() { + const conversations = this.loadConversations(); + const now = new Date(); + const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + + const cleaned = {}; + for (const [sessionId, session] of Object.entries(conversations)) { + const created = new Date(session.created); + if (created > sevenDaysAgo) { + cleaned[sessionId] = session; + } + } + + this.saveConversations(cleaned); + return Object.keys(conversations).length - Object.keys(cleaned).length; + } + + loadConversations() { + if (!fs.existsSync(this.conversationPath)) { + return {}; + } + + try { + return JSON.parse(fs.readFileSync(this.conversationPath, 'utf8')); + } catch (error) { + console.error('Failed to load conversations:', error); + return {}; + } + } + + saveConversations(conversations) { + try { + fs.writeFileSync(this.conversationPath, JSON.stringify(conversations, null, 2)); + } catch (error) { + console.error('Failed to save conversations:', error); + } + } +} + +module.exports = ConversationTracker; \ No newline at end of file diff --git a/src/utils/tmux-monitor.js b/src/utils/tmux-monitor.js new file mode 100644 index 0000000..db85e02 --- /dev/null +++ b/src/utils/tmux-monitor.js @@ -0,0 +1,207 @@ +/** + * Tmux Session Monitor + * Captures input/output from tmux sessions for email notifications + */ + +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +class TmuxMonitor { + constructor() { + this.captureDir = path.join(__dirname, '../data/tmux-captures'); + this._ensureCaptureDir(); + } + + _ensureCaptureDir() { + if (!fs.existsSync(this.captureDir)) { + fs.mkdirSync(this.captureDir, { recursive: true }); + } + } + + /** + * Start capturing a tmux session + * @param {string} sessionName - The tmux session name + */ + startCapture(sessionName) { + try { + const captureFile = path.join(this.captureDir, `${sessionName}.log`); + + // Start pipe-pane to capture all session output + execSync(`tmux pipe-pane -t ${sessionName} -o "cat >> ${captureFile}"`, { + encoding: 'utf8', + stdio: 'ignore' + }); + + return captureFile; + } catch (error) { + console.error(`Failed to start capture for session ${sessionName}:`, error.message); + return null; + } + } + + /** + * Stop capturing a tmux session + * @param {string} sessionName - The tmux session name + */ + stopCapture(sessionName) { + try { + execSync(`tmux pipe-pane -t ${sessionName}`, { + encoding: 'utf8', + stdio: 'ignore' + }); + } catch (error) { + console.error(`Failed to stop capture for session ${sessionName}:`, error.message); + } + } + + /** + * Get recent conversation from a tmux session + * @param {string} sessionName - The tmux session name + * @param {number} lines - Number of lines to retrieve + * @returns {Object} - { userQuestion, claudeResponse } + */ + getRecentConversation(sessionName, lines = 50) { + try { + const captureFile = path.join(this.captureDir, `${sessionName}.log`); + + if (!fs.existsSync(captureFile)) { + // If no capture file, try to get from tmux buffer + return this.getFromTmuxBuffer(sessionName, lines); + } + + // Read the capture file + const content = fs.readFileSync(captureFile, 'utf8'); + const allLines = content.split('\n'); + const recentLines = allLines.slice(-lines); + + return this.extractConversation(recentLines.join('\n')); + } catch (error) { + console.error(`Failed to get conversation for session ${sessionName}:`, error.message); + return { userQuestion: '', claudeResponse: '' }; + } + } + + /** + * Get conversation from tmux buffer + * @param {string} sessionName - The tmux session name + * @param {number} lines - Number of lines to retrieve + */ + getFromTmuxBuffer(sessionName, lines = 50) { + try { + // Capture the pane contents + const buffer = execSync(`tmux capture-pane -t ${sessionName} -p -S -${lines}`, { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'] + }); + + return this.extractConversation(buffer); + } catch (error) { + console.error(`Failed to get tmux buffer for session ${sessionName}:`, error.message); + return { userQuestion: '', claudeResponse: '' }; + } + } + + /** + * Extract user question and Claude response from captured text + * @param {string} text - The captured text + * @returns {Object} - { userQuestion, claudeResponse } + */ + extractConversation(text) { + const lines = text.split('\n'); + + let userQuestion = ''; + let claudeResponse = ''; + let responseLines = []; + let inResponse = false; + + // Find the most recent user question and Claude response + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + + // Detect user input (line starting with "> " followed by content) + if (line.startsWith('> ') && line.length > 2) { + userQuestion = line.substring(2).trim(); + inResponse = false; // Reset response capture + responseLines = []; // Clear previous response + continue; + } + + // Detect Claude response (line starting with "⏺ " or other response indicators) + if (line.startsWith('⏺ ') || + (inResponse && line.length > 0 && + !line.startsWith('╭') && !line.startsWith('│') && !line.startsWith('╰') && + !line.startsWith('> ') && !line.includes('? for shortcuts'))) { + + if (line.startsWith('⏺ ')) { + inResponse = true; + responseLines = [line.substring(2).trim()]; // Remove "⏺ " prefix + } else if (inResponse) { + responseLines.push(line); + } + } + + // Stop capturing response when we hit another prompt or box boundary + if (inResponse && (line.startsWith('╭') || line.startsWith('│ > ') || line.includes('? for shortcuts'))) { + inResponse = false; + } + } + + // Join response lines and clean up + claudeResponse = responseLines.join('\n').trim(); + + // Remove box characters and clean up formatting + claudeResponse = claudeResponse + .replace(/[╭╰│]/g, '') + .replace(/^\s*│\s*/gm, '') + .replace(/\s+/g, ' ') + .trim(); + + // Limit response length + if (claudeResponse.length > 500) { + claudeResponse = claudeResponse.substring(0, 497) + '...'; + } + + // If we didn't find a question in the standard format, look for any recent text input + if (!userQuestion) { + for (let i = lines.length - 1; i >= 0; i--) { + const line = lines[i].trim(); + if (line.startsWith('> ') && line.length > 2) { + userQuestion = line.substring(2).trim(); + break; + } + } + } + + return { + userQuestion: userQuestion || '无用户输入', + claudeResponse: claudeResponse || '无Claude回复' + }; + } + + /** + * Clean up old capture files + * @param {number} daysToKeep - Number of days to keep capture files + */ + cleanupOldCaptures(daysToKeep = 7) { + try { + const files = fs.readdirSync(this.captureDir); + const now = Date.now(); + const maxAge = daysToKeep * 24 * 60 * 60 * 1000; + + files.forEach(file => { + const filePath = path.join(this.captureDir, file); + const stats = fs.statSync(filePath); + + if (now - stats.mtime.getTime() > maxAge) { + fs.unlinkSync(filePath); + console.log(`Cleaned up old capture file: ${file}`); + } + }); + } catch (error) { + console.error('Failed to cleanup captures:', error.message); + } + } +} + +module.exports = TmuxMonitor; \ No newline at end of file diff --git a/start-relay-pty.js b/start-relay-pty.js index a8077fb..d1cd0fc 100755 --- a/start-relay-pty.js +++ b/start-relay-pty.js @@ -74,8 +74,45 @@ function createExampleSession() { } } +// PID文件路径 +const PID_FILE = path.join(__dirname, 'relay-pty.pid'); + +// 检查是否已有实例在运行 +function checkSingleInstance() { + if (fs.existsSync(PID_FILE)) { + try { + const oldPid = parseInt(fs.readFileSync(PID_FILE, 'utf8')); + // 检查进程是否真的在运行 + process.kill(oldPid, 0); + // 如果没有抛出错误,说明进程还在运行 + console.error('❌ 错误: relay-pty 服务已经在运行中 (PID: ' + oldPid + ')'); + console.log('\n如果您确定服务没有运行,可以删除 PID 文件:'); + console.log(' rm ' + PID_FILE); + console.log('\n或停止现有服务:'); + console.log(' kill ' + oldPid); + process.exit(1); + } catch (err) { + // 进程不存在,删除旧的 PID 文件 + fs.unlinkSync(PID_FILE); + } + } + + // 写入当前进程的 PID + fs.writeFileSync(PID_FILE, process.pid.toString()); +} + +// 清理 PID 文件 +function cleanupPidFile() { + if (fs.existsSync(PID_FILE)) { + fs.unlinkSync(PID_FILE); + } +} + // 启动服务 function startService() { + // 检查单实例 + checkSingleInstance(); + console.log('🚀 正在启动 TaskPing PTY Relay 服务...\n'); const relayPath = path.join(__dirname, 'src/relay/relay-pty.js'); @@ -93,15 +130,21 @@ function startService() { process.on('SIGINT', () => { console.log('\n⏹️ 正在停止服务...'); relay.kill('SIGINT'); + cleanupPidFile(); process.exit(0); }); + process.on('exit', cleanupPidFile); + process.on('SIGTERM', cleanupPidFile); + relay.on('error', (error) => { console.error('❌ 启动失败:', error.message); + cleanupPidFile(); process.exit(1); }); relay.on('exit', (code, signal) => { + cleanupPidFile(); if (signal) { console.log(`\n服务已停止 (信号: ${signal})`); } else if (code !== 0) { diff --git a/taskping.js b/taskping.js index ae2fb87..eeb8b74 100755 --- a/taskping.js +++ b/taskping.js @@ -108,7 +108,10 @@ class TaskPingCLI { process.exit(1); } - const result = await this.notifier.notify(type); + // 自动捕获当前tmux会话的对话内容 + const metadata = await this.captureCurrentConversation(); + + const result = await this.notifier.notify(type, metadata); if (result.success) { this.logger.info(`${type} notification sent successfully`); @@ -119,6 +122,42 @@ class TaskPingCLI { } } + async captureCurrentConversation() { + try { + const { execSync } = require('child_process'); + const TmuxMonitor = require('./src/utils/tmux-monitor'); + + // 获取当前tmux会话名称 + let currentSession = null; + try { + currentSession = execSync('tmux display-message -p "#S"', { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'] + }).trim(); + } catch (e) { + // 不在tmux中运行,返回空metadata + return {}; + } + + if (!currentSession) { + return {}; + } + + // 使用TmuxMonitor捕获对话 + const tmuxMonitor = new TmuxMonitor(); + const conversation = tmuxMonitor.getRecentConversation(currentSession); + + return { + userQuestion: conversation.userQuestion, + claudeResponse: conversation.claudeResponse, + tmuxSession: currentSession + }; + } catch (error) { + this.logger.debug('Failed to capture conversation:', error.message); + return {}; + } + } + async handleTest(args) { console.log('Testing notification channels...\n'); diff --git a/test-clipboard.js b/test-clipboard.js deleted file mode 100755 index 795fff2..0000000 --- a/test-clipboard.js +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env node - -/** - * 剪贴板自动化测试工具 - * 用于测试邮件回复命令的自动粘贴功能 - */ - -const ClipboardAutomation = require('./src/automation/clipboard-automation'); - -async function testClipboardAutomation() { - console.log('🧪 剪贴板自动化测试\n'); - - const automation = new ClipboardAutomation(); - - if (!automation.isSupported()) { - console.log('❌ 当前平台不支持剪贴板自动化'); - process.exit(1); - } - - console.log('✅ 剪贴板自动化支持检查通过'); - - // 测试命令 - const testCommand = 'echo "这是一个来自邮件回复的测试命令"'; - - console.log(`📝 测试命令: ${testCommand}`); - console.log('\n⚠️ 请确保:'); - console.log(' 1. Claude Code 或 Terminal 窗口已打开'); - console.log(' 2. 输入框处于活动状态'); - console.log(' 3. 准备好在 5 秒后接收自动输入'); - - // 等待用户准备 - console.log('\n⏳ 5 秒后开始测试...'); - for (let i = 5; i > 0; i--) { - process.stdout.write(` ${i}... `); - await new Promise(resolve => setTimeout(resolve, 1000)); - } - console.log('\n'); - - try { - console.log('🚀 发送测试命令...'); - const success = await automation.sendCommand(testCommand); - - if (success) { - console.log('✅ 测试成功!命令应该已经自动粘贴到 Claude Code 中'); - console.log('💡 如果没有看到命令,请检查:'); - console.log(' - Claude Code 窗口是否在前台'); - console.log(' - 输入框是否处于焦点状态'); - console.log(' - 系统是否允许自动化权限'); - } else { - console.log('❌ 测试失败:命令发送不成功'); - console.log('💡 请尝试:'); - console.log(' - 给予应用自动化权限(系统偏好设置 > 安全性与隐私 > 隐私 > 辅助功能)'); - console.log(' - 确保 Claude Code 或 Terminal 正在运行'); - } - } catch (error) { - console.error('❌ 测试过程中发生错误:', error.message); - } - - // 显示剪贴板内容验证 - try { - const clipboardContent = await automation.getClipboardContent(); - if (clipboardContent) { - console.log(`\n📋 当前剪贴板内容: "${clipboardContent.trim()}"`); - } - } catch (error) { - console.log('📋 无法读取剪贴板内容'); - } -} - -// 处理命令行参数 -const args = process.argv.slice(2); - -if (args.includes('--help') || args.includes('-h')) { - console.log(` -剪贴板自动化测试工具 - -用法: node test-clipboard.js [选项] - -选项: - -h, --help 显示帮助信息 - -这个工具用于测试 TaskPing 的邮件回复自动化功能。 -它会模拟邮件回复的过程,将测试命令自动粘贴到 Claude Code 中。 - -确保在运行测试前: -1. 打开 Claude Code 或 Terminal -2. 点击输入框使其获得焦点 -3. 给予应用必要的自动化权限 - `); - process.exit(0); -} - -// 运行测试 -testClipboardAutomation().catch(console.error); \ No newline at end of file diff --git a/test-email-reply.js b/test-email-reply.js deleted file mode 100755 index 69f1cce..0000000 --- a/test-email-reply.js +++ /dev/null @@ -1,231 +0,0 @@ -#!/usr/bin/env node - -/** - * TaskPing 邮件回复测试工具 - * 用于测试邮件命令提取和 PTY 注入功能 - */ - -const path = require('path'); -const fs = require('fs'); - -// 加载 relay-pty 模块 -const { - extractTokenFromSubject, - stripReply, - handleMailMessage -} = require('./src/relay/relay-pty'); - -// 测试用例 -const testCases = [ - { - name: '基本命令', - email: { - subject: 'Re: [TaskPing #TEST123] 任务等待您的指示', - from: { text: 'user@example.com', value: [{ address: 'user@example.com' }] }, - text: '继续执行\n\n> 原始邮件内容...' - }, - expectedToken: 'TEST123', - expectedCommand: '继续执行' - }, - { - name: 'CMD前缀', - email: { - subject: 'Re: TaskPing: ABC789', - from: { text: 'user@example.com', value: [{ address: 'user@example.com' }] }, - text: 'CMD: npm run build\n\n发自我的iPhone' - }, - expectedToken: 'ABC789', - expectedCommand: 'npm run build' - }, - { - name: '代码块', - email: { - subject: 'Re: [TaskPing #XYZ456]', - from: { text: 'user@example.com', value: [{ address: 'user@example.com' }] }, - text: '这是我的命令:\n\n```\ngit add .\ngit commit -m "Update"\n```\n\n谢谢!' - }, - expectedToken: 'XYZ456', - expectedCommand: 'git add .\ngit commit -m "Update"' - }, - { - name: '复杂邮件引用', - email: { - subject: 'Re: [TaskPing #TASK999] 请输入下一步操作', - from: { text: 'boss@company.com', value: [{ address: 'boss@company.com' }] }, - text: `yes, please continue - --- -Best regards, -Boss - -On 2024-01-01, TaskPing wrote: -> 任务已完成第一步 -> 会话ID: 12345-67890 -> 请回复您的下一步指示` - }, - expectedToken: 'TASK999', - expectedCommand: 'yes, please continue' - }, - { - name: 'HTML邮件转纯文本', - email: { - subject: 'Re: [TaskPing #HTML123]', - from: { text: 'user@example.com', value: [{ address: 'user@example.com' }] }, - html: '
运行测试套件

原始邮件...
', - text: '运行测试套件\n\n> 原始邮件...' - }, - expectedToken: 'HTML123', - expectedCommand: '运行测试套件' - } -]; - -// 运行测试 -function runTests() { - console.log('🧪 TaskPing 邮件解析测试\n'); - - let passed = 0; - let failed = 0; - - testCases.forEach((testCase, index) => { - console.log(`测试 ${index + 1}: ${testCase.name}`); - - try { - // 测试 Token 提取 - const token = extractTokenFromSubject(testCase.email.subject); - if (token === testCase.expectedToken) { - console.log(` ✅ Token提取正确: ${token}`); - } else { - console.log(` ❌ Token提取错误: 期望 "${testCase.expectedToken}", 实际 "${token}"`); - failed++; - console.log(''); - return; - } - - // 测试命令提取 - const command = stripReply(testCase.email.text || testCase.email.html); - if (command === testCase.expectedCommand) { - console.log(` ✅ 命令提取正确: "${command}"`); - passed++; - } else { - console.log(` ❌ 命令提取错误:`); - console.log(` 期望: "${testCase.expectedCommand}"`); - console.log(` 实际: "${command}"`); - failed++; - } - - } catch (error) { - console.log(` ❌ 测试出错: ${error.message}`); - failed++; - } - - console.log(''); - }); - - // 显示结果 - console.log('━'.repeat(50)); - console.log(`测试完成: ${passed} 通过, ${failed} 失败`); - - if (failed === 0) { - console.log('\n✅ 所有测试通过!'); - } else { - console.log('\n❌ 部分测试失败,请检查实现'); - } -} - -// 测试实际邮件处理 -async function testEmailProcessing() { - console.log('\n\n📧 测试邮件处理流程\n'); - - // 设置测试环境变量 - process.env.ALLOWED_SENDERS = 'user@example.com,boss@company.com'; - process.env.SESSION_MAP_PATH = path.join(__dirname, 'test-session-map.json'); - - // 创建测试会话 - const testSessions = { - 'TEST123': { - type: 'pty', - createdAt: Math.floor(Date.now() / 1000), - expiresAt: Math.floor((Date.now() + 3600000) / 1000), - cwd: process.cwd() - } - }; - - fs.writeFileSync(process.env.SESSION_MAP_PATH, JSON.stringify(testSessions, null, 2)); - console.log('✅ 创建测试会话文件'); - - // 模拟邮件消息 - const { simpleParser } = require('mailparser'); - const testEmail = `From: user@example.com -To: taskping@example.com -Subject: Re: [TaskPing #TEST123] 测试 -Content-Type: text/plain; charset=utf-8 - -这是测试命令 - -> 原始邮件内容...`; - - try { - console.log('🔄 处理模拟邮件...'); - - // 注意:实际的 handleMailMessage 会尝试创建 PTY - // 在测试环境中可能会失败,这是预期的 - await handleMailMessage(Buffer.from(testEmail), 'test-uid-123'); - - console.log('✅ 邮件处理流程完成(注意:PTY创建可能失败,这在测试中是正常的)'); - } catch (error) { - console.log(`⚠️ 邮件处理出错(预期的): ${error.message}`); - } - - // 清理测试文件 - if (fs.existsSync(process.env.SESSION_MAP_PATH)) { - fs.unlinkSync(process.env.SESSION_MAP_PATH); - console.log('🧹 清理测试文件'); - } -} - -// 显示集成说明 -function showIntegrationGuide() { - console.log('\n\n📚 集成指南\n'); - console.log('1. 配置邮件服务器信息:'); - console.log(' 编辑 .env 文件,填入 IMAP 配置'); - console.log(''); - console.log('2. 设置白名单发件人:'); - console.log(' ALLOWED_SENDERS=your-email@gmail.com'); - console.log(''); - console.log('3. 创建会话映射:'); - console.log(' 当发送提醒邮件时,在 session-map.json 中添加:'); - console.log(' {'); - console.log(' "YOUR_TOKEN": {'); - console.log(' "type": "pty",'); - console.log(' "createdAt": 1234567890,'); - console.log(' "expiresAt": 1234567890,'); - console.log(' "cwd": "/path/to/project"'); - console.log(' }'); - console.log(' }'); - console.log(''); - console.log('4. 启动服务:'); - console.log(' ./start-relay-pty.js'); - console.log(''); - console.log('5. 发送测试邮件:'); - console.log(' 主题包含 [TaskPing #YOUR_TOKEN]'); - console.log(' 正文为要执行的命令'); -} - -// 主函数 -async function main() { - console.log('╔══════════════════════════════════════════════════════════╗'); - console.log('║ TaskPing Email Reply Test Suite ║'); - console.log('╚══════════════════════════════════════════════════════════╝\n'); - - // 运行单元测试 - runTests(); - - // 测试邮件处理 - await testEmailProcessing(); - - // 显示集成指南 - showIntegrationGuide(); -} - -// 运行测试 -main().catch(console.error); \ No newline at end of file diff --git a/test-email-send.js b/test-email-send.js deleted file mode 100755 index 9544307..0000000 --- a/test-email-send.js +++ /dev/null @@ -1,180 +0,0 @@ -#!/usr/bin/env node - -/** - * 测试邮件发送功能 - * 验证 SMTP 配置是否正确 - */ - -const nodemailer = require('nodemailer'); -const fs = require('fs'); -const path = require('path'); -require('dotenv').config(); - -async function testEmailSend() { - console.log('📧 测试邮件发送功能\n'); - - // 检查配置 - if (!process.env.SMTP_HOST || !process.env.SMTP_USER || !process.env.SMTP_PASS) { - console.error('❌ 缺少 SMTP 配置,请检查 .env 文件'); - process.exit(1); - } - - // 创建测试会话 - const testToken = 'TEST' + Date.now().toString(36).toUpperCase(); - const sessionMapPath = process.env.SESSION_MAP_PATH || path.join(__dirname, 'src/data/session-map.json'); - - // 确保目录存在 - const dir = path.dirname(sessionMapPath); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - - // 创建会话 - const sessions = fs.existsSync(sessionMapPath) - ? JSON.parse(fs.readFileSync(sessionMapPath, 'utf8')) - : {}; - - sessions[testToken] = { - type: 'pty', - createdAt: Math.floor(Date.now() / 1000), - expiresAt: Math.floor((Date.now() + 3600000) / 1000), // 1小时后过期 - cwd: process.cwd(), - description: '测试会话' - }; - - fs.writeFileSync(sessionMapPath, JSON.stringify(sessions, null, 2)); - console.log(`✅ 创建测试会话: ${testToken}\n`); - - // 创建邮件传输器 - const transporter = nodemailer.createTransport({ - host: process.env.SMTP_HOST, - port: parseInt(process.env.SMTP_PORT || '465'), - secure: process.env.SMTP_SECURE === 'true', - auth: { - user: process.env.SMTP_USER, - pass: process.env.SMTP_PASS - } - }); - - // 准备邮件内容 - const mailOptions = { - from: `${process.env.EMAIL_FROM_NAME || 'TaskPing'} <${process.env.EMAIL_FROM || process.env.SMTP_USER}>`, - to: process.env.EMAIL_TO || 'jiaxicui446@gmail.com', - subject: `[TaskPing #${testToken}] 测试邮件 - 等待您的指令`, - html: ` -
-

🔔 TaskPing 测试通知

- -

这是一封测试邮件,用于验证邮件配置是否正确。

- -
-

会话信息:

-

Token: ${testToken}

-

创建时间: ${new Date().toLocaleString('zh-CN')}

-

过期时间: ${new Date(Date.now() + 3600000).toLocaleString('zh-CN')}

-
- -
-

📝 如何回复测试指令:

-

方式1(推荐):直接回复此邮件,内容输入:

-
    -
  • echo "Hello from email"
  • -
  • CMD: pwd
  • -
  • 使用代码块: -
    -\`\`\`
    -ls -la
    -\`\`\`
    -                            
    -
  • -
-

方式2:从任何邮箱发送邮件到 noreply@pandalla.ai

-

主题必须包含:[TaskPing #${testToken}]

-
- -
-

💡 多邮箱支持:

-

您可以从以下任意邮箱发送回复到 noreply@pandalla.ai

-
    -
  • Gmail、QQ邮箱、163邮箱
  • -
  • Outlook、企业邮箱
  • -
  • 任何支持SMTP的邮箱
  • -
-

回复路径:您的邮箱 → noreply@pandalla.ai → TaskPing系统处理

-
- -
- -

- 会话ID: ${testToken}
- 发送自: ${process.env.SMTP_USER}
- 通知邮箱: ${process.env.EMAIL_TO}
- 回复邮箱: ${process.env.SMTP_USER}
- 系统支持任意邮箱回复 -

-
- `, - text: `TaskPing 测试通知\n\n` + - `这是一封测试邮件,用于验证邮件配置是否正确。\n\n` + - `会话Token: ${testToken}\n` + - `创建时间: ${new Date().toLocaleString('zh-CN')}\n\n` + - `请回复任意命令来测试,例如: echo "Hello from email"\n\n` + - `会话ID: ${testToken}`, - headers: { - 'X-TaskPing-Session-ID': testToken, - 'X-TaskPing-Type': 'test' - } - }; - - // 发送邮件 - try { - console.log('🚀 正在发送测试邮件...'); - console.log(` 发件人: ${mailOptions.from}`); - console.log(` 收件人: ${mailOptions.to}`); - console.log(` 主题: ${mailOptions.subject}\n`); - - const info = await transporter.sendMail(mailOptions); - - console.log('✅ 邮件发送成功!'); - console.log(` Message ID: ${info.messageId}`); - console.log(` Response: ${info.response}\n`); - - console.log('📋 下一步操作:'); - console.log('1. 检查收件箱是否收到邮件'); - console.log('2. 确保 Gmail 已配置应用专用密码'); - console.log('3. 启动邮件监听服务: npm run relay:pty'); - console.log('4. 回复邮件测试命令注入功能'); - console.log(`\n💡 回复时主题保持: [TaskPing #${testToken}]`); - - } catch (error) { - console.error('❌ 邮件发送失败:', error.message); - - if (error.code === 'EAUTH') { - console.log('\n可能的原因:'); - console.log('1. SMTP 密码错误'); - console.log('2. 邮箱未开启 SMTP 服务'); - console.log('3. 需要使用应用专用密码而非账号密码'); - } - - if (error.code === 'ECONNECTION') { - console.log('\n可能的原因:'); - console.log('1. SMTP 服务器地址或端口错误'); - console.log('2. 网络连接问题'); - console.log('3. 防火墙阻止了连接'); - } - - process.exit(1); - } -} - -// 主函数 -async function main() { - console.log('╔══════════════════════════════════════════════════════════╗'); - console.log('║ TaskPing Email Send Test ║'); - console.log('╚══════════════════════════════════════════════════════════╝\n'); - - await testEmailSend(); -} - -// 运行 -main().catch(console.error); \ No newline at end of file diff --git a/test-imap-connection.js b/test-imap-connection.js deleted file mode 100755 index ee85088..0000000 --- a/test-imap-connection.js +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env node - -/** - * 测试 IMAP 连接 - * 验证邮箱服务器连接和邮件读取 - */ - -const { ImapFlow } = require('imapflow'); -const { simpleParser } = require('mailparser'); -require('dotenv').config(); - -async function testImapConnection() { - console.log('🔍 测试 IMAP 连接...\n'); - - const client = new ImapFlow({ - host: process.env.IMAP_HOST, - port: Number(process.env.IMAP_PORT || 993), - secure: process.env.IMAP_SECURE === 'true', - auth: { - user: process.env.IMAP_USER, - pass: process.env.IMAP_PASS - }, - logger: false - }); - - try { - console.log(`连接到: ${process.env.IMAP_HOST}:${process.env.IMAP_PORT}`); - console.log(`账号: ${process.env.IMAP_USER}`); - console.log(`SSL: ${process.env.IMAP_SECURE}`); - console.log(''); - - // 连接 - await client.connect(); - console.log('✅ IMAP 连接成功'); - - // 打开收件箱 - const lock = await client.getMailboxLock('INBOX'); - console.log('✅ 收件箱已打开'); - - try { - // 获取邮箱状态 - const status = await client.status('INBOX', { - messages: true, - unseen: true, - recent: true - }); - console.log(`📧 邮箱状态:`); - console.log(` 总邮件数: ${status.messages}`); - console.log(` 未读邮件: ${status.unseen}`); - console.log(` 新邮件: ${status.recent}`); - console.log(''); - - // 搜索未读邮件 - const unseenMessages = await client.search({ seen: false }); - console.log(`🔍 找到 ${unseenMessages.length} 封未读邮件`); - - if (unseenMessages.length > 0) { - console.log('📋 未读邮件列表:'); - - // 只处理最近的5封邮件 - const recentMessages = unseenMessages.slice(-5); - - for (const uid of recentMessages) { - try { - console.log(`\n📧 处理邮件 UID: ${uid}`); - - // 获取邮件头信息 - const envelope = await client.fetchOne(uid, { - envelope: true, - flags: true - }, { uid: true }); - - console.log(` 发件人: ${envelope.envelope?.from?.[0]?.address || 'unknown'}`); - console.log(` 主题: ${envelope.envelope?.subject || 'no subject'}`); - console.log(` 日期: ${envelope.envelope?.date?.toLocaleString('zh-CN') || 'unknown'}`); - console.log(` 标志: ${Array.isArray(envelope.flags) ? envelope.flags.join(', ') : 'none'}`); - - // 下载并解析邮件 - const message = await client.fetchOne(uid, { - source: true - }, { uid: true }); - - if (!message || !message.source) { - console.log(` ⚠️ 无法获取邮件内容`); - continue; - } - - const chunks = []; - for await (const chunk of message.source) { - chunks.push(chunk); - } - - const parsed = await simpleParser(Buffer.concat(chunks)); - - // 检查是否是 TaskPing 相关邮件 - const isTaskPingReply = parsed.subject?.includes('TaskPing') || - parsed.subject?.includes('[TaskPing') || - parsed.text?.includes('TaskPing') || - parsed.headers?.get('x-taskping-session-id'); - - if (isTaskPingReply) { - console.log(` 🎯 这是 TaskPing 相关邮件!`); - console.log(` 正文预览: ${(parsed.text || '').substring(0, 100)}...`); - - // 尝试提取 Token - const tokenMatch = parsed.subject?.match(/\[TaskPing\s+#([A-Za-z0-9_-]+)\]/); - if (tokenMatch) { - console.log(` 🔑 找到 Token: ${tokenMatch[1]}`); - } - } else { - console.log(` ℹ️ 非 TaskPing 邮件,跳过`); - } - - } catch (messageError) { - console.log(` ❌ 处理邮件失败: ${messageError.message}`); - } - } - } - - } finally { - lock.release(); - } - - console.log('\n✅ IMAP 测试完成'); - - } catch (error) { - console.error('\n❌ IMAP 连接失败:', error.message); - console.log('\n可能的原因:'); - console.log('1. 邮箱服务器地址或端口错误'); - console.log('2. 用户名或密码错误'); - console.log('3. IMAP 服务未开启'); - console.log('4. 网络连接问题'); - console.log('5. SSL/TLS 配置错误'); - - if (error.code) { - console.log(`\n错误代码: ${error.code}`); - } - - } finally { - await client.logout(); - } -} - -// 主函数 -async function main() { - console.log('╔══════════════════════════════════════════════════════════╗'); - console.log('║ TaskPing IMAP Connection Test ║'); - console.log('╚══════════════════════════════════════════════════════════╝\n'); - - // 检查配置 - if (!process.env.IMAP_HOST || !process.env.IMAP_USER || !process.env.IMAP_PASS) { - console.error('❌ 缺少 IMAP 配置,请检查 .env 文件'); - process.exit(1); - } - - await testImapConnection(); -} - -// 运行 -main().catch(console.error); \ No newline at end of file diff --git a/update-email-config.js b/update-email-config.js deleted file mode 100755 index ac018f9..0000000 --- a/update-email-config.js +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env node - -/** - * 更新邮件配置 - * 从 .env 文件读取邮件配置并更新到 channels.json - */ - -const fs = require('fs'); -const path = require('path'); -require('dotenv').config(); - -function updateEmailConfig() { - console.log('📧 正在更新邮件配置...\n'); - - // 验证必需的环境变量 - const required = ['SMTP_HOST', 'SMTP_USER', 'SMTP_PASS', 'EMAIL_TO']; - const missing = required.filter(key => !process.env[key]); - - if (missing.length > 0) { - console.error('❌ 缺少必需的环境变量:'); - missing.forEach(key => console.log(` - ${key}`)); - console.log('\n请检查 .env 文件配置'); - process.exit(1); - } - - // 构建邮件配置 - const emailConfig = { - email: { - type: 'email', - enabled: true, - config: { - smtp: { - host: process.env.SMTP_HOST, - port: parseInt(process.env.SMTP_PORT || '465'), - secure: process.env.SMTP_SECURE === 'true', - auth: { - user: process.env.SMTP_USER, - pass: process.env.SMTP_PASS - } - }, - imap: { - host: process.env.IMAP_HOST, - port: parseInt(process.env.IMAP_PORT || '993'), - secure: process.env.IMAP_SECURE === 'true', - auth: { - user: process.env.IMAP_USER, - pass: process.env.IMAP_PASS - } - }, - from: `${process.env.EMAIL_FROM_NAME || 'TaskPing'} <${process.env.EMAIL_FROM || process.env.SMTP_USER}>`, - to: process.env.EMAIL_TO, - template: { - checkInterval: parseInt(process.env.CHECK_INTERVAL || '30') - } - } - } - }; - - // 读取现有的 channels.json - const channelsPath = path.join(__dirname, 'config/channels.json'); - let channels = {}; - - if (fs.existsSync(channelsPath)) { - try { - channels = JSON.parse(fs.readFileSync(channelsPath, 'utf8')); - } catch (error) { - console.warn('⚠️ 无法读取现有配置,将创建新配置'); - } - } - - // 更新邮件通道配置 - channels.email = emailConfig.email; - - // 写入配置文件 - try { - fs.writeFileSync(channelsPath, JSON.stringify(channels, null, 2)); - console.log('✅ 邮件配置已更新到 config/channels.json'); - - // 显示配置信息 - console.log('\n📋 当前配置:'); - console.log(` 发件服务器: ${process.env.SMTP_HOST}`); - console.log(` 发件邮箱: ${process.env.SMTP_USER}`); - console.log(` 收件邮箱: ${process.env.EMAIL_TO}`); - - if (process.env.IMAP_USER) { - console.log(` 监听邮箱: ${process.env.IMAP_USER}`); - console.log(` IMAP服务器: ${process.env.IMAP_HOST}`); - } else { - console.log('\n⚠️ 注意: IMAP 未配置,邮件回复功能需要配置 Gmail 的 IMAP 信息'); - console.log(' 请在 .env 文件中设置 IMAP_PASS(Gmail应用专用密码)'); - } - - console.log('\n💡 提示:'); - console.log(' 1. Gmail 需要使用应用专用密码'); - console.log(' 访问 https://myaccount.google.com/apppasswords 生成'); - console.log(' 2. 测试邮件发送: npm test'); - console.log(' 3. 启动邮件监听: npm run relay:pty'); - - } catch (error) { - console.error('❌ 更新配置失败:', error.message); - process.exit(1); - } -} - -// 显示 Gmail 配置指南 -function showGmailGuide() { - console.log('\n📖 Gmail 配置指南:'); - console.log('1. 登录 Gmail 账号'); - console.log('2. 访问 https://myaccount.google.com/security'); - console.log('3. 开启"两步验证"(如果未开启)'); - console.log('4. 访问 https://myaccount.google.com/apppasswords'); - console.log('5. 选择应用"邮件",设备"其他"'); - console.log('6. 输入名称"TaskPing"'); - console.log('7. 生成16位应用专用密码'); - console.log('8. 将密码复制到 .env 文件的 IMAP_PASS'); - console.log('\n注意: 应用专用密码只显示一次,请妥善保存'); -} - -// 主函数 -function main() { - console.log('╔══════════════════════════════════════════════════════════╗'); - console.log('║ TaskPing Email Configuration ║'); - console.log('╚══════════════════════════════════════════════════════════╝\n'); - - // 检查 .env 文件 - if (!fs.existsSync('.env')) { - console.error('❌ 未找到 .env 文件'); - console.log('请先复制 .env.example 到 .env 并配置'); - process.exit(1); - } - - // 更新配置 - updateEmailConfig(); - - // 如果 IMAP 未配置,显示 Gmail 指南 - if (!process.env.IMAP_PASS || process.env.IMAP_PASS === 'your-gmail-app-password') { - showGmailGuide(); - } -} - -// 运行 -main(); \ No newline at end of file