@link-assistant/hive-mind 1.42.0 → 1.44.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.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/lino.lib.mjs +42 -0
- package/src/telegram-accept-invitations.lib.mjs +8 -6
- package/src/telegram-bot.mjs +61 -59
- package/src/telegram-merge-command.lib.mjs +11 -6
- package/src/telegram-solve-queue-command.lib.mjs +8 -7
- package/src/telegram-top-command.lib.mjs +10 -5
- package/src/version-info.lib.mjs +458 -63
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.44.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e7ce2dd: Add TELEGRAM_ALLOWED_TOPICS for forum topic filtering (issue #1100)
|
|
8
|
+
|
|
9
|
+
## 1.43.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 91479e3: Better /version command output with uniform formatting and bug fixes: add regex version parsers for all 40+ tools, fix LLD/Xvfb/Playwright MCP detection, add Playwright browser cache fallback, fail Docker build on MCP registration failure
|
|
14
|
+
|
|
3
15
|
## 1.42.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/package.json
CHANGED
package/src/lino.lib.mjs
CHANGED
|
@@ -96,6 +96,48 @@ export class LinksNotationManager {
|
|
|
96
96
|
return [];
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
parseLinks(input) {
|
|
100
|
+
if (!input) return [];
|
|
101
|
+
|
|
102
|
+
const parsed = this.parser.parse(input);
|
|
103
|
+
if (!parsed || parsed.length === 0) return [];
|
|
104
|
+
|
|
105
|
+
const link = parsed[0];
|
|
106
|
+
const pairs = [];
|
|
107
|
+
|
|
108
|
+
if (link.values && link.values.length > 0) {
|
|
109
|
+
const flatNumbers = [];
|
|
110
|
+
|
|
111
|
+
for (const value of link.values) {
|
|
112
|
+
if (value.id === null && value.values && value.values.length >= 2) {
|
|
113
|
+
const source = parseInt(value.values[0]?.id || value.values[0], 10);
|
|
114
|
+
const target = parseInt(value.values[1]?.id || value.values[1], 10);
|
|
115
|
+
if (!isNaN(source) && !isNaN(target)) {
|
|
116
|
+
pairs.push({ source, target });
|
|
117
|
+
}
|
|
118
|
+
} else if (value.id) {
|
|
119
|
+
const num = parseInt(value.id, 10);
|
|
120
|
+
if (!isNaN(num)) {
|
|
121
|
+
flatNumbers.push(num);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (let i = 0; i < flatNumbers.length - 1; i += 2) {
|
|
127
|
+
pairs.push({ source: flatNumbers[i], target: flatNumbers[i + 1] });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return pairs;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
formatLinks(pairs) {
|
|
135
|
+
if (!pairs || pairs.length === 0) return '()';
|
|
136
|
+
|
|
137
|
+
const formattedValues = pairs.map(pair => ` ${pair.source} ${pair.target}`).join('\n');
|
|
138
|
+
return `(\n${formattedValues}\n)`;
|
|
139
|
+
}
|
|
140
|
+
|
|
99
141
|
format(values) {
|
|
100
142
|
if (!values || values.length === 0) return '()';
|
|
101
143
|
|
|
@@ -116,10 +116,12 @@ function buildProgressMessage(state) {
|
|
|
116
116
|
* @param {Function} options.isForwardedOrReply - Function to check if message is forwarded/reply
|
|
117
117
|
* @param {Function} options.isGroupChat - Function to check if chat is a group
|
|
118
118
|
* @param {Function} options.isChatAuthorized - Function to check if chat is authorized
|
|
119
|
+
* @param {Function} [options.isTopicAuthorized] - Function to check if topic is authorized (issue #1100)
|
|
120
|
+
* @param {Function} [options.buildAuthErrorMessage] - Function to build authorization error message
|
|
119
121
|
* @param {Function} options.addBreadcrumb - Function to add breadcrumbs for monitoring
|
|
120
122
|
*/
|
|
121
123
|
export function registerAcceptInvitesCommand(bot, options) {
|
|
122
|
-
const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized, addBreadcrumb } = options;
|
|
124
|
+
const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized, isTopicAuthorized, buildAuthErrorMessage, addBreadcrumb } = options;
|
|
123
125
|
|
|
124
126
|
bot.command(/^accept[_-]?invites$/i, async ctx => {
|
|
125
127
|
VERBOSE && console.log('[VERBOSE] /accept_invites command received');
|
|
@@ -134,11 +136,11 @@ export function registerAcceptInvitesCommand(bot, options) {
|
|
|
134
136
|
return await ctx.reply('❌ The /accept_invites command only works in group chats. Please add this bot to a group and make it an admin.', {
|
|
135
137
|
reply_to_message_id: ctx.message.message_id,
|
|
136
138
|
});
|
|
137
|
-
const
|
|
138
|
-
if (!
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
const authorize = isTopicAuthorized || (ctx => isChatAuthorized(ctx.chat.id));
|
|
140
|
+
if (!authorize(ctx)) {
|
|
141
|
+
const errMsg = buildAuthErrorMessage ? buildAuthErrorMessage(ctx) : `❌ This chat (ID: ${ctx.chat.id}) is not authorized.`;
|
|
142
|
+
return await ctx.reply(errMsg, { reply_to_message_id: ctx.message.message_id });
|
|
143
|
+
}
|
|
142
144
|
|
|
143
145
|
const fetchingMessage = await ctx.reply('🔄 Fetching pending GitHub invitations\\.\\.\\.', {
|
|
144
146
|
reply_to_message_id: ctx.message.message_id,
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -78,6 +78,12 @@ const config = yargs(hideBin(process.argv))
|
|
|
78
78
|
alias: 'allowed-chats',
|
|
79
79
|
default: getenv('TELEGRAM_ALLOWED_CHATS', ''),
|
|
80
80
|
})
|
|
81
|
+
.option('allowedTopics', {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: 'Allowed topic IDs in Links Notation format "chatId topicId" pairs',
|
|
84
|
+
alias: 'allowed-topics',
|
|
85
|
+
default: getenv('TELEGRAM_ALLOWED_TOPICS', ''),
|
|
86
|
+
})
|
|
81
87
|
.option('solveOverrides', {
|
|
82
88
|
type: 'string',
|
|
83
89
|
description: 'Override options for /solve command in lino notation, e.g., "(\n --auto-continue\n --attach-logs\n)"',
|
|
@@ -154,6 +160,10 @@ if (!BOT_TOKEN) {
|
|
|
154
160
|
const resolvedAllowedChats = config.allowedChats || getenv('TELEGRAM_ALLOWED_CHATS', '');
|
|
155
161
|
const allowedChats = resolvedAllowedChats ? lino.parseNumericIds(resolvedAllowedChats) : null;
|
|
156
162
|
|
|
163
|
+
// Parse allowed topics (chatId:topicId pairs in Links Notation)
|
|
164
|
+
const resolvedAllowedTopics = config.allowedTopics || getenv('TELEGRAM_ALLOWED_TOPICS', '');
|
|
165
|
+
const allowedTopics = resolvedAllowedTopics ? lino.parseLinks(resolvedAllowedTopics) : null;
|
|
166
|
+
|
|
157
167
|
// Parse override options
|
|
158
168
|
const resolvedSolveOverrides = config.solveOverrides || getenv('TELEGRAM_SOLVE_OVERRIDES', '');
|
|
159
169
|
const solveOverrides = resolvedSolveOverrides
|
|
@@ -277,6 +287,9 @@ if (config.dryRun) {
|
|
|
277
287
|
} else {
|
|
278
288
|
console.log(' Allowed chats: All (no restrictions)');
|
|
279
289
|
}
|
|
290
|
+
if (allowedTopics && allowedTopics.length > 0) {
|
|
291
|
+
console.log(' Allowed topics:', lino.formatLinks(allowedTopics));
|
|
292
|
+
}
|
|
280
293
|
console.log(' Commands enabled:', { solve: solveEnabled, hive: hiveEnabled });
|
|
281
294
|
if (solveOverrides.length > 0) {
|
|
282
295
|
console.log(' Solve overrides:', lino.format(solveOverrides));
|
|
@@ -319,6 +332,22 @@ function isChatAuthorized(chatId) {
|
|
|
319
332
|
return _isChatAuthorized(chatId, allowedChats);
|
|
320
333
|
}
|
|
321
334
|
|
|
335
|
+
// Topic-level authorization (issue #1100): chat-level auth overrides topic-level
|
|
336
|
+
function isTopicAuthorized(ctx) {
|
|
337
|
+
if (isChatAuthorized(ctx.chat?.id)) return true;
|
|
338
|
+
if (!allowedTopics || allowedTopics.length === 0) return false;
|
|
339
|
+
const chatId = ctx.chat?.id;
|
|
340
|
+
const topicId = ctx.message?.message_thread_id;
|
|
341
|
+
return allowedTopics.some(pair => pair.source === chatId && pair.target === topicId);
|
|
342
|
+
}
|
|
343
|
+
function buildAuthErrorMessage(ctx) {
|
|
344
|
+
const chatId = ctx.chat?.id;
|
|
345
|
+
const topicId = ctx.message?.message_thread_id;
|
|
346
|
+
let msg = `❌ This chat (ID: ${chatId})`;
|
|
347
|
+
if (topicId) msg += ` and topic (ID: ${topicId})`;
|
|
348
|
+
return msg + ' is not authorized.\n\nUse /help to see your chat and topic IDs.';
|
|
349
|
+
}
|
|
350
|
+
|
|
322
351
|
function isOldMessage(ctx) {
|
|
323
352
|
return _isOldMessage(ctx, BOT_START_TIME, { verbose: VERBOSE });
|
|
324
353
|
}
|
|
@@ -621,7 +650,7 @@ bot.command('help', async ctx => {
|
|
|
621
650
|
const chatId = ctx.chat.id;
|
|
622
651
|
const chatType = ctx.chat.type;
|
|
623
652
|
const chatTitle = ctx.chat.title || 'Private Chat';
|
|
624
|
-
|
|
653
|
+
const topicId = ctx.message?.message_thread_id; // Forum topic ID (issue #1100)
|
|
625
654
|
let message = '🤖 *SwarmMindBot Help*\n\n';
|
|
626
655
|
|
|
627
656
|
// Show stopped status if chat is stopped (issue #1081)
|
|
@@ -638,6 +667,7 @@ bot.command('help', async ctx => {
|
|
|
638
667
|
|
|
639
668
|
message += '📋 *Diagnostic Information:*\n';
|
|
640
669
|
message += `• Chat ID: \`${chatId}\`\n`;
|
|
670
|
+
if (topicId) message += `• Topic ID: \`${topicId}\`\n`;
|
|
641
671
|
message += `• Chat Type: ${chatType}\n`;
|
|
642
672
|
message += `• Chat Title: ${chatTitle}\n\n`;
|
|
643
673
|
message += '📝 *Available Commands:*\n\n';
|
|
@@ -685,9 +715,10 @@ bot.command('help', async ctx => {
|
|
|
685
715
|
message += '• `--verbose` or `-v` - Verbose output | `--attach-logs` - Attach logs to PR\n';
|
|
686
716
|
message += '\n💡 *Tip:* Many more options available. See full documentation for complete list.\n';
|
|
687
717
|
|
|
688
|
-
if (allowedChats) {
|
|
689
|
-
|
|
690
|
-
message +=
|
|
718
|
+
if (allowedChats || allowedTopics) {
|
|
719
|
+
const authorized = isTopicAuthorized(ctx);
|
|
720
|
+
message += `\n🔒 *Restricted Mode:* Authorized: ${authorized ? '✅ Yes' : '❌ No'}`;
|
|
721
|
+
if (!authorized && topicId) message += `\n💡 To allow this topic: \`TELEGRAM_ALLOWED_TOPICS="(${chatId} ${topicId})"\``;
|
|
691
722
|
}
|
|
692
723
|
|
|
693
724
|
message += '\n\n🔧 *Troubleshooting:*\n';
|
|
@@ -728,10 +759,11 @@ bot.command('limits', async ctx => {
|
|
|
728
759
|
return;
|
|
729
760
|
}
|
|
730
761
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
762
|
+
if (!isTopicAuthorized(ctx)) {
|
|
763
|
+
if (VERBOSE) {
|
|
764
|
+
console.log('[VERBOSE] /limits ignored: not authorized');
|
|
765
|
+
}
|
|
766
|
+
await ctx.reply(buildAuthErrorMessage(ctx), { reply_to_message_id: ctx.message.message_id });
|
|
735
767
|
return;
|
|
736
768
|
}
|
|
737
769
|
|
|
@@ -768,8 +800,7 @@ bot.command('version', async ctx => {
|
|
|
768
800
|
});
|
|
769
801
|
if (isOldMessage(ctx) || isForwardedOrReply(ctx)) return;
|
|
770
802
|
if (!_isGroupChat(ctx)) return await ctx.reply('❌ The /version command only works in group chats. Please add this bot to a group and make it an admin.', { reply_to_message_id: ctx.message.message_id });
|
|
771
|
-
|
|
772
|
-
if (!isChatAuthorized(chatId)) return await ctx.reply(`❌ This chat (ID: ${chatId}) is not authorized to use this bot. Please contact the bot administrator.`, { reply_to_message_id: ctx.message.message_id });
|
|
803
|
+
if (!isTopicAuthorized(ctx)) return await ctx.reply(buildAuthErrorMessage(ctx), { reply_to_message_id: ctx.message.message_id });
|
|
773
804
|
const fetchingMessage = await ctx.reply('🔄 Gathering version information...', {
|
|
774
805
|
reply_to_message_id: ctx.message.message_id,
|
|
775
806
|
});
|
|
@@ -778,48 +809,18 @@ bot.command('version', async ctx => {
|
|
|
778
809
|
await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, '🤖 *Version Information*\n\n' + formatVersionMessage(result.versions), { parse_mode: 'Markdown' });
|
|
779
810
|
});
|
|
780
811
|
|
|
781
|
-
// Register
|
|
782
|
-
// This keeps telegram-bot.mjs under the 1500 line limit
|
|
812
|
+
// Register external command modules (keeps telegram-bot.mjs under line limit)
|
|
783
813
|
const { registerAcceptInvitesCommand } = await import('./telegram-accept-invitations.lib.mjs');
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
isOldMessage,
|
|
787
|
-
isForwardedOrReply,
|
|
788
|
-
isGroupChat: _isGroupChat,
|
|
789
|
-
isChatAuthorized,
|
|
790
|
-
addBreadcrumb,
|
|
791
|
-
});
|
|
792
|
-
|
|
793
|
-
// Register /merge command from separate module (experimental, see issue #1143)
|
|
814
|
+
const sharedCommandOpts = { VERBOSE, isOldMessage, isForwardedOrReply, isGroupChat: _isGroupChat, isChatAuthorized, isTopicAuthorized, buildAuthErrorMessage, addBreadcrumb, isChatStopped, getStoppedChatRejectMessage };
|
|
815
|
+
registerAcceptInvitesCommand(bot, sharedCommandOpts);
|
|
794
816
|
const { registerMergeCommand } = await import('./telegram-merge-command.lib.mjs');
|
|
795
|
-
registerMergeCommand(bot,
|
|
796
|
-
VERBOSE,
|
|
797
|
-
isOldMessage,
|
|
798
|
-
isForwardedOrReply,
|
|
799
|
-
isGroupChat: _isGroupChat,
|
|
800
|
-
isChatAuthorized,
|
|
801
|
-
addBreadcrumb,
|
|
802
|
-
isChatStopped,
|
|
803
|
-
getStoppedChatRejectMessage,
|
|
804
|
-
});
|
|
805
|
-
|
|
806
|
-
// Register /solve_queue command from separate module (issue #1232)
|
|
817
|
+
registerMergeCommand(bot, sharedCommandOpts);
|
|
807
818
|
const { registerSolveQueueCommand } = await import('./telegram-solve-queue-command.lib.mjs');
|
|
808
|
-
const { handleSolveQueueCommand } = registerSolveQueueCommand(bot, {
|
|
809
|
-
VERBOSE,
|
|
810
|
-
isOldMessage,
|
|
811
|
-
isForwardedOrReply,
|
|
812
|
-
isGroupChat: _isGroupChat,
|
|
813
|
-
isChatAuthorized,
|
|
814
|
-
addBreadcrumb,
|
|
815
|
-
getSolveQueue,
|
|
816
|
-
});
|
|
819
|
+
const { handleSolveQueueCommand } = registerSolveQueueCommand(bot, { ...sharedCommandOpts, getSolveQueue });
|
|
817
820
|
|
|
818
821
|
// Named handler for /solve command - extracted for reuse by text-based fallback (issue #1207)
|
|
819
822
|
async function handleSolveCommand(ctx) {
|
|
820
|
-
|
|
821
|
-
console.log('[VERBOSE] /solve command received');
|
|
822
|
-
}
|
|
823
|
+
VERBOSE && console.log('[VERBOSE] /solve command received');
|
|
823
824
|
|
|
824
825
|
// Add breadcrumb for error tracking
|
|
825
826
|
await addBreadcrumb({
|
|
@@ -871,16 +872,16 @@ async function handleSolveCommand(ctx) {
|
|
|
871
872
|
return;
|
|
872
873
|
}
|
|
873
874
|
|
|
874
|
-
|
|
875
|
-
if (!isChatAuthorized(chatId)) {
|
|
875
|
+
if (!isTopicAuthorized(ctx)) {
|
|
876
876
|
if (VERBOSE) {
|
|
877
|
-
console.log('[VERBOSE] /solve ignored:
|
|
877
|
+
console.log('[VERBOSE] /solve ignored: not authorized');
|
|
878
878
|
}
|
|
879
|
-
await ctx.reply(
|
|
879
|
+
await ctx.reply(buildAuthErrorMessage(ctx), { reply_to_message_id: ctx.message.message_id });
|
|
880
880
|
return;
|
|
881
881
|
}
|
|
882
882
|
|
|
883
883
|
// Check if chat is stopped (issue #1081) - reject with same style as queue rejected mode
|
|
884
|
+
const chatId = ctx.chat.id;
|
|
884
885
|
if (isChatStopped(chatId)) {
|
|
885
886
|
VERBOSE && console.log('[VERBOSE] /solve rejected: chat is stopped');
|
|
886
887
|
await safeReply(ctx, getStoppedChatRejectMessage(chatId, 'Solve'), { reply_to_message_id: ctx.message.message_id });
|
|
@@ -1017,7 +1018,7 @@ async function handleSolveCommand(ctx) {
|
|
|
1017
1018
|
const existingItem = solveQueue.findByUrl(normalizedUrl);
|
|
1018
1019
|
if (existingItem) {
|
|
1019
1020
|
const statusText = existingItem.status === 'starting' || existingItem.status === 'started' ? 'being processed' : 'already in the queue';
|
|
1020
|
-
await safeReply(ctx, `❌ This URL is ${statusText}.\n\nURL: ${escapeMarkdown(normalizedUrl)}\nStatus: ${existingItem.status}\n\n💡 Use /
|
|
1021
|
+
await safeReply(ctx, `❌ This URL is ${statusText}.\n\nURL: ${escapeMarkdown(normalizedUrl)}\nStatus: ${existingItem.status}\n\n💡 Use /solve_queue to check the queue status.`, { reply_to_message_id: ctx.message.message_id });
|
|
1021
1022
|
return;
|
|
1022
1023
|
}
|
|
1023
1024
|
|
|
@@ -1099,16 +1100,16 @@ async function handleHiveCommand(ctx) {
|
|
|
1099
1100
|
return;
|
|
1100
1101
|
}
|
|
1101
1102
|
|
|
1102
|
-
|
|
1103
|
-
if (!isChatAuthorized(chatId)) {
|
|
1103
|
+
if (!isTopicAuthorized(ctx)) {
|
|
1104
1104
|
if (VERBOSE) {
|
|
1105
|
-
console.log('[VERBOSE] /hive ignored:
|
|
1105
|
+
console.log('[VERBOSE] /hive ignored: not authorized');
|
|
1106
1106
|
}
|
|
1107
|
-
await ctx.reply(
|
|
1107
|
+
await ctx.reply(buildAuthErrorMessage(ctx), { reply_to_message_id: ctx.message.message_id });
|
|
1108
1108
|
return;
|
|
1109
1109
|
}
|
|
1110
1110
|
|
|
1111
1111
|
// Check if chat is stopped (issue #1081) - reject with same style as queue rejected mode
|
|
1112
|
+
const chatId = ctx.chat.id;
|
|
1112
1113
|
if (isChatStopped(chatId)) {
|
|
1113
1114
|
VERBOSE && console.log('[VERBOSE] /hive rejected: chat is stopped');
|
|
1114
1115
|
await safeReply(ctx, getStoppedChatRejectMessage(chatId, 'Hive'), { reply_to_message_id: ctx.message.message_id });
|
|
@@ -1201,12 +1202,10 @@ async function handleHiveCommand(ctx) {
|
|
|
1201
1202
|
|
|
1202
1203
|
bot.command(/^hive$/i, handleHiveCommand);
|
|
1203
1204
|
|
|
1204
|
-
// Register commands from separate modules (keeps telegram-bot.mjs under line limit)
|
|
1205
1205
|
const { registerTopCommand } = await import('./telegram-top-command.lib.mjs');
|
|
1206
1206
|
const { registerStartStopCommands } = await import('./telegram-start-stop-command.lib.mjs');
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
registerStartStopCommands(bot, commandOptions); // issue #1081
|
|
1207
|
+
registerTopCommand(bot, sharedCommandOpts);
|
|
1208
|
+
registerStartStopCommands(bot, sharedCommandOpts);
|
|
1210
1209
|
|
|
1211
1210
|
// Add message listener for verbose debugging
|
|
1212
1211
|
if (VERBOSE) {
|
|
@@ -1389,6 +1388,9 @@ if (allowedChats && allowedChats.length > 0) {
|
|
|
1389
1388
|
} else {
|
|
1390
1389
|
console.log('Allowed chats: All (no restrictions)');
|
|
1391
1390
|
}
|
|
1391
|
+
if (allowedTopics && allowedTopics.length > 0) {
|
|
1392
|
+
console.log('Allowed topics (lino):', lino.formatLinks(allowedTopics));
|
|
1393
|
+
}
|
|
1392
1394
|
console.log('Commands enabled:', { solve: solveEnabled, hive: hiveEnabled });
|
|
1393
1395
|
if (solveOverrides.length > 0) console.log('Solve overrides (lino):', lino.format(solveOverrides));
|
|
1394
1396
|
if (hiveOverrides.length > 0) console.log('Hive overrides (lino):', lino.format(hiveOverrides));
|
|
@@ -130,10 +130,14 @@ function formatUserError(error, verbose) {
|
|
|
130
130
|
* @param {Function} options.isForwardedOrReply - Function to check if message is forwarded/reply
|
|
131
131
|
* @param {Function} options.isGroupChat - Function to check if chat is a group
|
|
132
132
|
* @param {Function} options.isChatAuthorized - Function to check if chat is authorized
|
|
133
|
+
* @param {Function} [options.isTopicAuthorized] - Function to check if topic is authorized (issue #1100)
|
|
134
|
+
* @param {Function} [options.buildAuthErrorMessage] - Function to build authorization error message
|
|
133
135
|
* @param {Function} options.addBreadcrumb - Function to add breadcrumbs for monitoring
|
|
136
|
+
* @param {Function} [options.isChatStopped] - Function to check if chat is stopped (issue #1081)
|
|
137
|
+
* @param {Function} [options.getStoppedChatRejectMessage] - Function to get stopped chat rejection message
|
|
134
138
|
*/
|
|
135
139
|
export function registerMergeCommand(bot, options) {
|
|
136
|
-
const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized, addBreadcrumb, isChatStopped, getStoppedChatRejectMessage } = options;
|
|
140
|
+
const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized, isTopicAuthorized, buildAuthErrorMessage, addBreadcrumb, isChatStopped, getStoppedChatRejectMessage } = options;
|
|
137
141
|
|
|
138
142
|
bot.command(/^merge$/i, async ctx => {
|
|
139
143
|
VERBOSE && console.log('[VERBOSE] /merge command received');
|
|
@@ -154,13 +158,14 @@ export function registerMergeCommand(bot, options) {
|
|
|
154
158
|
});
|
|
155
159
|
}
|
|
156
160
|
|
|
157
|
-
const
|
|
158
|
-
if (!
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
});
|
|
161
|
+
const authorize = isTopicAuthorized || (ctx => isChatAuthorized(ctx.chat.id));
|
|
162
|
+
if (!authorize(ctx)) {
|
|
163
|
+
const errMsg = buildAuthErrorMessage ? buildAuthErrorMessage(ctx) : `This chat (ID: ${ctx.chat.id}) is not authorized.`;
|
|
164
|
+
return await ctx.reply(errMsg, { reply_to_message_id: ctx.message.message_id });
|
|
162
165
|
}
|
|
163
166
|
|
|
167
|
+
const chatId = ctx.chat.id;
|
|
168
|
+
|
|
164
169
|
// Check if chat is stopped (issue #1081) - reject with same style as queue rejected mode
|
|
165
170
|
if (isChatStopped && isChatStopped(chatId)) {
|
|
166
171
|
VERBOSE && console.log('[VERBOSE] /merge rejected: chat is stopped');
|
|
@@ -22,12 +22,14 @@
|
|
|
22
22
|
* @param {Function} options.isForwardedOrReply - Function to check if message is forwarded/reply
|
|
23
23
|
* @param {Function} options.isGroupChat - Function to check if chat is a group
|
|
24
24
|
* @param {Function} options.isChatAuthorized - Function to check if chat is authorized
|
|
25
|
+
* @param {Function} [options.isTopicAuthorized] - Function to check if topic is authorized (issue #1100)
|
|
26
|
+
* @param {Function} [options.buildAuthErrorMessage] - Function to build authorization error message
|
|
25
27
|
* @param {Function} options.addBreadcrumb - Function to add breadcrumbs for monitoring
|
|
26
28
|
* @param {Function} options.getSolveQueue - Function to get the solve queue instance
|
|
27
29
|
* @returns {{ handleSolveQueueCommand: Function }} The command handler for use in text fallback
|
|
28
30
|
*/
|
|
29
31
|
export function registerSolveQueueCommand(bot, options) {
|
|
30
|
-
const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized, addBreadcrumb, getSolveQueue } = options;
|
|
32
|
+
const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized, isTopicAuthorized, buildAuthErrorMessage, addBreadcrumb, getSolveQueue } = options;
|
|
31
33
|
|
|
32
34
|
async function handleSolveQueueCommand(ctx) {
|
|
33
35
|
VERBOSE && console.log('[VERBOSE] /solve_queue command received');
|
|
@@ -59,12 +61,11 @@ export function registerSolveQueueCommand(bot, options) {
|
|
|
59
61
|
return;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
const
|
|
63
|
-
if (!
|
|
64
|
-
VERBOSE && console.log('[VERBOSE] /solve_queue ignored:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
});
|
|
64
|
+
const authorize = isTopicAuthorized || (ctx => isChatAuthorized(ctx.chat.id));
|
|
65
|
+
if (!authorize(ctx)) {
|
|
66
|
+
VERBOSE && console.log('[VERBOSE] /solve_queue ignored: not authorized');
|
|
67
|
+
const errMsg = buildAuthErrorMessage ? buildAuthErrorMessage(ctx) : `❌ This chat (ID: ${ctx.chat.id}) is not authorized.`;
|
|
68
|
+
await ctx.reply(errMsg, { reply_to_message_id: ctx.message.message_id });
|
|
68
69
|
return;
|
|
69
70
|
}
|
|
70
71
|
|
|
@@ -51,9 +51,11 @@ async function captureTopOutput(chatId) {
|
|
|
51
51
|
* @param {Function} options.isForwardedOrReply - Function to check if message is forwarded/reply
|
|
52
52
|
* @param {Function} options.isGroupChat - Function to check if chat is a group
|
|
53
53
|
* @param {Function} options.isChatAuthorized - Function to check if chat is authorized
|
|
54
|
+
* @param {Function} [options.isTopicAuthorized] - Function to check if topic is authorized (issue #1100)
|
|
55
|
+
* @param {Function} [options.buildAuthErrorMessage] - Function to build authorization error message
|
|
54
56
|
*/
|
|
55
57
|
export function registerTopCommand(bot, options) {
|
|
56
|
-
const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized } = options;
|
|
58
|
+
const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized, isTopicAuthorized, buildAuthErrorMessage } = options;
|
|
57
59
|
|
|
58
60
|
// /top command - show system top output in an auto-updating message (EXPERIMENTAL)
|
|
59
61
|
// Only accessible by chat owner
|
|
@@ -89,17 +91,20 @@ export function registerTopCommand(bot, options) {
|
|
|
89
91
|
return;
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
const
|
|
93
|
-
if (!
|
|
94
|
+
const authorize = isTopicAuthorized || (ctx => isChatAuthorized(ctx.chat.id));
|
|
95
|
+
if (!authorize(ctx)) {
|
|
94
96
|
if (VERBOSE) {
|
|
95
|
-
console.log('[VERBOSE] /top ignored:
|
|
97
|
+
console.log('[VERBOSE] /top ignored: not authorized');
|
|
96
98
|
}
|
|
97
|
-
|
|
99
|
+
const errMsg = buildAuthErrorMessage ? buildAuthErrorMessage(ctx) : `❌ This chat (ID: ${ctx.chat.id}) is not authorized.`;
|
|
100
|
+
await ctx.reply(errMsg, {
|
|
98
101
|
reply_to_message_id: ctx.message.message_id,
|
|
99
102
|
});
|
|
100
103
|
return;
|
|
101
104
|
}
|
|
102
105
|
|
|
106
|
+
const chatId = ctx.chat.id;
|
|
107
|
+
|
|
103
108
|
// Check if user is chat owner
|
|
104
109
|
try {
|
|
105
110
|
const chatMember = await ctx.telegram.getChatMember(chatId, ctx.from.id);
|
package/src/version-info.lib.mjs
CHANGED
|
@@ -33,6 +33,397 @@ async function execCommandAsync(command, timeout = 5000) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Per-tool regex parsers to normalize raw --version output into uniform format:
|
|
38
|
+
* <version> (<commit>, <revision>, <date>, etc.)
|
|
39
|
+
*
|
|
40
|
+
* Each parser returns { version, extra[] } or null if it doesn't match.
|
|
41
|
+
* The `version` is the most specific version string (for bug reporting).
|
|
42
|
+
* Items in `extra` are joined with ", " and placed in parentheses.
|
|
43
|
+
*
|
|
44
|
+
* @type {Record<string, (raw: string) => {version: string, extra: string[]} | null>}
|
|
45
|
+
*/
|
|
46
|
+
const VERSION_PARSERS = {
|
|
47
|
+
// rustc 1.94.1 (e408947bf 2026-03-25)
|
|
48
|
+
rust: raw => {
|
|
49
|
+
const m = raw.match(/^rustc\s+([\d.]+(?:-\S+)?)\s*(?:\(([^)]+)\))?/);
|
|
50
|
+
if (!m) return null;
|
|
51
|
+
const extra = m[2] ? m[2].trim().split(/\s+/) : [];
|
|
52
|
+
return { version: m[1], extra };
|
|
53
|
+
},
|
|
54
|
+
// cargo 1.94.1 (29ea6fb6a 2026-03-24)
|
|
55
|
+
cargo: raw => {
|
|
56
|
+
const m = raw.match(/^cargo\s+([\d.]+(?:-\S+)?)\s*(?:\(([^)]+)\))?/);
|
|
57
|
+
if (!m) return null;
|
|
58
|
+
const extra = m[2] ? m[2].trim().split(/\s+/) : [];
|
|
59
|
+
return { version: m[1], extra };
|
|
60
|
+
},
|
|
61
|
+
// go version go1.26.1 linux/amd64
|
|
62
|
+
go: raw => {
|
|
63
|
+
const m = raw.match(/go([\d.]+(?:\S*)?)\s+(.*)/);
|
|
64
|
+
if (!m) return null;
|
|
65
|
+
return { version: m[1], extra: [m[2].trim()] };
|
|
66
|
+
},
|
|
67
|
+
// PHP 8.3.30 (cli) (built: Jan 13 2026 22:36:55) (NTS)
|
|
68
|
+
php: raw => {
|
|
69
|
+
const m = raw.match(/^PHP\s+([\d.]+(?:-\S+)?)\s*(.*)/);
|
|
70
|
+
if (!m) return null;
|
|
71
|
+
const tags = [];
|
|
72
|
+
const parts = m[2].matchAll(/\(([^)]+)\)/g);
|
|
73
|
+
for (const p of parts) tags.push(p[1]);
|
|
74
|
+
return { version: m[1], extra: tags };
|
|
75
|
+
},
|
|
76
|
+
// openjdk version "21" 2023-09-19 LTS
|
|
77
|
+
java: raw => {
|
|
78
|
+
const m = raw.match(/version\s+"([^"]+)"(?:\s+(.+))?/);
|
|
79
|
+
if (!m) return null;
|
|
80
|
+
return { version: m[1], extra: m[2] ? [m[2].trim()] : [] };
|
|
81
|
+
},
|
|
82
|
+
// gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 — use full distro version
|
|
83
|
+
gcc: raw => {
|
|
84
|
+
const m = raw.match(/^gcc\s+(?:\((\S+)\s+([\d.]+\S*)\)\s+)?([\d.]+)/);
|
|
85
|
+
if (!m) return null;
|
|
86
|
+
// If distro info present, use full distro version (e.g. 13.3.0-6ubuntu2~24.04.1)
|
|
87
|
+
if (m[1] && m[2]) return { version: m[2], extra: [] };
|
|
88
|
+
return { version: m[3], extra: [] };
|
|
89
|
+
},
|
|
90
|
+
// g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 — use full distro version
|
|
91
|
+
gpp: raw => {
|
|
92
|
+
const m = raw.match(/^g\+\+\s+(?:\((\S+)\s+([\d.]+\S*)\)\s+)?([\d.]+)/);
|
|
93
|
+
if (!m) return null;
|
|
94
|
+
// If distro info present, use full distro version (e.g. 13.3.0-6ubuntu2~24.04.1)
|
|
95
|
+
if (m[1] && m[2]) return { version: m[2], extra: [] };
|
|
96
|
+
return { version: m[3], extra: [] };
|
|
97
|
+
},
|
|
98
|
+
// clang version 17.0.0 (https://github.com/... commit)
|
|
99
|
+
clang: raw => {
|
|
100
|
+
const m = raw.match(/^clang\s+version\s+([\d.]+(?:-\S+)?)\s*(?:\(([^)]+)\))?/);
|
|
101
|
+
if (!m) return null;
|
|
102
|
+
return { version: m[1], extra: m[2] ? [m[2].trim()] : [] };
|
|
103
|
+
},
|
|
104
|
+
// LLD 17.0.0 (compatible with GNU linkers) — only version number matters
|
|
105
|
+
lld: raw => {
|
|
106
|
+
const m = raw.match(/^LLD\s+([\d.]+)/);
|
|
107
|
+
if (!m) return null;
|
|
108
|
+
return { version: m[1], extra: [] };
|
|
109
|
+
},
|
|
110
|
+
// Python 3.14.3
|
|
111
|
+
python: raw => {
|
|
112
|
+
const m = raw.match(/^Python\s+([\d.]+(?:\S*)?)/);
|
|
113
|
+
if (!m) return null;
|
|
114
|
+
return { version: m[1], extra: [] };
|
|
115
|
+
},
|
|
116
|
+
// ruby 3.4.9 (2026-03-11 revision 76cca827ab) +PRISM [x86_64-linux]
|
|
117
|
+
ruby: raw => {
|
|
118
|
+
const m = raw.match(/^ruby\s+([\d.]+(?:p\d+)?)\s*(?:\(([^)]+)\))?\s*(.*)/);
|
|
119
|
+
if (!m) return null;
|
|
120
|
+
const extra = [];
|
|
121
|
+
if (m[2]) extra.push(m[2].trim());
|
|
122
|
+
const tail = m[3] ? m[3].trim() : '';
|
|
123
|
+
if (tail) extra.push(tail);
|
|
124
|
+
return { version: m[1], extra };
|
|
125
|
+
},
|
|
126
|
+
// Kotlin version 2.3.20-release-208 (JRE 21+35-LTS)
|
|
127
|
+
kotlin: raw => {
|
|
128
|
+
const m = raw.match(/^Kotlin\s+version\s+([\d.\-\w]+)\s*(?:\(([^)]+)\))?/);
|
|
129
|
+
if (!m) return null;
|
|
130
|
+
return { version: m[1], extra: m[2] ? [m[2].trim()] : [] };
|
|
131
|
+
},
|
|
132
|
+
// Swift version 6.0.3 (swift-6.0.3-RELEASE)
|
|
133
|
+
swift: raw => {
|
|
134
|
+
const m = raw.match(/^Swift\s+version\s+([\d.]+(?:\.\d+)?)\s*(?:\(([^)]+)\))?/);
|
|
135
|
+
if (!m) return null;
|
|
136
|
+
return { version: m[1], extra: m[2] ? [m[2].trim()] : [] };
|
|
137
|
+
},
|
|
138
|
+
// R version 4.3.3 (2024-02-29) -- "Angel Food Cake"
|
|
139
|
+
r: raw => {
|
|
140
|
+
const m = raw.match(/^R\s+version\s+([\d.]+)\s*(?:\(([^)]+)\))?(?:\s+--\s+"([^"]+)")?/);
|
|
141
|
+
if (!m) return null;
|
|
142
|
+
const extra = [];
|
|
143
|
+
if (m[2]) extra.push(m[2]);
|
|
144
|
+
if (m[3]) extra.push(m[3]);
|
|
145
|
+
return { version: m[1], extra };
|
|
146
|
+
},
|
|
147
|
+
// git version 2.43.0
|
|
148
|
+
git: raw => {
|
|
149
|
+
const m = raw.match(/^git\s+version\s+([\d.]+)/);
|
|
150
|
+
if (!m) return null;
|
|
151
|
+
return { version: m[1], extra: [] };
|
|
152
|
+
},
|
|
153
|
+
// gh version 2.89.0 (2026-03-26)
|
|
154
|
+
gh: raw => {
|
|
155
|
+
const m = raw.match(/^gh\s+version\s+([\d.]+)\s*(?:\(([^)]+)\))?/);
|
|
156
|
+
if (!m) return null;
|
|
157
|
+
return { version: m[1], extra: m[2] ? [m[2]] : [] };
|
|
158
|
+
},
|
|
159
|
+
// glab version 1.36.0
|
|
160
|
+
glab: raw => {
|
|
161
|
+
const m = raw.match(/^glab\s+version\s+([\d.]+)/);
|
|
162
|
+
if (!m) return null;
|
|
163
|
+
return { version: m[1], extra: [] };
|
|
164
|
+
},
|
|
165
|
+
// curl 8.19.0 (x86_64-pc-linux-gnu) libcurl/8.19.0 ...
|
|
166
|
+
curl: raw => {
|
|
167
|
+
const m = raw.match(/^curl\s+([\d.]+)\s*(?:\(([^)]+)\))?/);
|
|
168
|
+
if (!m) return null;
|
|
169
|
+
return { version: m[1], extra: m[2] ? [m[2]] : [] };
|
|
170
|
+
},
|
|
171
|
+
// GNU Wget 1.21.4 built on linux-gnu.
|
|
172
|
+
wget: raw => {
|
|
173
|
+
const m = raw.match(/^GNU\s+Wget\s+([\d.]+)/);
|
|
174
|
+
if (!m) return null;
|
|
175
|
+
return { version: m[1], extra: [] };
|
|
176
|
+
},
|
|
177
|
+
// cmake version 3.28.3
|
|
178
|
+
cmake: raw => {
|
|
179
|
+
const m = raw.match(/^cmake\s+version\s+([\d.]+)/);
|
|
180
|
+
if (!m) return null;
|
|
181
|
+
return { version: m[1], extra: [] };
|
|
182
|
+
},
|
|
183
|
+
// GNU Make 4.3
|
|
184
|
+
make: raw => {
|
|
185
|
+
const m = raw.match(/^GNU\s+Make\s+([\d.]+)/);
|
|
186
|
+
if (!m) return null;
|
|
187
|
+
return { version: m[1], extra: [] };
|
|
188
|
+
},
|
|
189
|
+
// NASM version 2.16.01
|
|
190
|
+
nasm: raw => {
|
|
191
|
+
const m = raw.match(/^NASM\s+version\s+([\d.]+)/);
|
|
192
|
+
if (!m) return null;
|
|
193
|
+
return { version: m[1], extra: [] };
|
|
194
|
+
},
|
|
195
|
+
// flat assembler version 1.73.32
|
|
196
|
+
fasm: raw => {
|
|
197
|
+
const m = raw.match(/version\s+([\d.]+)/);
|
|
198
|
+
if (!m) return null;
|
|
199
|
+
return { version: m[1], extra: [] };
|
|
200
|
+
},
|
|
201
|
+
// Screen version 4.09.01 (GNU) 20-Aug-23
|
|
202
|
+
screen: raw => {
|
|
203
|
+
const m = raw.match(/^Screen\s+version\s+([\d.]+)\s*(?:\(([^)]+)\))?\s*(.*)/);
|
|
204
|
+
if (!m) return null;
|
|
205
|
+
const extra = [];
|
|
206
|
+
if (m[2]) extra.push(m[2]);
|
|
207
|
+
if (m[3] && m[3].trim()) extra.push(m[3].trim());
|
|
208
|
+
return { version: m[1], extra };
|
|
209
|
+
},
|
|
210
|
+
// expect version 5.45.4
|
|
211
|
+
expect: raw => {
|
|
212
|
+
const m = raw.match(/^expect\s+version\s+([\d.]+)/);
|
|
213
|
+
if (!m) return null;
|
|
214
|
+
return { version: m[1], extra: [] };
|
|
215
|
+
},
|
|
216
|
+
// The OCaml toplevel, version 5.4.1
|
|
217
|
+
ocaml: raw => {
|
|
218
|
+
const m = raw.match(/version\s+([\d.]+)/);
|
|
219
|
+
if (!m) return null;
|
|
220
|
+
return { version: m[1], extra: [] };
|
|
221
|
+
},
|
|
222
|
+
// The Rocq Prover, version 9.1.1
|
|
223
|
+
rocq: raw => {
|
|
224
|
+
const m = raw.match(/version\s+([\d.]+)/);
|
|
225
|
+
if (!m) return null;
|
|
226
|
+
return { version: m[1], extra: [] };
|
|
227
|
+
},
|
|
228
|
+
// elan 4.2.1 (3d5138e15 2026-03-18)
|
|
229
|
+
elan: raw => {
|
|
230
|
+
const m = raw.match(/^elan\s+([\d.]+)\s*(?:\(([^)]+)\))?/);
|
|
231
|
+
if (!m) return null;
|
|
232
|
+
const extra = m[2] ? m[2].trim().split(/\s+/) : [];
|
|
233
|
+
return { version: m[1], extra };
|
|
234
|
+
},
|
|
235
|
+
// Lean (version 4.29.0, x86_64-unknown-linux-gnu, commit abc123, Release)
|
|
236
|
+
lean: raw => {
|
|
237
|
+
const m = raw.match(/version\s+([\d.]+)(?:,\s*(.+?))\)?$/);
|
|
238
|
+
if (!m) return null;
|
|
239
|
+
const extra = m[2]
|
|
240
|
+
? m[2]
|
|
241
|
+
.split(',')
|
|
242
|
+
.map(s => s.trim().replace(/\)$/, ''))
|
|
243
|
+
.filter(Boolean)
|
|
244
|
+
: [];
|
|
245
|
+
return { version: m[1], extra };
|
|
246
|
+
},
|
|
247
|
+
// Google Chrome 146.0.7680.164
|
|
248
|
+
chrome: raw => {
|
|
249
|
+
const m = raw.match(/^Google\s+Chrome\s+([\d.]+)/);
|
|
250
|
+
if (!m) return null;
|
|
251
|
+
return { version: m[1], extra: [] };
|
|
252
|
+
},
|
|
253
|
+
// Chromium 137.0.7151.0
|
|
254
|
+
chromium: raw => {
|
|
255
|
+
const m = raw.match(/^Chromium\s+([\d.]+)/);
|
|
256
|
+
if (!m) return null;
|
|
257
|
+
return { version: m[1], extra: [] };
|
|
258
|
+
},
|
|
259
|
+
// Mozilla Firefox 139.0
|
|
260
|
+
firefox: raw => {
|
|
261
|
+
const m = raw.match(/^Mozilla\s+Firefox\s+([\d.]+)/);
|
|
262
|
+
if (!m) return null;
|
|
263
|
+
return { version: m[1], extra: [] };
|
|
264
|
+
},
|
|
265
|
+
// Microsoft Edge 146.0.3856.84
|
|
266
|
+
msedge: raw => {
|
|
267
|
+
const m = raw.match(/^Microsoft\s+Edge\s+([\d.]+)/);
|
|
268
|
+
if (!m) return null;
|
|
269
|
+
return { version: m[1], extra: [] };
|
|
270
|
+
},
|
|
271
|
+
// deno 2.7.9 (stable, release, x86_64-unknown-linux-gnu)
|
|
272
|
+
deno: raw => {
|
|
273
|
+
const m = raw.match(/^deno\s+([\d.]+)\s*(?:\(([^)]+)\))?/);
|
|
274
|
+
if (!m) return null;
|
|
275
|
+
const extra = m[2]
|
|
276
|
+
? m[2]
|
|
277
|
+
.split(',')
|
|
278
|
+
.map(s => s.trim())
|
|
279
|
+
.filter(Boolean)
|
|
280
|
+
: [];
|
|
281
|
+
return { version: m[1], extra };
|
|
282
|
+
},
|
|
283
|
+
// Version 1.58.2 (Playwright CLI)
|
|
284
|
+
playwright: raw => {
|
|
285
|
+
const m = raw.match(/(?:Version\s+)?([\d.]+)/);
|
|
286
|
+
if (!m) return null;
|
|
287
|
+
return { version: m[1], extra: [] };
|
|
288
|
+
},
|
|
289
|
+
// @playwright/test@1.58.2
|
|
290
|
+
playwrightTest: raw => {
|
|
291
|
+
const m = raw.match(/@playwright\/test@([\d.]+)/);
|
|
292
|
+
if (!m) return null;
|
|
293
|
+
return { version: m[1], extra: [] };
|
|
294
|
+
},
|
|
295
|
+
// @playwright/mcp@0.0.69 or `-- @playwright/mcp@0.0.69
|
|
296
|
+
playwrightMcp: raw => {
|
|
297
|
+
const m = raw.match(/@playwright\/mcp@([\d.]+)/);
|
|
298
|
+
if (!m) return null;
|
|
299
|
+
return { version: m[1], extra: [] };
|
|
300
|
+
},
|
|
301
|
+
// @puppeteer/browsers@2.13.0
|
|
302
|
+
puppeteerBrowsers: raw => {
|
|
303
|
+
const m = raw.match(/@puppeteer\/browsers@([\d.]+)/);
|
|
304
|
+
if (!m) return null;
|
|
305
|
+
return { version: m[1], extra: [] };
|
|
306
|
+
},
|
|
307
|
+
// 2.1.87 (Claude Code)
|
|
308
|
+
claudeCode: raw => {
|
|
309
|
+
const m = raw.match(/([\d.]+)\s*(?:\(([^)]+)\))?/);
|
|
310
|
+
if (!m) return null;
|
|
311
|
+
return { version: m[1], extra: m[2] ? [m[2]] : [] };
|
|
312
|
+
},
|
|
313
|
+
// GitHub Copilot CLI 1.0.14.\nRun 'copilot update'...
|
|
314
|
+
copilot: raw => {
|
|
315
|
+
const m = raw.match(/([\d.]+)/);
|
|
316
|
+
if (!m) return null;
|
|
317
|
+
// Strip trailing dot from version (e.g. "1.0.14." -> "1.0.14")
|
|
318
|
+
const version = m[1].replace(/\.$/, '');
|
|
319
|
+
return { version, extra: [] };
|
|
320
|
+
},
|
|
321
|
+
// pyenv 2.6.26
|
|
322
|
+
pyenv: raw => {
|
|
323
|
+
const m = raw.match(/^pyenv\s+([\d.]+)/);
|
|
324
|
+
if (!m) return null;
|
|
325
|
+
return { version: m[1], extra: [] };
|
|
326
|
+
},
|
|
327
|
+
// /workspace/.perl5/bin/perlbrew - App::perlbrew/1.02
|
|
328
|
+
perlbrew: raw => {
|
|
329
|
+
const m = raw.match(/App::perlbrew\/([\d.]+)/);
|
|
330
|
+
if (!m) return null;
|
|
331
|
+
return { version: m[1], extra: [] };
|
|
332
|
+
},
|
|
333
|
+
// rbenv 1.3.2-20-g23c3041
|
|
334
|
+
rbenv: raw => {
|
|
335
|
+
const m = raw.match(/^rbenv\s+([\d.]+(?:-[\w]+)*)/);
|
|
336
|
+
if (!m) return null;
|
|
337
|
+
return { version: m[1], extra: [] };
|
|
338
|
+
},
|
|
339
|
+
// Homebrew 5.1.2
|
|
340
|
+
brew: raw => {
|
|
341
|
+
const m = raw.match(/^Homebrew\s+([\d.]+)/);
|
|
342
|
+
if (!m) return null;
|
|
343
|
+
return { version: m[1], extra: [] };
|
|
344
|
+
},
|
|
345
|
+
// This is Zip 3.0 (July 5th 2008), by Info-ZIP.
|
|
346
|
+
zip: raw => {
|
|
347
|
+
const m = raw.match(/Zip\s+([\d.]+)\s*(?:\(([^)]+)\))?/);
|
|
348
|
+
if (!m) return null;
|
|
349
|
+
return { version: m[1], extra: m[2] ? [m[2]] : [] };
|
|
350
|
+
},
|
|
351
|
+
// UnZip 6.00 of 20 April 2009, by Debian.
|
|
352
|
+
unzip: raw => {
|
|
353
|
+
const m = raw.match(/UnZip\s+([\d.]+)\s*(?:of\s+([^,]+))?/);
|
|
354
|
+
if (!m) return null;
|
|
355
|
+
return { version: m[1], extra: m[2] ? [m[2].trim()] : [] };
|
|
356
|
+
},
|
|
357
|
+
// ii xvfb 2:21.1.12-1ubuntu1.5 amd64 Virtual Framebuffer...
|
|
358
|
+
xvfb: raw => {
|
|
359
|
+
// dpkg output format
|
|
360
|
+
const dpkg = raw.match(/^ii\s+xvfb\s+(\S+)/);
|
|
361
|
+
if (dpkg) {
|
|
362
|
+
// Strip epoch (e.g. "2:21.1.12-1ubuntu1.5" -> "21.1.12-1ubuntu1.5")
|
|
363
|
+
const ver = dpkg[1].replace(/^\d+:/, '');
|
|
364
|
+
return { version: ver, extra: [] };
|
|
365
|
+
}
|
|
366
|
+
// X.Org X Server version output (if it ever works)
|
|
367
|
+
const xorg = raw.match(/X\.Org\s+X\s+Server\s+([\d.]+)/);
|
|
368
|
+
if (xorg) return { version: xorg[1], extra: [] };
|
|
369
|
+
return null;
|
|
370
|
+
},
|
|
371
|
+
// Xvfb returns "Unrecognized option: -version" — this is handled by fixing the command
|
|
372
|
+
// to use dpkg fallback first
|
|
373
|
+
|
|
374
|
+
// agent 1.0.0 or similar
|
|
375
|
+
agent: raw => {
|
|
376
|
+
const m = raw.match(/([\d.]+)/);
|
|
377
|
+
if (!m) return null;
|
|
378
|
+
return { version: m[1], extra: [] };
|
|
379
|
+
},
|
|
380
|
+
// codex-cli 0.117.0 or similar
|
|
381
|
+
codex: raw => {
|
|
382
|
+
const m = raw.match(/([\d.]+)/);
|
|
383
|
+
if (!m) return null;
|
|
384
|
+
return { version: m[1], extra: [] };
|
|
385
|
+
},
|
|
386
|
+
// opencode 1.3.10 or similar
|
|
387
|
+
opencode: raw => {
|
|
388
|
+
const m = raw.match(/([\d.]+)/);
|
|
389
|
+
if (!m) return null;
|
|
390
|
+
return { version: m[1], extra: [] };
|
|
391
|
+
},
|
|
392
|
+
// qwen-code version
|
|
393
|
+
qwenCode: raw => {
|
|
394
|
+
const m = raw.match(/([\d.]+)/);
|
|
395
|
+
if (!m) return null;
|
|
396
|
+
return { version: m[1], extra: [] };
|
|
397
|
+
},
|
|
398
|
+
// gemini version
|
|
399
|
+
gemini: raw => {
|
|
400
|
+
const m = raw.match(/([\d.]+)/);
|
|
401
|
+
if (!m) return null;
|
|
402
|
+
return { version: m[1], extra: [] };
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Parse a raw version string using the per-tool parser, returning uniform format:
|
|
408
|
+
* "<version>" or "<version> (<extra1>, <extra2>, ...)"
|
|
409
|
+
* Falls back to the raw string if no parser matches.
|
|
410
|
+
* @param {string} key - Tool key (must match a key in VERSION_PARSERS)
|
|
411
|
+
* @param {string} raw - Raw version string from command output
|
|
412
|
+
* @returns {string} Parsed version string in uniform format
|
|
413
|
+
*/
|
|
414
|
+
export function parseVersion(key, raw) {
|
|
415
|
+
if (!raw) return raw;
|
|
416
|
+
const parser = VERSION_PARSERS[key];
|
|
417
|
+
if (!parser) return raw;
|
|
418
|
+
const result = parser(raw);
|
|
419
|
+
if (!result) return raw;
|
|
420
|
+
const { version, extra } = result;
|
|
421
|
+
if (extra && extra.length > 0) {
|
|
422
|
+
return `${version} (${extra.join(', ')})`;
|
|
423
|
+
}
|
|
424
|
+
return version;
|
|
425
|
+
}
|
|
426
|
+
|
|
36
427
|
/**
|
|
37
428
|
* Command definitions for version checking
|
|
38
429
|
* Each entry has: key, command, and optional fallbacks
|
|
@@ -57,8 +448,8 @@ const VERSION_COMMANDS = [
|
|
|
57
448
|
|
|
58
449
|
// Browsers (installed via Playwright)
|
|
59
450
|
{ key: 'chrome', command: 'google-chrome --version 2>&1' },
|
|
60
|
-
{ key: 'chromium', command: 'chromium --version 2>&1', fallbacks: ['chromium-browser --version 2>&1'] },
|
|
61
|
-
{ key: 'firefox', command: 'firefox --version 2>&1' },
|
|
451
|
+
{ key: 'chromium', command: 'chromium --version 2>&1', fallbacks: ['chromium-browser --version 2>&1', "ls ~/.cache/ms-playwright/ 2>/dev/null | grep -oE 'chromium-[0-9]+' | head -1"] },
|
|
452
|
+
{ key: 'firefox', command: 'firefox --version 2>&1', fallbacks: ["ls ~/.cache/ms-playwright/ 2>/dev/null | grep -oE 'firefox-[0-9]+' | head -1"] },
|
|
62
453
|
{ key: 'msedge', command: 'microsoft-edge --version 2>&1', fallbacks: ['microsoft-edge-stable --version 2>&1'] },
|
|
63
454
|
{ key: 'webkit', command: "ls ~/.cache/ms-playwright/ 2>/dev/null | grep -oE 'webkit-[0-9]+' | head -1" },
|
|
64
455
|
|
|
@@ -109,7 +500,7 @@ const VERSION_COMMANDS = [
|
|
|
109
500
|
{ key: 'gpp', command: 'g++ --version 2>&1 | head -n1' },
|
|
110
501
|
{ key: 'clang', command: 'clang --version 2>&1 | head -n1' },
|
|
111
502
|
{ key: 'llvm', command: 'llvm-config --version 2>&1' },
|
|
112
|
-
{ key: 'lld', command: 'lld --version 2>&1 | head -n1' },
|
|
503
|
+
{ key: 'lld', command: 'ld.lld --version 2>&1 | head -n1', fallbacks: ['lld --version 2>&1 | head -n1'] },
|
|
113
504
|
{ key: 'make', command: 'make --version 2>&1 | head -n1' },
|
|
114
505
|
{ key: 'cmake', command: 'cmake --version 2>&1 | head -n1' },
|
|
115
506
|
|
|
@@ -139,7 +530,7 @@ const VERSION_COMMANDS = [
|
|
|
139
530
|
{ key: 'unzip', command: 'unzip -v 2>&1 | head -n1' },
|
|
140
531
|
{ key: 'expect', command: 'expect -version 2>&1' },
|
|
141
532
|
{ key: 'screen', command: 'screen --version 2>&1' },
|
|
142
|
-
{ key: 'xvfb', command: '
|
|
533
|
+
{ key: 'xvfb', command: 'dpkg -l xvfb 2>/dev/null | grep "^ii" | head -1', fallbacks: ['Xvfb -version 2>&1 | head -n1'] },
|
|
143
534
|
];
|
|
144
535
|
|
|
145
536
|
/**
|
|
@@ -353,14 +744,17 @@ export async function getVersionInfo(verbose = false, processVersion = null) {
|
|
|
353
744
|
}
|
|
354
745
|
|
|
355
746
|
/**
|
|
356
|
-
* Helper to add version line if version exists
|
|
747
|
+
* Helper to add version line if version exists.
|
|
748
|
+
* Uses parseVersion() to normalize raw output into uniform format.
|
|
357
749
|
* @param {string[]} lines - Array to push to
|
|
358
750
|
* @param {string} label - Display label
|
|
359
751
|
* @param {string|null} version - Version string or null
|
|
752
|
+
* @param {string} [key] - Tool key for version parser lookup
|
|
360
753
|
*/
|
|
361
|
-
function addVersionLine(lines, label, version) {
|
|
754
|
+
function addVersionLine(lines, label, version, key) {
|
|
362
755
|
if (version) {
|
|
363
|
-
|
|
756
|
+
const display = key ? parseVersion(key, version) : version;
|
|
757
|
+
lines.push(`• ${label}: \`${display}\``);
|
|
364
758
|
}
|
|
365
759
|
}
|
|
366
760
|
|
|
@@ -384,13 +778,13 @@ export function formatVersionMessage(versions) {
|
|
|
384
778
|
|
|
385
779
|
// === AI Agents (--tool options) ===
|
|
386
780
|
const agentLines = [];
|
|
387
|
-
addVersionLine(agentLines, 'Claude Code', versions.claudeCode);
|
|
388
|
-
addVersionLine(agentLines, 'Agent CLI', versions.agent);
|
|
389
|
-
addVersionLine(agentLines, 'OpenAI Codex', versions.codex);
|
|
390
|
-
addVersionLine(agentLines, 'OpenCode', versions.opencode);
|
|
391
|
-
addVersionLine(agentLines, 'Qwen Code', versions.qwenCode);
|
|
392
|
-
addVersionLine(agentLines, 'Gemini CLI', versions.gemini);
|
|
393
|
-
addVersionLine(agentLines, 'GitHub Copilot', versions.copilot);
|
|
781
|
+
addVersionLine(agentLines, 'Claude Code', versions.claudeCode, 'claudeCode');
|
|
782
|
+
addVersionLine(agentLines, 'Agent CLI', versions.agent, 'agent');
|
|
783
|
+
addVersionLine(agentLines, 'OpenAI Codex', versions.codex, 'codex');
|
|
784
|
+
addVersionLine(agentLines, 'OpenCode', versions.opencode, 'opencode');
|
|
785
|
+
addVersionLine(agentLines, 'Qwen Code', versions.qwenCode, 'qwenCode');
|
|
786
|
+
addVersionLine(agentLines, 'Gemini CLI', versions.gemini, 'gemini');
|
|
787
|
+
addVersionLine(agentLines, 'GitHub Copilot', versions.copilot, 'copilot');
|
|
394
788
|
|
|
395
789
|
if (agentLines.length > 0) {
|
|
396
790
|
lines.push('');
|
|
@@ -402,7 +796,7 @@ export function formatVersionMessage(versions) {
|
|
|
402
796
|
const jsLines = [];
|
|
403
797
|
addVersionLine(jsLines, 'Node.js', versions.node);
|
|
404
798
|
addVersionLine(jsLines, 'Bun', versions.bun);
|
|
405
|
-
addVersionLine(jsLines, 'Deno', versions.deno);
|
|
799
|
+
addVersionLine(jsLines, 'Deno', versions.deno, 'deno');
|
|
406
800
|
addVersionLine(jsLines, 'NPM', versions.npm);
|
|
407
801
|
addVersionLine(jsLines, 'NVM', versions.nvm);
|
|
408
802
|
|
|
@@ -414,8 +808,8 @@ export function formatVersionMessage(versions) {
|
|
|
414
808
|
|
|
415
809
|
// === Python ===
|
|
416
810
|
const pythonLines = [];
|
|
417
|
-
addVersionLine(pythonLines, 'Python', versions.python);
|
|
418
|
-
addVersionLine(pythonLines, 'Pyenv', versions.pyenv);
|
|
811
|
+
addVersionLine(pythonLines, 'Python', versions.python, 'python');
|
|
812
|
+
addVersionLine(pythonLines, 'Pyenv', versions.pyenv, 'pyenv');
|
|
419
813
|
|
|
420
814
|
if (pythonLines.length > 0) {
|
|
421
815
|
lines.push('');
|
|
@@ -425,8 +819,8 @@ export function formatVersionMessage(versions) {
|
|
|
425
819
|
|
|
426
820
|
// === Rust ===
|
|
427
821
|
const rustLines = [];
|
|
428
|
-
addVersionLine(rustLines, 'Rustc', versions.rust);
|
|
429
|
-
addVersionLine(rustLines, 'Cargo', versions.cargo);
|
|
822
|
+
addVersionLine(rustLines, 'Rustc', versions.rust, 'rust');
|
|
823
|
+
addVersionLine(rustLines, 'Cargo', versions.cargo, 'cargo');
|
|
430
824
|
|
|
431
825
|
if (rustLines.length > 0) {
|
|
432
826
|
lines.push('');
|
|
@@ -436,7 +830,7 @@ export function formatVersionMessage(versions) {
|
|
|
436
830
|
|
|
437
831
|
// === Java ===
|
|
438
832
|
const javaLines = [];
|
|
439
|
-
addVersionLine(javaLines, 'Java', versions.java);
|
|
833
|
+
addVersionLine(javaLines, 'Java', versions.java, 'java');
|
|
440
834
|
addVersionLine(javaLines, 'SDKMAN', versions.sdkman);
|
|
441
835
|
|
|
442
836
|
if (javaLines.length > 0) {
|
|
@@ -449,14 +843,14 @@ export function formatVersionMessage(versions) {
|
|
|
449
843
|
if (versions.go) {
|
|
450
844
|
lines.push('');
|
|
451
845
|
lines.push('*🔷 Go*');
|
|
452
|
-
addVersionLine(lines, 'Go', versions.go);
|
|
846
|
+
addVersionLine(lines, 'Go', versions.go, 'go');
|
|
453
847
|
}
|
|
454
848
|
|
|
455
849
|
// === PHP ===
|
|
456
850
|
if (versions.php) {
|
|
457
851
|
lines.push('');
|
|
458
852
|
lines.push('*🐘 PHP*');
|
|
459
|
-
addVersionLine(lines, 'PHP', versions.php);
|
|
853
|
+
addVersionLine(lines, 'PHP', versions.php, 'php');
|
|
460
854
|
}
|
|
461
855
|
|
|
462
856
|
// === .NET ===
|
|
@@ -469,7 +863,7 @@ export function formatVersionMessage(versions) {
|
|
|
469
863
|
// === Perl ===
|
|
470
864
|
const perlLines = [];
|
|
471
865
|
addVersionLine(perlLines, 'Perl', versions.perl);
|
|
472
|
-
addVersionLine(perlLines, 'Perlbrew', versions.perlbrew);
|
|
866
|
+
addVersionLine(perlLines, 'Perlbrew', versions.perlbrew, 'perlbrew');
|
|
473
867
|
|
|
474
868
|
if (perlLines.length > 0) {
|
|
475
869
|
lines.push('');
|
|
@@ -479,9 +873,9 @@ export function formatVersionMessage(versions) {
|
|
|
479
873
|
|
|
480
874
|
// === OCaml/Rocq ===
|
|
481
875
|
const ocamlLines = [];
|
|
482
|
-
addVersionLine(ocamlLines, 'OCaml', versions.ocaml);
|
|
876
|
+
addVersionLine(ocamlLines, 'OCaml', versions.ocaml, 'ocaml');
|
|
483
877
|
addVersionLine(ocamlLines, 'Opam', versions.opam);
|
|
484
|
-
addVersionLine(ocamlLines, 'Rocq/Coq', versions.rocq);
|
|
878
|
+
addVersionLine(ocamlLines, 'Rocq/Coq', versions.rocq, 'rocq');
|
|
485
879
|
|
|
486
880
|
if (ocamlLines.length > 0) {
|
|
487
881
|
lines.push('');
|
|
@@ -491,8 +885,8 @@ export function formatVersionMessage(versions) {
|
|
|
491
885
|
|
|
492
886
|
// === Lean ===
|
|
493
887
|
const leanLines = [];
|
|
494
|
-
addVersionLine(leanLines, 'Lean', versions.lean);
|
|
495
|
-
addVersionLine(leanLines, 'Elan', versions.elan);
|
|
888
|
+
addVersionLine(leanLines, 'Lean', versions.lean, 'lean');
|
|
889
|
+
addVersionLine(leanLines, 'Elan', versions.elan, 'elan');
|
|
496
890
|
addVersionLine(leanLines, 'Lake', versions.lake);
|
|
497
891
|
|
|
498
892
|
if (leanLines.length > 0) {
|
|
@@ -503,8 +897,8 @@ export function formatVersionMessage(versions) {
|
|
|
503
897
|
|
|
504
898
|
// === Ruby ===
|
|
505
899
|
const rubyLines = [];
|
|
506
|
-
addVersionLine(rubyLines, 'Ruby', versions.ruby);
|
|
507
|
-
addVersionLine(rubyLines, 'Rbenv', versions.rbenv);
|
|
900
|
+
addVersionLine(rubyLines, 'Ruby', versions.ruby, 'ruby');
|
|
901
|
+
addVersionLine(rubyLines, 'Rbenv', versions.rbenv, 'rbenv');
|
|
508
902
|
|
|
509
903
|
if (rubyLines.length > 0) {
|
|
510
904
|
lines.push('');
|
|
@@ -516,34 +910,34 @@ export function formatVersionMessage(versions) {
|
|
|
516
910
|
if (versions.kotlin) {
|
|
517
911
|
lines.push('');
|
|
518
912
|
lines.push('*🟣 Kotlin*');
|
|
519
|
-
addVersionLine(lines, 'Kotlin', versions.kotlin);
|
|
913
|
+
addVersionLine(lines, 'Kotlin', versions.kotlin, 'kotlin');
|
|
520
914
|
}
|
|
521
915
|
|
|
522
916
|
// === Swift ===
|
|
523
917
|
if (versions.swift) {
|
|
524
918
|
lines.push('');
|
|
525
919
|
lines.push('*🦅 Swift*');
|
|
526
|
-
addVersionLine(lines, 'Swift', versions.swift);
|
|
920
|
+
addVersionLine(lines, 'Swift', versions.swift, 'swift');
|
|
527
921
|
}
|
|
528
922
|
|
|
529
923
|
// === R ===
|
|
530
924
|
if (versions.r) {
|
|
531
925
|
lines.push('');
|
|
532
926
|
lines.push('*📊 R*');
|
|
533
|
-
addVersionLine(lines, 'R', versions.r);
|
|
927
|
+
addVersionLine(lines, 'R', versions.r, 'r');
|
|
534
928
|
}
|
|
535
929
|
|
|
536
930
|
// === C/C++ ===
|
|
537
931
|
const cppLines = [];
|
|
538
|
-
addVersionLine(cppLines, 'GCC', versions.gcc);
|
|
539
|
-
addVersionLine(cppLines, 'G++', versions.gpp);
|
|
540
|
-
addVersionLine(cppLines, 'Clang', versions.clang);
|
|
932
|
+
addVersionLine(cppLines, 'GCC', versions.gcc, 'gcc');
|
|
933
|
+
addVersionLine(cppLines, 'G++', versions.gpp, 'gpp');
|
|
934
|
+
addVersionLine(cppLines, 'Clang', versions.clang, 'clang');
|
|
541
935
|
addVersionLine(cppLines, 'LLVM', versions.llvm);
|
|
542
|
-
addVersionLine(cppLines, 'LLD', versions.lld);
|
|
543
|
-
addVersionLine(cppLines, 'Make', versions.make);
|
|
544
|
-
addVersionLine(cppLines, 'CMake', versions.cmake);
|
|
545
|
-
addVersionLine(cppLines, 'NASM', versions.nasm);
|
|
546
|
-
addVersionLine(cppLines, 'FASM', versions.fasm);
|
|
936
|
+
addVersionLine(cppLines, 'LLD', versions.lld, 'lld');
|
|
937
|
+
addVersionLine(cppLines, 'Make', versions.make, 'make');
|
|
938
|
+
addVersionLine(cppLines, 'CMake', versions.cmake, 'cmake');
|
|
939
|
+
addVersionLine(cppLines, 'NASM', versions.nasm, 'nasm');
|
|
940
|
+
addVersionLine(cppLines, 'FASM', versions.fasm, 'fasm');
|
|
547
941
|
|
|
548
942
|
if (cppLines.length > 0) {
|
|
549
943
|
lines.push('');
|
|
@@ -553,10 +947,10 @@ export function formatVersionMessage(versions) {
|
|
|
553
947
|
|
|
554
948
|
// === Browsers ===
|
|
555
949
|
const browserLines = [];
|
|
556
|
-
addVersionLine(browserLines, 'Google Chrome', versions.chrome);
|
|
557
|
-
addVersionLine(browserLines, 'Chromium', versions.chromium);
|
|
558
|
-
addVersionLine(browserLines, 'Firefox', versions.firefox);
|
|
559
|
-
addVersionLine(browserLines, 'Microsoft Edge', versions.msedge);
|
|
950
|
+
addVersionLine(browserLines, 'Google Chrome', versions.chrome, 'chrome');
|
|
951
|
+
addVersionLine(browserLines, 'Chromium', versions.chromium, 'chromium');
|
|
952
|
+
addVersionLine(browserLines, 'Firefox', versions.firefox, 'firefox');
|
|
953
|
+
addVersionLine(browserLines, 'Microsoft Edge', versions.msedge, 'msedge');
|
|
560
954
|
addVersionLine(browserLines, 'WebKit', versions.webkit);
|
|
561
955
|
|
|
562
956
|
if (browserLines.length > 0) {
|
|
@@ -567,15 +961,15 @@ export function formatVersionMessage(versions) {
|
|
|
567
961
|
|
|
568
962
|
// === Browser Automation ===
|
|
569
963
|
const browserAutoLines = [];
|
|
570
|
-
addVersionLine(browserAutoLines, 'Playwright', versions.playwright);
|
|
571
|
-
addVersionLine(browserAutoLines, 'Playwright Test', versions.playwrightTest);
|
|
572
|
-
|
|
573
|
-
if (versions.
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
browserAutoLines.push(
|
|
964
|
+
addVersionLine(browserAutoLines, 'Playwright', versions.playwright, 'playwright');
|
|
965
|
+
addVersionLine(browserAutoLines, 'Playwright Test', versions.playwrightTest, 'playwrightTest');
|
|
966
|
+
// Playwright MCP: show version with Claude Code connection status inline
|
|
967
|
+
if (versions.playwrightMcp) {
|
|
968
|
+
const mcpVersion = parseVersion('playwrightMcp', versions.playwrightMcp);
|
|
969
|
+
const claudeStatus = versions.playwrightMcpStatus ? 'connected' : 'not connected';
|
|
970
|
+
browserAutoLines.push(`• Playwright MCP: \`${mcpVersion} | Claude Code: ${claudeStatus}\``);
|
|
577
971
|
}
|
|
578
|
-
addVersionLine(browserAutoLines, 'Puppeteer Browsers', versions.puppeteerBrowsers);
|
|
972
|
+
addVersionLine(browserAutoLines, 'Puppeteer Browsers', versions.puppeteerBrowsers, 'puppeteerBrowsers');
|
|
579
973
|
|
|
580
974
|
if (browserAutoLines.length > 0) {
|
|
581
975
|
lines.push('');
|
|
@@ -585,17 +979,17 @@ export function formatVersionMessage(versions) {
|
|
|
585
979
|
|
|
586
980
|
// === Development Tools ===
|
|
587
981
|
const toolLines = [];
|
|
588
|
-
addVersionLine(toolLines, 'Git', versions.git);
|
|
589
|
-
addVersionLine(toolLines, 'GitHub CLI', versions.gh);
|
|
590
|
-
addVersionLine(toolLines, 'GitLab CLI', versions.glab);
|
|
591
|
-
addVersionLine(toolLines, 'Homebrew', versions.brew);
|
|
592
|
-
addVersionLine(toolLines, 'cURL', versions.curl);
|
|
593
|
-
addVersionLine(toolLines, 'Wget', versions.wget);
|
|
594
|
-
addVersionLine(toolLines, 'Zip', versions.zip);
|
|
595
|
-
addVersionLine(toolLines, 'Unzip', versions.unzip);
|
|
596
|
-
addVersionLine(toolLines, 'Expect', versions.expect);
|
|
597
|
-
addVersionLine(toolLines, 'Screen', versions.screen);
|
|
598
|
-
addVersionLine(toolLines, 'Xvfb', versions.xvfb);
|
|
982
|
+
addVersionLine(toolLines, 'Git', versions.git, 'git');
|
|
983
|
+
addVersionLine(toolLines, 'GitHub CLI', versions.gh, 'gh');
|
|
984
|
+
addVersionLine(toolLines, 'GitLab CLI', versions.glab, 'glab');
|
|
985
|
+
addVersionLine(toolLines, 'Homebrew', versions.brew, 'brew');
|
|
986
|
+
addVersionLine(toolLines, 'cURL', versions.curl, 'curl');
|
|
987
|
+
addVersionLine(toolLines, 'Wget', versions.wget, 'wget');
|
|
988
|
+
addVersionLine(toolLines, 'Zip', versions.zip, 'zip');
|
|
989
|
+
addVersionLine(toolLines, 'Unzip', versions.unzip, 'unzip');
|
|
990
|
+
addVersionLine(toolLines, 'Expect', versions.expect, 'expect');
|
|
991
|
+
addVersionLine(toolLines, 'Screen', versions.screen, 'screen');
|
|
992
|
+
addVersionLine(toolLines, 'Xvfb', versions.xvfb, 'xvfb');
|
|
599
993
|
|
|
600
994
|
if (toolLines.length > 0) {
|
|
601
995
|
lines.push('');
|
|
@@ -616,4 +1010,5 @@ export function formatVersionMessage(versions) {
|
|
|
616
1010
|
export default {
|
|
617
1011
|
getVersionInfo,
|
|
618
1012
|
formatVersionMessage,
|
|
1013
|
+
parseVersion,
|
|
619
1014
|
};
|