@yvhitxcel/opencode-remote 0.15.1 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -8
- package/dist/core/auth.js +41 -108
- package/dist/core/notifications.js +11 -0
- package/dist/core/router.js +291 -61
- package/dist/feishu/commands.js +29 -35
- package/dist/feishu/handler.js +17 -26
- package/dist/opencode/client.js +48 -77
- package/dist/plugins/agents/claude-code/index.js +46 -4
- package/dist/telegram/adapter.js +75 -0
- package/dist/telegram/bot.js +66 -208
- package/dist/weixin/bot.js +12 -2
- package/dist/weixin/commands.js +29 -42
- package/dist/weixin/handler.js +80 -107
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -2,12 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
通过微信、Telegram、飞书等平台随时随地控制 OpenCode。
|
|
4
4
|
|
|
5
|
+
## 为什么这么爽?
|
|
6
|
+
|
|
7
|
+
**输入一个字母 `z`,14 位 AI 专家帮你审项目。** 架构师、安全研究员、测试工程师、运维……每人给出犀利点评,技术经理汇总 P0-P2 任务清单。你在手机上躺着看就行。
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# 安装
|
|
11
|
+
npm install -g @yvhitxcel/opencode-remote
|
|
12
|
+
|
|
13
|
+
# 开干
|
|
14
|
+
opencode-remote telegram
|
|
15
|
+
# → 输入 /z 叫专家团队
|
|
16
|
+
# → 输入 z 让专家分析项目
|
|
17
|
+
# → AI 自动干活,你喝茶
|
|
18
|
+
```
|
|
19
|
+
|
|
5
20
|
## 功能特性
|
|
6
21
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
22
|
+
- **🤖 专家评审系统** — `/z` 一键召唤 14 位 AI 专家,自动分析、投票、出方案
|
|
23
|
+
- **📱 多平台支持** — 微信、飞书、Telegram
|
|
24
|
+
- **🧠 多 AI Agent** — OpenCode、Claude Code、Codex、GitHub Copilot
|
|
25
|
+
- **🔄 循环任务** — `/loop` 让 AI 持续干活
|
|
26
|
+
- **🔍 一键诊断** — `/diagnose` 检查各组件状态
|
|
11
27
|
|
|
12
28
|
## 安装
|
|
13
29
|
|
|
@@ -17,15 +33,42 @@ npm install -g opencode-remote
|
|
|
17
33
|
|
|
18
34
|
## 快速开始
|
|
19
35
|
|
|
36
|
+
推荐路径:**Telegram(5分钟)→ 微信(10分钟)→ 飞书(30分钟)**
|
|
37
|
+
|
|
20
38
|
```bash
|
|
21
|
-
#
|
|
39
|
+
# 1. 安装
|
|
40
|
+
npm install -g @yvhitxcel/opencode-remote
|
|
41
|
+
|
|
42
|
+
# 2. 启动 Telegram(最快上手,无需配置)
|
|
43
|
+
opencode-remote telegram
|
|
44
|
+
# 在 Telegram 里搜索你的 bot,发送 /start
|
|
45
|
+
|
|
46
|
+
# 3. 微信(需要 iLink 平台账号)
|
|
22
47
|
opencode-remote weixin
|
|
48
|
+
# 扫码登录后即可使用
|
|
23
49
|
|
|
24
|
-
#
|
|
50
|
+
# 4. 飞书(需要企业版账号)
|
|
25
51
|
opencode-remote feishu
|
|
52
|
+
```
|
|
26
53
|
|
|
27
|
-
|
|
28
|
-
|
|
54
|
+
## 首次使用
|
|
55
|
+
|
|
56
|
+
1. 安装后运行 `opencode-remote telegram`
|
|
57
|
+
2. 在 Telegram 里找到你的 bot,发送 `/start`
|
|
58
|
+
3. 发送 `/help` 查看所有命令
|
|
59
|
+
4. 发送一条消息给 AI,比如"你好"
|
|
60
|
+
5. **发送 `/z` 启动专家模式,然后发送 `z` —— 14 位 AI 专家开始分析你的项目**
|
|
61
|
+
|
|
62
|
+
> 💡 所有核心功能(对话、会话管理、AI 模型切换、专家评审)无需任何配置。只有 `/upload` 上传才需要七牛云。
|
|
63
|
+
|
|
64
|
+
## 手机开发工作流
|
|
65
|
+
|
|
66
|
+
把 `weixin.bat` 复制到项目根目录,双击运行,扫码登录后即可在手机上通过微信开发该项目。
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 或者手动指定目录
|
|
70
|
+
cd 你的项目
|
|
71
|
+
opencode-remote
|
|
29
72
|
```
|
|
30
73
|
|
|
31
74
|
## 平台兼容性
|
|
@@ -90,10 +133,37 @@ opencode-remote
|
|
|
90
133
|
|
|
91
134
|
**七牛云是可选的**,不配也能用全部核心功能。只有上传构建产物才需要配置。
|
|
92
135
|
|
|
136
|
+
## 常见问题
|
|
137
|
+
|
|
138
|
+
**Q: 为什么有些命令不能用?**
|
|
139
|
+
A: 先运行 `/diagnose` 检查各组件状态。Telegram 功能最全,微信和飞书部分命令需要额外配置。
|
|
140
|
+
|
|
141
|
+
**Q: 微信怎么登录?**
|
|
142
|
+
A: 运行 `opencode-remote weixin`,终端会显示二维码,用微信扫码即可。
|
|
143
|
+
|
|
144
|
+
**Q: 专家评审怎么用?**
|
|
145
|
+
A: 发 `z` 或 `叫全部专家`,AI 自动扫项目、组队评审、出 P0/P1 修复方案,**然后自动修代码**。评审过程包括三级质量保障:脑内路径追踪、服务端模拟验证、对抗评审。
|
|
146
|
+
|
|
147
|
+
**Q: `/z` 什么时候用?**
|
|
148
|
+
A: 任何时候。发一个 `z` 让 AI 评审当前项目,发 `z 帮我看看这个bug` 聚焦具体问题。先 `/z` 设置自定义 prompt 再发问题也行。
|
|
149
|
+
|
|
150
|
+
**Q: `/z` 自动修代码会改坏吗?**
|
|
151
|
+
A: **强烈建议在 git 仓库中使用。** 如果改坏了可以 `git checkout .` 回滚。没有 git 的项目,AI 改完不可逆。不确定的话先发 `/z off` 关闭自动执行,只看报告不改代码。
|
|
152
|
+
|
|
153
|
+
**Q: 需要自己的服务器吗?**
|
|
154
|
+
A: 需要一台电脑运行 bot,手机上通过 IM 控制。OpenCode 也运行在这台电脑上。
|
|
155
|
+
|
|
156
|
+
**Q: 如何更新?**
|
|
157
|
+
A: `npm update -g @yvhitxcel/opencode-remote`
|
|
158
|
+
|
|
93
159
|
## 系统要求
|
|
94
160
|
|
|
95
161
|
- Node.js >= 18.0.0
|
|
96
162
|
|
|
163
|
+
## 致谢
|
|
164
|
+
|
|
165
|
+
本项目基于 [opencode-remote-control](https://github.com/ceociocto/opencode-remote-control) 开发。
|
|
166
|
+
|
|
97
167
|
## 许可证
|
|
98
168
|
|
|
99
169
|
MIT License
|
package/dist/core/auth.js
CHANGED
|
@@ -1,119 +1,52 @@
|
|
|
1
|
-
// Authorization management for OpenCode Remote Control
|
|
2
|
-
// First user to send /start becomes the owner automatically
|
|
3
|
-
const authState = {
|
|
4
|
-
telegramOwner: null,
|
|
5
|
-
feishuOwner: null,
|
|
6
|
-
weixinOwner: null,
|
|
7
|
-
};
|
|
8
|
-
// Auth file path for persistence
|
|
9
1
|
import { homedir } from 'os';
|
|
10
2
|
import { join } from 'path';
|
|
11
3
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
writeFileSync(AUTH_FILE, JSON.stringify(authState, null, 2));
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
console.error('Failed to save auth state:', error);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
// Initialize on module load
|
|
42
|
-
loadAuth();
|
|
4
|
+
|
|
5
|
+
const OWNER_KEY = { telegram: 'telegramOwner', feishu: 'feishuOwner', weixin: 'weixinOwner' };
|
|
6
|
+
const AUTH_FILE = join(homedir(), '.opencode-remote', 'auth.json');
|
|
7
|
+
|
|
8
|
+
const state = { telegramOwner: null, feishuOwner: null, weixinOwner: null };
|
|
9
|
+
|
|
10
|
+
function load() {
|
|
11
|
+
try {
|
|
12
|
+
if (existsSync(AUTH_FILE)) {
|
|
13
|
+
const d = JSON.parse(readFileSync(AUTH_FILE, 'utf-8'));
|
|
14
|
+
for (const k of Object.keys(OWNER_KEY)) state[OWNER_KEY[k]] = d[OWNER_KEY[k]] || null;
|
|
15
|
+
}
|
|
16
|
+
} catch (e) { console.warn('[auth] load failed:', e.message); }
|
|
17
|
+
}
|
|
18
|
+
function save() {
|
|
19
|
+
try {
|
|
20
|
+
const dir = join(homedir(), '.opencode-remote');
|
|
21
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
22
|
+
writeFileSync(AUTH_FILE, JSON.stringify(state, null, 2));
|
|
23
|
+
} catch (e) { console.error('[auth] save failed:', e.message); }
|
|
24
|
+
}
|
|
25
|
+
load();
|
|
26
|
+
|
|
43
27
|
export function isAuthorized(platform, userId) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
else if (platform === 'feishu') {
|
|
48
|
-
return authState.feishuOwner === userId;
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
return authState.weixinOwner === userId;
|
|
52
|
-
}
|
|
28
|
+
const key = OWNER_KEY[platform];
|
|
29
|
+
return key ? state[key] === userId : false;
|
|
53
30
|
}
|
|
54
31
|
export function hasOwner(platform) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
else if (platform === 'feishu') {
|
|
59
|
-
return authState.feishuOwner !== null;
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
return authState.weixinOwner !== null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
export function claimOwnership(platform, userId) {
|
|
66
|
-
if (platform === 'telegram') {
|
|
67
|
-
if (authState.telegramOwner) {
|
|
68
|
-
if (authState.telegramOwner === userId) {
|
|
69
|
-
return { success: true, message: 'already_owner' };
|
|
70
|
-
}
|
|
71
|
-
return { success: false, message: 'already_claimed' };
|
|
72
|
-
}
|
|
73
|
-
authState.telegramOwner = userId;
|
|
74
|
-
saveAuth();
|
|
75
|
-
return { success: true, message: 'claimed' };
|
|
76
|
-
}
|
|
77
|
-
else if (platform === 'feishu') {
|
|
78
|
-
if (authState.feishuOwner) {
|
|
79
|
-
if (authState.feishuOwner === userId) {
|
|
80
|
-
return { success: true, message: 'already_owner' };
|
|
81
|
-
}
|
|
82
|
-
return { success: false, message: 'already_claimed' };
|
|
83
|
-
}
|
|
84
|
-
authState.feishuOwner = userId;
|
|
85
|
-
saveAuth();
|
|
86
|
-
return { success: true, message: 'claimed' };
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
// weixin
|
|
90
|
-
if (authState.weixinOwner) {
|
|
91
|
-
if (authState.weixinOwner === userId) {
|
|
92
|
-
return { success: true, message: 'already_owner' };
|
|
93
|
-
}
|
|
94
|
-
return { success: false, message: 'already_claimed' };
|
|
95
|
-
}
|
|
96
|
-
authState.weixinOwner = userId;
|
|
97
|
-
saveAuth();
|
|
98
|
-
return { success: true, message: 'claimed' };
|
|
99
|
-
}
|
|
32
|
+
const key = OWNER_KEY[platform];
|
|
33
|
+
return key ? state[key] !== null : false;
|
|
100
34
|
}
|
|
101
35
|
export function getOwner(platform) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
36
|
+
const key = OWNER_KEY[platform];
|
|
37
|
+
return key ? state[key] : null;
|
|
38
|
+
}
|
|
39
|
+
export function claimOwnership(platform, userId) {
|
|
40
|
+
const key = OWNER_KEY[platform];
|
|
41
|
+
if (!key) return { success: false, message: 'unknown_platform' };
|
|
42
|
+
if (state[key]) {
|
|
43
|
+
if (state[key] === userId) return { success: true, message: 'already_owner' };
|
|
44
|
+
return { success: false, message: 'already_claimed' };
|
|
45
|
+
}
|
|
46
|
+
state[key] = userId;
|
|
47
|
+
save();
|
|
48
|
+
return { success: true, message: 'claimed' };
|
|
111
49
|
}
|
|
112
|
-
// For debugging/display
|
|
113
50
|
export function getAuthStatus() {
|
|
114
|
-
|
|
115
|
-
telegram: authState.telegramOwner !== null,
|
|
116
|
-
feishu: authState.feishuOwner !== null,
|
|
117
|
-
weixin: authState.weixinOwner !== null,
|
|
118
|
-
};
|
|
51
|
+
return { telegram: !!state.telegramOwner, feishu: !!state.feishuOwner, weixin: !!state.weixinOwner };
|
|
119
52
|
}
|
|
@@ -101,6 +101,17 @@ export const TEMPLATES = {
|
|
|
101
101
|
details: 'Changes were automatically rejected.',
|
|
102
102
|
}),
|
|
103
103
|
};
|
|
104
|
+
// Task completion notification with timing
|
|
105
|
+
export function formatTaskCompletion(taskName, startTime, extra) {
|
|
106
|
+
const elapsed = Date.now() - startTime;
|
|
107
|
+
const seconds = Math.floor(elapsed / 1000);
|
|
108
|
+
const timeStr = seconds >= 60 ? `${Math.floor(seconds / 60)}分${seconds % 60}秒` : `${seconds}秒`;
|
|
109
|
+
const lines = [`✅ 任务完成: ${taskName}`, '', `⏱️ 耗时: ${timeStr}`];
|
|
110
|
+
if (extra?.files && extra.files > 0) lines.push(`📄 修改文件: ${extra.files} 个`);
|
|
111
|
+
if (extra?.iterations && extra.iterations > 0) lines.push(`🔄 迭代次数: ${extra.iterations}`);
|
|
112
|
+
return lines.join('\n');
|
|
113
|
+
}
|
|
114
|
+
|
|
104
115
|
// Split message for Telegram's 4096 char limit
|
|
105
116
|
export function splitMessage(text, maxLength = 4000) {
|
|
106
117
|
if (text.length <= maxLength) {
|