@link-assistant/hive-mind 1.74.6 → 1.74.7
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/CHANGELOG.md +8 -0
- package/README.hi.md +3 -0
- package/README.md +3 -0
- package/README.ru.md +3 -0
- package/README.zh.md +2 -0
- package/package.json +1 -1
- package/src/telegram-auth-command.lib.mjs +298 -0
- package/src/telegram-bot.mjs +11 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.74.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8ea7110: Document the issue #1858 case study and add an experimental private Telegram
|
|
8
|
+
`/auth` command for allowlisted chat owners to check or start GitHub, Claude,
|
|
9
|
+
and Codex auth flows.
|
|
10
|
+
|
|
3
11
|
## 1.74.6
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/README.hi.md
CHANGED
|
@@ -559,6 +559,7 @@ Shows:
|
|
|
559
559
|
- ✅ **Screen सत्र**: कमांड डिटैच्ड screen सत्रों में चलते हैं
|
|
560
560
|
- ✅ **Live Terminal Watch**: `/terminal_watch` और opt-in auto-start live session logs दिखाते हैं
|
|
561
561
|
- ✅ **चैट प्रतिबंध**: अनुमत चैट ID की वैकल्पिक सफेद सूची
|
|
562
|
+
- ✅ **Private Auth Check**: allowlisted chat owners के लिए experimental `/auth --status <gh|claude|codex>` और `/auth --login <gh|claude|codex>`
|
|
562
563
|
- ✅ **डायग्नोस्टिक टूल**: चैट ID और कॉन्फ़िगरेशन जानकारी प्राप्त करें
|
|
563
564
|
|
|
564
565
|
#### Live Terminal Watch
|
|
@@ -577,6 +578,8 @@ sessions के लिए अपने आप एक अलग live terminal wat
|
|
|
577
578
|
|
|
578
579
|
- केवल उन ग्रुप चैट में काम करता है जहाँ बॉट एडमिन है
|
|
579
580
|
- `TELEGRAM_ALLOWED_CHATS` के माध्यम से वैकल्पिक चैट ID प्रतिबंध
|
|
581
|
+
- private `/auth` तब disabled रहता है जब `TELEGRAM_ALLOWED_CHATS` set नहीं है,
|
|
582
|
+
और इसे केवल listed chats के owners इस्तेमाल कर सकते हैं
|
|
580
583
|
- बॉट चलाने वाले सिस्टम उपयोगकर्ता के रूप में कमांड चलते हैं
|
|
581
584
|
- उचित प्रमाणीकरण सुनिश्चित करें (`gh auth login`, `claude-profiles`)
|
|
582
585
|
|
package/README.md
CHANGED
|
@@ -580,6 +580,7 @@ Shows:
|
|
|
580
580
|
- ✅ **Screen Sessions**: Commands run in detached screen sessions
|
|
581
581
|
- ✅ **Live Terminal Watch**: `/terminal_watch` and opt-in auto-start show live session logs
|
|
582
582
|
- ✅ **Chat Restrictions**: Optional whitelist of allowed chat IDs
|
|
583
|
+
- ✅ **Private Auth Check**: Experimental `/auth --status <gh|claude|codex>` and `/auth --login <gh|claude|codex>` for owners of allowlisted chats
|
|
583
584
|
- ✅ **Diagnostic Tools**: Get chat ID and configuration info
|
|
584
585
|
|
|
585
586
|
#### Live Terminal Watch
|
|
@@ -597,6 +598,8 @@ When enabled with `--auto-start-screen-watch-message`, the bot automatically sta
|
|
|
597
598
|
|
|
598
599
|
- Only works in group chats where the bot is admin
|
|
599
600
|
- Optional chat ID restrictions via `TELEGRAM_ALLOWED_CHATS`
|
|
601
|
+
- Private `/auth` is disabled unless `TELEGRAM_ALLOWED_CHATS` is set and only
|
|
602
|
+
owners of listed chats can use it
|
|
600
603
|
- Commands run as the system user running the bot
|
|
601
604
|
- Ensure proper authentication (`gh auth login`, `claude-profiles`)
|
|
602
605
|
|
package/README.ru.md
CHANGED
|
@@ -561,6 +561,7 @@ Shows:
|
|
|
561
561
|
- ✅ **Screen-сессии**: команды запускаются в отсоединённых screen-сессиях
|
|
562
562
|
- ✅ **Live Terminal Watch**: `/terminal_watch` и opt-in auto-start показывают live session logs
|
|
563
563
|
- ✅ **Ограничения по чатам**: опциональный белый список разрешённых ID чатов
|
|
564
|
+
- ✅ **Приватная проверка auth**: экспериментальные `/auth --status <gh|claude|codex>` и `/auth --login <gh|claude|codex>` для владельцев разрешённых чатов
|
|
564
565
|
- ✅ **Диагностические инструменты**: получение ID чата и информации о конфигурации
|
|
565
566
|
|
|
566
567
|
#### Live Terminal Watch
|
|
@@ -579,6 +580,8 @@ Shows:
|
|
|
579
580
|
|
|
580
581
|
- Работает только в групповых чатах, где бот является администратором
|
|
581
582
|
- Опциональное ограничение по ID чата через `TELEGRAM_ALLOWED_CHATS`
|
|
583
|
+
- Приватная `/auth` отключена, если `TELEGRAM_ALLOWED_CHATS` не задан, и
|
|
584
|
+
доступна только владельцам перечисленных чатов
|
|
582
585
|
- Команды выполняются от имени системного пользователя, запустившего бота
|
|
583
586
|
- Убедитесь в наличии надлежащей аутентификации (`gh auth login`, `claude-profiles`)
|
|
584
587
|
|
package/README.zh.md
CHANGED
|
@@ -555,6 +555,7 @@ Shows:
|
|
|
555
555
|
- ✅ **Screen 会话**:命令在后台 Screen 会话中运行
|
|
556
556
|
- ✅ **Live Terminal Watch**:`/terminal_watch` 和 opt-in auto-start 显示 live session logs
|
|
557
557
|
- ✅ **聊天限制**:可选配置允许的聊天 ID 白名单
|
|
558
|
+
- ✅ **私聊 Auth 检查**:为白名单聊天所有者提供实验性的 `/auth --status <gh|claude|codex>` 和 `/auth --login <gh|claude|codex>`
|
|
558
559
|
- ✅ **诊断工具**:获取聊天 ID 和配置信息
|
|
559
560
|
|
|
560
561
|
#### Live Terminal Watch
|
|
@@ -573,6 +574,7 @@ Shows:
|
|
|
573
574
|
|
|
574
575
|
- 仅在机器人为管理员的群聊中有效
|
|
575
576
|
- 可通过 `TELEGRAM_ALLOWED_CHATS` 配置可选的聊天 ID 限制
|
|
577
|
+
- 如果未设置 `TELEGRAM_ALLOWED_CHATS`,私聊 `/auth` 会被禁用,且只有所列聊天的所有者可以使用
|
|
576
578
|
- 命令以运行机器人的系统用户身份执行
|
|
577
579
|
- 请确保已完成正确的身份验证(`gh auth login`、`claude-profiles`)
|
|
578
580
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { parseCommandArgs } from './telegram-solve-command.lib.mjs';
|
|
3
|
+
|
|
4
|
+
export const AUTH_PROVIDERS = Object.freeze(['gh', 'claude', 'codex']);
|
|
5
|
+
|
|
6
|
+
const AUTH_PROVIDER_SET = new Set(AUTH_PROVIDERS);
|
|
7
|
+
const AUTH_USAGE = 'Usage: /auth --status <gh|claude|codex> or /auth --login <gh|claude|codex>';
|
|
8
|
+
// eslint-disable-next-line no-control-regex
|
|
9
|
+
const ANSI_RE = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
10
|
+
const TOKEN_RE = /\b(?:gh[opsu]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk-proj-[A-Za-z0-9_-]{20,}|sk-[A-Za-z0-9_-]{20,}|xox[baprs]-[A-Za-z0-9-]{20,})\b/g;
|
|
11
|
+
const TOKEN_FIELD_RE = /\b(token|access_token|refresh_token|api[_-]?key|authorization)\s*[:=]\s*["']?[^"'\s,}]+/gi;
|
|
12
|
+
|
|
13
|
+
function trimOutput(text, max = 3500) {
|
|
14
|
+
const value = String(text || '').trim();
|
|
15
|
+
if (value.length <= max) return value;
|
|
16
|
+
return value.slice(0, max) + `\n... truncated ${value.length - max} characters`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function escapeCodeFence(text) {
|
|
20
|
+
return String(text || '').replace(/```/g, '` ` `');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeProvider(provider) {
|
|
24
|
+
return String(provider || '')
|
|
25
|
+
.trim()
|
|
26
|
+
.toLowerCase();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readActionValue(arg) {
|
|
30
|
+
if (arg === '--status') return { action: 'status', provider: null, consumesNext: true };
|
|
31
|
+
if (arg === '--login') return { action: 'login', provider: null, consumesNext: true };
|
|
32
|
+
if (arg.startsWith('--status=')) return { action: 'status', provider: arg.slice('--status='.length), consumesNext: false };
|
|
33
|
+
if (arg.startsWith('--login=')) return { action: 'login', provider: arg.slice('--login='.length), consumesNext: false };
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function parseAuthRequest(text) {
|
|
38
|
+
const args = parseCommandArgs(text || '');
|
|
39
|
+
let action = null;
|
|
40
|
+
let provider = null;
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < args.length; i++) {
|
|
43
|
+
const parsed = readActionValue(args[i]);
|
|
44
|
+
if (!parsed) {
|
|
45
|
+
return { action: null, provider: null, error: `Unsupported /auth argument: ${args[i]}\n\n${AUTH_USAGE}` };
|
|
46
|
+
}
|
|
47
|
+
if (action) {
|
|
48
|
+
return { action: null, provider: null, error: `Use exactly one of --status or --login.\n\n${AUTH_USAGE}` };
|
|
49
|
+
}
|
|
50
|
+
action = parsed.action;
|
|
51
|
+
provider = normalizeProvider(parsed.provider);
|
|
52
|
+
if (parsed.consumesNext) {
|
|
53
|
+
const next = args[i + 1];
|
|
54
|
+
if (!next || next.startsWith('--')) {
|
|
55
|
+
return { action: null, provider: null, error: AUTH_USAGE };
|
|
56
|
+
}
|
|
57
|
+
provider = normalizeProvider(next);
|
|
58
|
+
i++;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!action || !provider) {
|
|
63
|
+
return { action: null, provider: null, error: AUTH_USAGE };
|
|
64
|
+
}
|
|
65
|
+
if (!AUTH_PROVIDER_SET.has(provider)) {
|
|
66
|
+
return { action, provider: null, error: `Unsupported auth provider: ${provider}\n\n${AUTH_USAGE}` };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { action, provider, error: null };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function buildAuthCommand(action, provider) {
|
|
73
|
+
if (action === 'status') {
|
|
74
|
+
if (provider === 'gh') return { command: 'gh', args: ['auth', 'status', '--hostname', 'github.com'] };
|
|
75
|
+
if (provider === 'claude') return { command: 'claude', args: ['auth', 'status'] };
|
|
76
|
+
if (provider === 'codex') return { command: 'codex', args: ['login', 'status'] };
|
|
77
|
+
}
|
|
78
|
+
if (action === 'login') {
|
|
79
|
+
if (provider === 'gh') return { command: 'gh', args: ['auth', 'login', '--hostname', 'github.com', '--git-protocol', 'https', '--web'] };
|
|
80
|
+
if (provider === 'claude') return { command: 'claude', args: ['auth', 'login', '--claudeai'] };
|
|
81
|
+
if (provider === 'codex') return { command: 'codex', args: ['login', '--device-auth'] };
|
|
82
|
+
}
|
|
83
|
+
throw new Error(`Unsupported auth command: ${action} ${provider}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function redactAuthOutput(output) {
|
|
87
|
+
return String(output || '')
|
|
88
|
+
.replace(ANSI_RE, '')
|
|
89
|
+
.replace(TOKEN_RE, '[REDACTED_TOKEN]')
|
|
90
|
+
.replace(TOKEN_FIELD_RE, (_, name) => `${name}: [REDACTED_TOKEN]`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function collectAuthOutput(result) {
|
|
94
|
+
return redactAuthOutput([result?.stdout, result?.stderr].filter(Boolean).join('\n'));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function extractAuthStartDetails(output) {
|
|
98
|
+
const text = redactAuthOutput(output);
|
|
99
|
+
const urls = [...new Set([...text.matchAll(/https?:\/\/[^\s<>)"']+/g)].map(match => match[0].replace(/[.,;:!?]+$/, '')))];
|
|
100
|
+
|
|
101
|
+
const codePatterns = [/\bone-time code\s*[:=]\s*([A-Z0-9][A-Z0-9-]{3,})/i, /\b(?:user code|verification code|code)\s*[:=]\s*([A-Z0-9][A-Z0-9-]{3,})/i, /\b([A-Z0-9]{4,}-[A-Z0-9-]{4,})\b/];
|
|
102
|
+
let code = null;
|
|
103
|
+
for (const pattern of codePatterns) {
|
|
104
|
+
const match = text.match(pattern);
|
|
105
|
+
if (match) {
|
|
106
|
+
code = match[1].toUpperCase();
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { urls, code };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function formatAuthStatusMessage(provider, result) {
|
|
115
|
+
const code = result?.code;
|
|
116
|
+
const ok = code === 0;
|
|
117
|
+
const output = trimOutput(collectAuthOutput(result)) || '(no output)';
|
|
118
|
+
return `${ok ? 'OK' : 'ERROR'} *${provider} auth status*\n\nExit code: ${code ?? 'unknown'}\n\n\`\`\`\n${escapeCodeFence(output)}\n\`\`\``;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function formatAuthLoginMessage(provider, result) {
|
|
122
|
+
const output = collectAuthOutput(result);
|
|
123
|
+
const details = extractAuthStartDetails(output);
|
|
124
|
+
const lines = [`*${provider} auth login started*`, '', 'The local login command was cancelled locally after capturing the browser step, so this bot command did not replace existing credentials.'];
|
|
125
|
+
|
|
126
|
+
if (details.urls.length > 0) {
|
|
127
|
+
lines.push('', 'Open this URL:');
|
|
128
|
+
for (const url of details.urls) lines.push(url);
|
|
129
|
+
}
|
|
130
|
+
if (details.code) {
|
|
131
|
+
lines.push('', `Code: \`${details.code}\``);
|
|
132
|
+
}
|
|
133
|
+
if (details.urls.length === 0 && !details.code) {
|
|
134
|
+
const shownOutput = trimOutput(output) || '(no output captured)';
|
|
135
|
+
lines.push('', 'Captured output:', '', '```', escapeCodeFence(shownOutput), '```');
|
|
136
|
+
}
|
|
137
|
+
if (result?.cancelled) {
|
|
138
|
+
lines.push('', 'Status: cancelled locally after capture.');
|
|
139
|
+
} else if (typeof result?.code === 'number') {
|
|
140
|
+
lines.push('', `Status: login command exited with code ${result.code}.`);
|
|
141
|
+
}
|
|
142
|
+
lines.push('', 'Continuation by replying with a provider code is not automated yet; this is the first experimental CLI-backed /auth path.');
|
|
143
|
+
return lines.join('\n');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const resolveAllowedAuthChatIds = allowedChats => {
|
|
147
|
+
if (!allowedChats) return [];
|
|
148
|
+
const raw = typeof allowedChats === 'function' ? allowedChats() : allowedChats;
|
|
149
|
+
if (!Array.isArray(raw)) return [];
|
|
150
|
+
return raw.map(value => String(value)).filter(Boolean);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export async function isAuthOperator({ telegram, userId, allowedChatIds }) {
|
|
154
|
+
if (!telegram || !userId || !allowedChatIds || allowedChatIds.length === 0) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
for (const chatId of allowedChatIds) {
|
|
158
|
+
if (String(chatId) === String(userId)) return true;
|
|
159
|
+
try {
|
|
160
|
+
const member = await telegram.getChatMember(chatId, userId);
|
|
161
|
+
if (member?.status === 'creator') return true;
|
|
162
|
+
} catch {
|
|
163
|
+
// Try the next configured chat. The bot may no longer be a member.
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function runAuthCommand(command, args, options = {}) {
|
|
170
|
+
const { mode = 'status', loginCaptureMs = 15000, outputLimit = 20000, env = process.env } = options;
|
|
171
|
+
return new Promise(resolve => {
|
|
172
|
+
const child = spawn(command, args, {
|
|
173
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
174
|
+
env,
|
|
175
|
+
});
|
|
176
|
+
let stdout = '';
|
|
177
|
+
let stderr = '';
|
|
178
|
+
let settled = false;
|
|
179
|
+
let captureTimer = null;
|
|
180
|
+
|
|
181
|
+
const settle = result => {
|
|
182
|
+
if (settled) return;
|
|
183
|
+
settled = true;
|
|
184
|
+
if (captureTimer) clearTimeout(captureTimer);
|
|
185
|
+
resolve({
|
|
186
|
+
stdout: stdout.slice(0, outputLimit),
|
|
187
|
+
stderr: stderr.slice(0, outputLimit),
|
|
188
|
+
...result,
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const maybeCancelLogin = () => {
|
|
193
|
+
if (mode !== 'login' || settled) return;
|
|
194
|
+
const details = extractAuthStartDetails(`${stdout}\n${stderr}`);
|
|
195
|
+
if (details.urls.length === 0 && !details.code) return;
|
|
196
|
+
child.kill('SIGTERM');
|
|
197
|
+
settle({ code: null, signal: 'SIGTERM', cancelled: true });
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
child.stdout.on('data', data => {
|
|
201
|
+
stdout += data.toString();
|
|
202
|
+
maybeCancelLogin();
|
|
203
|
+
});
|
|
204
|
+
child.stderr.on('data', data => {
|
|
205
|
+
stderr += data.toString();
|
|
206
|
+
maybeCancelLogin();
|
|
207
|
+
});
|
|
208
|
+
child.on('error', error => {
|
|
209
|
+
settle({ code: null, error: error.message });
|
|
210
|
+
});
|
|
211
|
+
child.on('close', (code, signal) => {
|
|
212
|
+
settle({ code, signal, cancelled: false });
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (mode === 'login') {
|
|
216
|
+
captureTimer = setTimeout(() => {
|
|
217
|
+
child.kill('SIGTERM');
|
|
218
|
+
settle({ code: null, signal: 'SIGTERM', cancelled: true });
|
|
219
|
+
}, loginCaptureMs);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function registerAuthCommand(bot, options = {}) {
|
|
225
|
+
const { VERBOSE = false, isOldMessage, isForwardedOrReply, allowedChats, authEnabled = true } = options;
|
|
226
|
+
const execute = options.runCommand || runAuthCommand;
|
|
227
|
+
const reply = options.safeReply || ((ctx, text, replyOptions) => ctx.reply(text, replyOptions));
|
|
228
|
+
|
|
229
|
+
async function handleAuthCommand(ctx) {
|
|
230
|
+
VERBOSE && console.log('[VERBOSE] /auth command received');
|
|
231
|
+
|
|
232
|
+
if (isOldMessage && isOldMessage(ctx)) {
|
|
233
|
+
VERBOSE && console.log('[VERBOSE] /auth ignored: old message');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (isForwardedOrReply && isForwardedOrReply(ctx)) {
|
|
237
|
+
VERBOSE && console.log('[VERBOSE] /auth ignored: forwarded or reply');
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (!authEnabled) {
|
|
241
|
+
await reply(ctx, 'The /auth command is disabled on this bot instance.', { reply_to_message_id: ctx.message?.message_id });
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (!ctx.chat || !ctx.from || !ctx.message) return;
|
|
245
|
+
if (ctx.chat.type !== 'private') {
|
|
246
|
+
await reply(ctx, 'The /auth command is only available in private messages.', { reply_to_message_id: ctx.message.message_id });
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const allowedChatIds = resolveAllowedAuthChatIds(allowedChats);
|
|
251
|
+
if (allowedChatIds.length === 0) {
|
|
252
|
+
await reply(ctx, 'The /auth command is disabled because TELEGRAM_ALLOWED_CHATS is not configured.', { reply_to_message_id: ctx.message.message_id });
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const authorized = await isAuthOperator({ telegram: ctx.telegram, userId: ctx.from.id, allowedChatIds });
|
|
257
|
+
if (!authorized) {
|
|
258
|
+
VERBOSE && console.log(`[VERBOSE] /auth denied: user ${ctx.from.id} is not creator of any allowed chat`);
|
|
259
|
+
await reply(ctx, 'The /auth command is only available to owners of allowlisted chats.', { reply_to_message_id: ctx.message.message_id });
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const request = parseAuthRequest(ctx.message.text || '');
|
|
264
|
+
if (request.error) {
|
|
265
|
+
await reply(ctx, request.error, { reply_to_message_id: ctx.message.message_id });
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const { command, args } = buildAuthCommand(request.action, request.provider);
|
|
270
|
+
let result;
|
|
271
|
+
try {
|
|
272
|
+
result = await execute(command, args, { mode: request.action, provider: request.provider });
|
|
273
|
+
} catch (error) {
|
|
274
|
+
await reply(ctx, `Failed to run ${request.provider} auth ${request.action}: ${error.message || String(error)}`, { reply_to_message_id: ctx.message.message_id });
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const message = request.action === 'status' ? formatAuthStatusMessage(request.provider, result) : formatAuthLoginMessage(request.provider, result);
|
|
279
|
+
await reply(ctx, message, { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
bot.command('auth', handleAuthCommand);
|
|
283
|
+
return { handleAuthCommand };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export default {
|
|
287
|
+
AUTH_PROVIDERS,
|
|
288
|
+
buildAuthCommand,
|
|
289
|
+
extractAuthStartDetails,
|
|
290
|
+
formatAuthLoginMessage,
|
|
291
|
+
formatAuthStatusMessage,
|
|
292
|
+
isAuthOperator,
|
|
293
|
+
parseAuthRequest,
|
|
294
|
+
redactAuthOutput,
|
|
295
|
+
registerAuthCommand,
|
|
296
|
+
resolveAllowedAuthChatIds,
|
|
297
|
+
runAuthCommand,
|
|
298
|
+
};
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -100,6 +100,11 @@ const config = yargs(hideBin(process.argv))
|
|
|
100
100
|
description: 'Enable /task and /split commands (use --no-task to disable)',
|
|
101
101
|
default: getenv('TELEGRAM_TASK', 'true') !== 'false',
|
|
102
102
|
})
|
|
103
|
+
.option('auth', {
|
|
104
|
+
type: 'boolean',
|
|
105
|
+
description: 'Enable experimental private /auth command for allowlisted chat owners (use --no-auth to disable)',
|
|
106
|
+
default: getenv('TELEGRAM_AUTH', 'true') !== 'false',
|
|
107
|
+
})
|
|
103
108
|
.option('dryRun', {
|
|
104
109
|
type: 'boolean',
|
|
105
110
|
description: 'Validate configuration and options without starting the bot',
|
|
@@ -163,6 +168,7 @@ const hiveOverrides = resolvedHiveOverrides
|
|
|
163
168
|
const solveEnabled = config.solve;
|
|
164
169
|
const hiveEnabled = config.hive;
|
|
165
170
|
const taskEnabled = config.task;
|
|
171
|
+
const authEnabled = config.auth;
|
|
166
172
|
// Isolation mode (experimental): uses `$` from start-command with specified backend
|
|
167
173
|
const ISOLATION_BACKEND = (config.isolation || getenv('TELEGRAM_ISOLATION', '')).trim().toLowerCase();
|
|
168
174
|
let isolationRunner = null;
|
|
@@ -283,7 +289,7 @@ if (config.dryRun) {
|
|
|
283
289
|
if (allowedTopics && allowedTopics.length > 0) {
|
|
284
290
|
console.log(' Allowed topics:', lino.formatLinks(allowedTopics));
|
|
285
291
|
}
|
|
286
|
-
console.log(' Commands enabled:', { solve: solveEnabled, hive: hiveEnabled, task: taskEnabled });
|
|
292
|
+
console.log(' Commands enabled:', { solve: solveEnabled, hive: hiveEnabled, task: taskEnabled, auth: authEnabled });
|
|
287
293
|
if (solveOverrides.length > 0) {
|
|
288
294
|
console.log(' Solve overrides:', lino.format(solveOverrides));
|
|
289
295
|
}
|
|
@@ -606,6 +612,8 @@ const { registerSubscribeCommands } = await import('./telegram-subscribers.lib.m
|
|
|
606
612
|
registerSubscribeCommands(bot, sharedCommandOpts);
|
|
607
613
|
const { registerTaskCommands } = await import('./telegram-task-command.lib.mjs');
|
|
608
614
|
const { handleTaskCommand, TASK_COMMAND_NAMES } = registerTaskCommands(bot, { ...sharedCommandOpts, taskEnabled, safeReply, executeAndUpdateMessage, resolveLocale: resolveLocaleFromTelegramCtx });
|
|
615
|
+
const { registerAuthCommand } = await import('./telegram-auth-command.lib.mjs');
|
|
616
|
+
const { handleAuthCommand } = registerAuthCommand(bot, { ...sharedCommandOpts, allowedChats, authEnabled, safeReply });
|
|
609
617
|
|
|
610
618
|
// Named handler for /solve command - extracted for reuse by text-based fallback (issue #1207)
|
|
611
619
|
async function handleSolveCommand(ctx) {
|
|
@@ -1170,7 +1178,7 @@ bot.on('message', async (ctx, next) => {
|
|
|
1170
1178
|
const solveHandlers = Object.fromEntries(SOLVE_COMMAND_NAMES.map(command => [command, handleSolveCommand]));
|
|
1171
1179
|
const taskHandlers = Object.fromEntries(TASK_COMMAND_NAMES.map(command => [command, handleTaskCommand]));
|
|
1172
1180
|
// /queue is the short alias for /solve_queue (issue #1837)
|
|
1173
|
-
const handlers = { ...solveHandlers, ...taskHandlers, hive: handleHiveCommand, solve_queue: handleSolveQueueCommand, solvequeue: handleSolveQueueCommand, queue: handleSolveQueueCommand };
|
|
1181
|
+
const handlers = { ...solveHandlers, ...taskHandlers, auth: handleAuthCommand, hive: handleHiveCommand, solve_queue: handleSolveQueueCommand, solvequeue: handleSolveQueueCommand, queue: handleSolveQueueCommand };
|
|
1174
1182
|
|
|
1175
1183
|
const handler = handlers[extracted.command];
|
|
1176
1184
|
if (!handler) return next();
|
|
@@ -1279,7 +1287,7 @@ if (allowedChats && allowedChats.length > 0) {
|
|
|
1279
1287
|
if (allowedTopics && allowedTopics.length > 0) {
|
|
1280
1288
|
console.log('Allowed topics (lino):', lino.formatLinks(allowedTopics));
|
|
1281
1289
|
}
|
|
1282
|
-
console.log('Commands enabled:', { solve: solveEnabled, hive: hiveEnabled, task: taskEnabled });
|
|
1290
|
+
console.log('Commands enabled:', { solve: solveEnabled, hive: hiveEnabled, task: taskEnabled, auth: authEnabled });
|
|
1283
1291
|
if (solveOverrides.length > 0) console.log('Solve overrides (lino):', lino.format(solveOverrides));
|
|
1284
1292
|
if (hiveOverrides.length > 0) console.log('Hive overrides (lino):', lino.format(hiveOverrides));
|
|
1285
1293
|
if (VERBOSE) {
|