@yvhitxcel/opencode-remote 0.15.0

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.
Files changed (102) hide show
  1. package/README.md +82 -0
  2. package/bin/opencode-remote.js +70 -0
  3. package/bin/opencode-weixin.js +10 -0
  4. package/dist/AGENTS.md +20 -0
  5. package/dist/MEMORY.md +21 -0
  6. package/dist/bot-runner.js +180 -0
  7. package/dist/cli.js +256 -0
  8. package/dist/core/approval.js +95 -0
  9. package/dist/core/auth.js +119 -0
  10. package/dist/core/config.js +61 -0
  11. package/dist/core/notifications.js +134 -0
  12. package/dist/core/qiniu.js +267 -0
  13. package/dist/core/registry.js +86 -0
  14. package/dist/core/router.js +344 -0
  15. package/dist/core/session.js +403 -0
  16. package/dist/core/setup.js +418 -0
  17. package/dist/core/types.js +16 -0
  18. package/dist/feishu/adapter.js +72 -0
  19. package/dist/feishu/bot.js +168 -0
  20. package/dist/feishu/commands.js +601 -0
  21. package/dist/feishu/handler.js +380 -0
  22. package/dist/index.js +60 -0
  23. package/dist/opencode/client.js +823 -0
  24. package/dist/package-lock.json +762 -0
  25. package/dist/patch_spawn.js +28 -0
  26. package/dist/plugins/agents/acp/acp-adapter.js +42 -0
  27. package/dist/plugins/agents/claude-code/index.js +69 -0
  28. package/dist/plugins/agents/codex/index.js +44 -0
  29. package/dist/plugins/agents/copilot/index.js +44 -0
  30. package/dist/plugins/agents/opencode/index.js +66 -0
  31. package/dist/telegram/bot.js +288 -0
  32. package/dist/utils/message-split.js +38 -0
  33. package/dist/web/code-viewer.js +266 -0
  34. package/dist/weixin/adapter.js +135 -0
  35. package/dist/weixin/api.js +179 -0
  36. package/dist/weixin/bot.js +183 -0
  37. package/dist/weixin/commands.js +758 -0
  38. package/dist/weixin/handler.js +577 -0
  39. package/dist/weixin/node_modules/encodeurl/LICENSE +22 -0
  40. package/dist/weixin/node_modules/encodeurl/README.md +109 -0
  41. package/dist/weixin/node_modules/encodeurl/index.js +60 -0
  42. package/dist/weixin/node_modules/encodeurl/package.json +40 -0
  43. package/dist/weixin/node_modules/qiniu/.claude/settings.local.json +7 -0
  44. package/dist/weixin/node_modules/qiniu/.github/workflows/ci-test.yml +36 -0
  45. package/dist/weixin/node_modules/qiniu/.github/workflows/npm-publish.yml +20 -0
  46. package/dist/weixin/node_modules/qiniu/.github/workflows/version-check.yml +19 -0
  47. package/dist/weixin/node_modules/qiniu/.idea/MarsCodeWorkspaceAppSettings.xml +7 -0
  48. package/dist/weixin/node_modules/qiniu/.idea/codeStyles/Project.xml +44 -0
  49. package/dist/weixin/node_modules/qiniu/.idea/codeStyles/codeStyleConfig.xml +5 -0
  50. package/dist/weixin/node_modules/qiniu/.idea/git_toolbox_blame.xml +6 -0
  51. package/dist/weixin/node_modules/qiniu/.idea/inspectionProfiles/Project_Default.xml +6 -0
  52. package/dist/weixin/node_modules/qiniu/.idea/jsLibraryMappings.xml +6 -0
  53. package/dist/weixin/node_modules/qiniu/.idea/modules.xml +8 -0
  54. package/dist/weixin/node_modules/qiniu/.idea/nodejs-sdk.iml +12 -0
  55. package/dist/weixin/node_modules/qiniu/.idea/vcs.xml +6 -0
  56. package/dist/weixin/node_modules/qiniu/CHANGELOG.md +292 -0
  57. package/dist/weixin/node_modules/qiniu/README.md +56 -0
  58. package/dist/weixin/node_modules/qiniu/StorageResponseInterface.d.ts +239 -0
  59. package/dist/weixin/node_modules/qiniu/codecov.yml +28 -0
  60. package/dist/weixin/node_modules/qiniu/index.d.ts +1995 -0
  61. package/dist/weixin/node_modules/qiniu/index.js +32 -0
  62. package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/HISTORY.md +14 -0
  63. package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/LICENSE +22 -0
  64. package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/README.md +128 -0
  65. package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/index.js +60 -0
  66. package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/package.json +40 -0
  67. package/dist/weixin/node_modules/qiniu/package.json +80 -0
  68. package/dist/weixin/node_modules/qiniu/qiniu/auth/digest.js +13 -0
  69. package/dist/weixin/node_modules/qiniu/qiniu/cdn.js +149 -0
  70. package/dist/weixin/node_modules/qiniu/qiniu/conf.js +254 -0
  71. package/dist/weixin/node_modules/qiniu/qiniu/fop.js +112 -0
  72. package/dist/weixin/node_modules/qiniu/qiniu/httpc/client.js +253 -0
  73. package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpoint.js +66 -0
  74. package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpointsProvider.js +27 -0
  75. package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpointsRetryPolicy.js +76 -0
  76. package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/base.js +31 -0
  77. package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/index.js +9 -0
  78. package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/qiniuAuth.js +53 -0
  79. package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/retryDomains.js +101 -0
  80. package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/ua.js +36 -0
  81. package/dist/weixin/node_modules/qiniu/qiniu/httpc/region.js +349 -0
  82. package/dist/weixin/node_modules/qiniu/qiniu/httpc/regionsProvider.js +788 -0
  83. package/dist/weixin/node_modules/qiniu/qiniu/httpc/regionsRetryPolicy.js +242 -0
  84. package/dist/weixin/node_modules/qiniu/qiniu/httpc/responseWrapper.js +40 -0
  85. package/dist/weixin/node_modules/qiniu/qiniu/retry/index.js +4 -0
  86. package/dist/weixin/node_modules/qiniu/qiniu/retry/retrier.js +99 -0
  87. package/dist/weixin/node_modules/qiniu/qiniu/retry/retryPolicy.js +55 -0
  88. package/dist/weixin/node_modules/qiniu/qiniu/rpc.js +237 -0
  89. package/dist/weixin/node_modules/qiniu/qiniu/rtc/app.js +123 -0
  90. package/dist/weixin/node_modules/qiniu/qiniu/rtc/credentials.js +57 -0
  91. package/dist/weixin/node_modules/qiniu/qiniu/rtc/room.js +118 -0
  92. package/dist/weixin/node_modules/qiniu/qiniu/rtc/util.js +16 -0
  93. package/dist/weixin/node_modules/qiniu/qiniu/sms/message.js +58 -0
  94. package/dist/weixin/node_modules/qiniu/qiniu/storage/form.js +442 -0
  95. package/dist/weixin/node_modules/qiniu/qiniu/storage/internal.js +214 -0
  96. package/dist/weixin/node_modules/qiniu/qiniu/storage/resume.js +1272 -0
  97. package/dist/weixin/node_modules/qiniu/qiniu/storage/rs.js +1764 -0
  98. package/dist/weixin/node_modules/qiniu/qiniu/util.js +382 -0
  99. package/dist/weixin/node_modules/qiniu/qiniu/zone.js +230 -0
  100. package/dist/weixin/node_modules/qiniu/tsconfig.json +112 -0
  101. package/dist/weixin/types.js +25 -0
  102. package/package.json +56 -0
@@ -0,0 +1,183 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync, chmodSync, unlinkSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ import { initSessionManager, loadSessionMapping, saveSessionMapping } from '../core/session.js';
5
+ import { initOpenCode, initFetchConfig } from '../opencode/client.js';
6
+ import { getAuthStatus } from '../core/auth.js';
7
+ import { registry } from '../core/registry.js';
8
+ import { fetchQRCode, pollQRStatus, getUpdates } from './api.js';
9
+ import { DEFAULT_BASE_URL } from './types.js';
10
+ import { createWeixinAdapter } from './adapter.js';
11
+ import { handleMessage } from './handler.js';
12
+ export { COMMAND_ALIASES, detectCommand } from '../core/router.js';
13
+
14
+ const CONFIG_DIR = join(homedir(), '.opencode-remote');
15
+ const WEIXIN_DIR = join(CONFIG_DIR, 'weixin');
16
+ const INSTANCE_ID = process.env.OPENCODE_INSTANCE_ID || 'default';
17
+ const CREDENTIALS_FILE = INSTANCE_ID === 'default'
18
+ ? join(WEIXIN_DIR, 'credentials.json')
19
+ : join(WEIXIN_DIR, `credentials-${INSTANCE_ID}.json`);
20
+ const RESTART_NOTIFY_FILE = join(WEIXIN_DIR, 'restart-notify.json');
21
+
22
+ export async function loginWithQR(baseUrl = DEFAULT_BASE_URL, onQRCode) {
23
+ console.log('Starting Weixin login...');
24
+ try {
25
+ const qrResp = await fetchQRCode(baseUrl);
26
+ if (!qrResp.qrcode_img_content) { console.error('Failed to get QR code'); return null; }
27
+ console.log(`QR Code URL: ${qrResp.qrcode_img_content}`);
28
+ if (onQRCode) onQRCode(qrResp.qrcode_img_content);
29
+ const startTime = Date.now();
30
+ const timeout = 8 * 60 * 1000;
31
+ while (Date.now() - startTime < timeout) {
32
+ const status = await pollQRStatus(baseUrl, qrResp.qrcode);
33
+ switch (status.status) {
34
+ case 'wait': process.stdout.write('.'); break;
35
+ case 'scaned': console.log('\nScanned! Confirm on phone...'); break;
36
+ case 'expired': console.log('\nQR expired.'); return null;
37
+ case 'confirmed':
38
+ if (!status.bot_token || !status.ilink_bot_id) return null;
39
+ console.log('\nLogin successful!');
40
+ const creds = { token: status.bot_token, baseUrl: status.baseurl || baseUrl, accountId: status.ilink_bot_id, userId: status.ilink_user_id };
41
+ saveWeixinCredentials(creds);
42
+ return creds;
43
+ }
44
+ await new Promise(r => setTimeout(r, 1000));
45
+ }
46
+ console.log('\nLogin timed out.');
47
+ return null;
48
+ } catch (e) { console.error('Login error:', e); return null; }
49
+ }
50
+
51
+ function ensureDirs() { if (!existsSync(WEIXIN_DIR)) mkdirSync(WEIXIN_DIR, { recursive: true }); }
52
+ export function loadWeixinCredentials() {
53
+ ensureDirs();
54
+ if (!existsSync(CREDENTIALS_FILE)) return null;
55
+ try { return JSON.parse(readFileSync(CREDENTIALS_FILE, 'utf-8')); } catch (e) { console.debug('[credentials] Failed to parse:', e.message); return null; }
56
+ }
57
+ export function saveWeixinCredentials(creds) {
58
+ ensureDirs();
59
+ writeFileSync(CREDENTIALS_FILE, JSON.stringify({ ...creds, savedAt: new Date().toISOString() }, null, 2), 'utf-8');
60
+ try { const s = statSync(CREDENTIALS_FILE); chmodSync(CREDENTIALS_FILE, (s.mode & 0o777) | 0o600); } catch (e) { console.warn('[credentials] chmod failed:', e.message); }
61
+ }
62
+
63
+ let _restartCallback = null;
64
+ function setRestartCallback(fn) { _restartCallback = fn; }
65
+ export async function startWeixinBot(botConfig, restartFn) {
66
+ if (restartFn) _restartCallback = restartFn;
67
+ console.log('');
68
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
69
+ console.log(' OpenCode Remote Control - Weixin');
70
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
71
+ console.log('');
72
+
73
+ await registry.loadBuiltInPlugins();
74
+
75
+ try { await initFetchConfig(); } catch (e) { console.warn('⚠️ Fetch config failed:', e); }
76
+ let credentials = loadWeixinCredentials();
77
+ if (!credentials) {
78
+ console.log('No saved credentials. Starting login...');
79
+ credentials = await loginWithQR(botConfig.weixinBaseUrl || DEFAULT_BASE_URL);
80
+ if (!credentials) { console.error('Login failed'); process.exit(1); }
81
+ }
82
+ console.log(`Using account: ${credentials.accountId}`);
83
+ const baseUrl = credentials.baseUrl || DEFAULT_BASE_URL;
84
+ const token = credentials.token;
85
+ const botId = credentials.accountId;
86
+ initSessionManager(botConfig);
87
+ const openCodeSessions = new Map();
88
+ const adapter = createWeixinAdapter(baseUrl, token, botId);
89
+ try { await initOpenCode(); console.log('OpenCode ready'); } catch (e) { console.error('Failed to init OpenCode:', e); }
90
+
91
+ try {
92
+ const opencode = await initOpenCode();
93
+ if (opencode) {
94
+ const result = await opencode.client.session.list();
95
+ if (!result.error && result.data && result.data.length > 0) {
96
+ const sorted = result.data.sort((a, b) => (b.updated_at || 0) - (a.updated_at || 0));
97
+ const latest = sorted[0];
98
+ console.log(`Latest OpenCode session: ${latest.title || 'Untitled'} (${latest.id.slice(0, 8)}...)`);
99
+ globalThis.__latestOpenCodeSession = { id: latest.id, directory: latest.directory };
100
+ if (latest.directory) {
101
+ console.log(`Project directory: ${latest.directory}`);
102
+ globalThis.__autoProjectDir = latest.directory;
103
+ }
104
+ }
105
+ }
106
+ } catch (e) { console.warn('⚠️ Auto-resume failed:', e.message); }
107
+
108
+ if (!getAuthStatus().weixin) {
109
+ console.log('\n🔒 Bot not secured! First user to send /start becomes owner.\n');
110
+ }
111
+
112
+ try {
113
+ if (existsSync(RESTART_NOTIFY_FILE)) {
114
+ const data = JSON.parse(readFileSync(RESTART_NOTIFY_FILE, 'utf8'));
115
+ if (data.threadId && Date.now() - data.time < 60000) {
116
+ await adapter.reply(data.threadId, '✅ Bot 重启完成!');
117
+ }
118
+ unlinkSync(RESTART_NOTIFY_FILE);
119
+ }
120
+ } catch (e) { console.warn('[restart-notify] Failed to read restart file:', e.message); }
121
+
122
+ let running = true;
123
+ let shouldRestart = false;
124
+ const shutdown = (restart = false) => {
125
+ console.log(restart ? '\nRestarting...' : '\nShutting down...');
126
+ saveSessionMapping();
127
+ running = false;
128
+ shouldRestart = restart;
129
+ for (const [, s] of openCodeSessions.entries()) { try { s.server?.shutdown?.(); } catch (e) { console.warn('[shutdown] Server shutdown error:', e.message); } }
130
+ openCodeSessions.clear();
131
+ };
132
+
133
+ globalThis.__weixinBotShutdown = (restart = false) => shutdown(restart);
134
+ globalThis.__weixinBotRunning = () => running;
135
+
136
+ let buf = '';
137
+ console.log('Polling for messages...');
138
+
139
+ if (process.env.OPENCODE_RESTART === '1') {
140
+ try {
141
+ const restartInfoPath = join(process.env.HOME || process.cwd(), '.opencode-remote', '.restart_user.json');
142
+ if (existsSync(restartInfoPath)) {
143
+ const restartInfo = JSON.parse(readFileSync(restartInfoPath, 'utf8'));
144
+ if (Date.now() - restartInfo.time < 60000) {
145
+ await adapter.reply(restartInfo.threadId, '✅ Bot 重启完成!');
146
+ console.log('Sent restart notification to user');
147
+ }
148
+ unlinkSync(restartInfoPath);
149
+ }
150
+ } catch (e) {
151
+ console.log('Could not send restart notification:', e.message);
152
+ }
153
+ }
154
+
155
+ while (running) {
156
+ try {
157
+ const resp = await getUpdates({ baseUrl, token, get_updates_buf: buf });
158
+ if (!running) break;
159
+ if (resp.get_updates_buf) buf = resp.get_updates_buf;
160
+ for (const msg of (resp.msgs || [])) {
161
+ if (msg.message_type !== 1) continue;
162
+ const textItem = msg.item_list?.find((i) => i.type === 1);
163
+ const text = textItem?.text_item?.text;
164
+ const fromUserId = msg.from_user_id;
165
+ if (!fromUserId || !text) continue;
166
+ const messageId = msg.message_id?.toString();
167
+ if (adapter.isDuplicate(messageId)) continue;
168
+ if (msg.context_token) adapter.contextTokens.set(fromUserId, msg.context_token);
169
+ handleMessage(adapter, { platform: 'weixin', threadId: fromUserId, userId: fromUserId, messageId }, text, openCodeSessions).catch(e => console.error('Handle error:', e));
170
+ }
171
+ } catch (e) {
172
+ if (!running) break;
173
+ console.error('Polling error:', e);
174
+ await new Promise(r => setTimeout(r, 2000));
175
+ }
176
+ }
177
+
178
+ if (shouldRestart) {
179
+ console.log('✅ Bot shutdown complete, exiting for restart...');
180
+ process.exit(0);
181
+ }
182
+ return shouldRestart;
183
+ }