// ========================================== // Regex Tester — Browser Extension // ========================================== // Tests regular expressions against input strings. // Registers the regex_test tool so the LLM can verify // patterns instead of guessing. // ========================================== Extensions.register({ id: 'regex-tester', async init(ctx) { const self = this; ctx.tools.handle('regex_test', async (args) => { return self._test(args.pattern, args.flags || '', args.inputs || []); }); }, _test(pattern, flags, inputs) { // Validate pattern let regex; try { regex = new RegExp(pattern, flags); } catch (e) { return { success: false, error: `Invalid regex: ${e.message}`, pattern, flags, }; } const results = inputs.map(input => { // Reset lastIndex for global/sticky patterns regex.lastIndex = 0; const isGlobal = regex.global; const matches = []; if (isGlobal) { // Collect all matches for global flag let match; while ((match = regex.exec(input)) !== null) { matches.push(this._formatMatch(match)); // Prevent infinite loops on zero-length matches if (match[0].length === 0) regex.lastIndex++; } } else { const match = regex.exec(input); if (match) { matches.push(this._formatMatch(match)); } } return { input, matched: matches.length > 0, matchCount: matches.length, matches, }; }); const totalMatched = results.filter(r => r.matched).length; return { success: true, pattern, flags, summary: `${totalMatched}/${inputs.length} inputs matched`, results, }; }, _formatMatch(match) { const result = { fullMatch: match[0], index: match.index, length: match[0].length, }; // Named groups (ES2018) if (match.groups) { result.namedGroups = {}; for (const [name, value] of Object.entries(match.groups)) { result.namedGroups[name] = value; } } // Numbered capture groups if (match.length > 1) { result.groups = []; for (let i = 1; i < match.length; i++) { result.groups.push(match[i] !== undefined ? match[i] : null); } } return result; }, destroy() {} });