From bd53e289cf6220f1ab2fdb0cf0017959b22b5001 Mon Sep 17 00:00:00 2001 From: rob Date: Tue, 7 Oct 2025 18:51:39 +0000 Subject: [PATCH] Update src/ai-repo-commander.user.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Debug threshold clarified & correct logLoop() logs at a visible level (INFO by default) Cleanup summary logs at INFO Console output is suppressed unless DEBUG_LEVEL β‰₯ 3 (verbose/trace) Clipboard copy has a safe fallback Pause button shows a clear visual state Panel mount is resilient if document.body isn’t ready Emergency STOP also clears the cleanup interval Initial scan explicitly skipped when PROCESS_EXISTING: false Plus the earlier low-risk hardening (require action:; pre-mark history) --- src/ai-repo-commander.user.js | 416 +++++++++++++++++++++++++++------- 1 file changed, 331 insertions(+), 85 deletions(-) diff --git a/src/ai-repo-commander.user.js b/src/ai-repo-commander.user.js index 6e22153..dc70a7f 100644 --- a/src/ai-repo-commander.user.js +++ b/src/ai-repo-commander.user.js @@ -1,8 +1,8 @@ // ==UserScript== // @name AI Repo Commander // @namespace http://tampermonkey.net/ -// @version 1.3.0 -// @description Execute ^%$bridge YAML commands from AI assistants in code blocks with persistent dedupe, robust paste, and optional auto-submit +// @version 1.3.2 +// @description Execute ^%$bridge YAML commands from AI assistants in code blocks with persistent dedupe, robust paste, optional auto-submit, and a built-in debug console // @author Your Name // @match https://chat.openai.com/* // @match https://chatgpt.com/* @@ -19,14 +19,18 @@ // ---------------------- Config ---------------------- const CONFIG = { - ENABLE_API: true, // Master kill switch - DEBUG_MODE: true, // Console logs + ENABLE_API: true, // Master kill switch (STOP API flips this to false) + DEBUG_MODE: true, // Global on/off for debug logging + DEBUG_LEVEL: 2, // 0=off, 1=errors, 2=info, 3=verbose, 4=trace + DEBUG_WATCH_MS: 120000, // Only log tight loop spam for the first 2 minutes + DEBUG_MAX_LINES: 400, // In-memory + panel lines + DEBUG_SHOW_PANEL: true, // Show floating debug console UI DEBOUNCE_DELAY: 5000, // Bot typing protection MAX_RETRIES: 2, // Retry attempts (=> up to MAX_RETRIES+1 total tries) - VERSION: '1.3.0', + VERSION: '1.3.2', - PROCESS_EXISTING: false, // If false, only process messages added after init (but see initial delayed scan) - ASSISTANT_ONLY: true, // Process assistant messages by default (your core use case) + PROCESS_EXISTING: false, // If false, only process *new* messages (no initial rescan) + ASSISTANT_ONLY: true, // Process assistant messages by default (core use case) // Persistent dedupe window DEDUPE_TTL_MS: 30 * 24 * 60 * 60 * 1000, // 30 days @@ -38,10 +42,195 @@ // Paste + submit behavior APPEND_TRAILING_NEWLINE: true, // Add '\n' after pasted text AUTO_SUBMIT: true, // Try to submit after pasting content - POST_PASTE_DELAY_MS: 250, // Small delay before submit to let editors settle + POST_PASTE_DELAY_MS: 250, // Delay before submit to let editors settle SUBMIT_MODE: 'button_first', // 'button_first' | 'enter_only' | 'smart' + + // Runtime toggles (live-updated by the debug panel) + RUNTIME: { + PAUSED: false, // Pause scanning + execution via panel + } }; + // ---------------------- Debug Console ---------------------- + let RC_DEBUG = null; + + class DebugConsole { + constructor(cfg) { + this.cfg = cfg; + this.buf = []; + this.loopCounts = new Map(); + this.startedAt = Date.now(); + this.panel = null; + if (cfg.DEBUG_SHOW_PANEL) this.mount(); + this.info(`Debug console ready (level=${cfg.DEBUG_LEVEL})`); + } + // Levels: 1=ERROR, 2=WARN, 3=INFO, 4=VERB, 5=TRACE + error(msg, data) { this._log(1, 'ERROR', msg, data); } + warn(msg, data) { this._log(2, 'WARN', msg, data); } + info(msg, data) { this._log(3, 'INFO', msg, data); } + verbose(msg, data){ this._log(4, 'VERB', msg, data); } + trace(msg, data) { this._log(5, 'TRACE', msg, data); } + + command(action, status, extra={}) { + const icon = { detected:'πŸ‘οΈ', parsing:'πŸ“', validating:'βœ“', debouncing:'⏳', executing:'βš™οΈ', complete:'βœ…', error:'❌' }[status] || 'β€’'; + this.info(`${icon} ${action} [${status}]`, extra); + } + + nowIso() { return new Date().toISOString(); } + withinWatch() { return Date.now() - this.startedAt <= this.cfg.DEBUG_WATCH_MS; } + + // Loop/ticker messages β†’ suppress after 10 repeats or after WATCH window + logLoop(kind, msg) { + const k = `${kind}:${msg}`; + const cur = this.loopCounts.get(k) || 0; + if (!this.withinWatch() && kind !== 'WARN') return; + if (cur >= 10) return; + this.loopCounts.set(k, cur + 1); + const suffix = (cur + 1) > 1 ? ` (${cur + 1}x)` : ''; + // default to INFO (visible at level 2+) + if (kind === 'ERROR') this.error(`${msg}${suffix}`); + else if (kind === 'WARN') this.warn(`${msg}${suffix}`); + else this.info(`${msg}${suffix}`); + } + + copyLast(n=50) { + const lines = this.buf.slice(-n).map(e => `${e.ts} ${e.level.padEnd(5)} ${e.msg}${e.data? ' ' + JSON.stringify(e.data): ''}`).join('\n'); + + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(lines).then(() => { + this.info(`Copied last ${Math.min(n, this.buf.length)} lines to clipboard`); + }).catch(e => this._fallbackCopy(lines, e)); + } else { + this._fallbackCopy(lines); + } + } + + _fallbackCopy(text, originalError = null) { + try { + const ta = document.createElement('textarea'); + ta.value = text; + ta.style.position = 'fixed'; + ta.style.opacity = '0'; + document.body.appendChild(ta); + ta.focus(); + ta.select(); + const ok = document.execCommand('copy'); + document.body.removeChild(ta); + if (ok) this.info(`Copied last ${text.split('\n').length} lines to clipboard (fallback)`); + else this.warn('Clipboard copy failed (fallback)'); + } catch (e) { + this.warn('Clipboard copy failed', { error: originalError?.message || e.message }); + } + } + + setLevel(n) { + const lv = Math.max(0, Math.min(4, n)); // clamp 0..4 + this.cfg.DEBUG_LEVEL = lv; + this.info(`Log level => ${lv}`); + } + + _sanitize(data) { + if (!data) return null; + try { + if (data instanceof HTMLElement) return '[HTMLElement]'; + if (typeof data === 'string' && data.length > 400) return data.slice(0,400)+'…'; + if (typeof data === 'object') { + const clone = { ...data }; + if (clone.element instanceof HTMLElement) clone.element = '[HTMLElement]'; + return clone; + } + } catch {} + return data; + } + + _log(numericLevel, levelName, msg, data) { + if (!this.cfg.DEBUG_MODE) return; + + // Threshold map: 0=off, 1=ERROR, 2=+WARN+INFO, 3=+VERB, 4=+TRACE + const thresholdMap = { 0: 0, 1: 1, 2: 3, 3: 4, 4: 5 }; + const threshold = thresholdMap[this.cfg.DEBUG_LEVEL] ?? 0; + if (numericLevel > threshold) return; + + const entry = { ts: this.nowIso(), level: levelName, msg: String(msg), data: this._sanitize(data) }; + this.buf.push(entry); + if (this.buf.length > this.cfg.DEBUG_MAX_LINES) this.buf.splice(0, this.buf.length - this.cfg.DEBUG_MAX_LINES); + + // Keep console quiet unless verbose+ is enabled + if (this.cfg.DEBUG_LEVEL >= 3) { + const prefix = `[AI RC]`; + if (entry.data != null) console.log(prefix, entry.level, entry.msg, entry.data); + else console.log(prefix, entry.level, entry.msg); + } + + if (this.panel) this._renderRow(entry); + } + + mount() { + if (!document.body) { + setTimeout(() => this.mount(), 100); + return; + } + const root = document.createElement('div'); + root.style.cssText = ` + position: fixed; right: 16px; bottom: 16px; z-index: 2147483647; + width: 420px; max-height: 45vh; display: flex; flex-direction: column; + background: rgba(20,20,24,0.92); border:1px solid #3b3b46; border-radius: 8px; + color:#e5e7eb; font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + box-shadow: 0 16px 40px rgba(0,0,0,0.55); backdrop-filter: blur(4px); + `; + root.innerHTML = ` +
+ AI Repo Commander β€” Debug + + + + +
+
+ `; + document.body.appendChild(root); + this.panel = root; + + const sel = root.querySelector('.rc-level'); + sel.value = String(this.cfg.DEBUG_LEVEL); + sel.addEventListener('change', () => this.setLevel(parseInt(sel.value,10))); + + root.querySelector('.rc-copy').addEventListener('click', () => this.copyLast(50)); + + const pauseBtn = root.querySelector('.rc-pause'); + pauseBtn.addEventListener('click', () => { + this.cfg.RUNTIME.PAUSED = !this.cfg.RUNTIME.PAUSED; + pauseBtn.textContent = this.cfg.RUNTIME.PAUSED ? 'Resume' : 'Pause'; + pauseBtn.style.background = this.cfg.RUNTIME.PAUSED ? '#f59e0b' : ''; + pauseBtn.style.color = this.cfg.RUNTIME.PAUSED ? '#111827' : ''; + this.info(`Runtime ${this.cfg.RUNTIME.PAUSED ? 'paused' : 'resumed'}`); + }); + + root.querySelector('.rc-stop').addEventListener('click', () => { + window.AI_REPO_STOP?.(); + this.warn('Emergency STOP activated'); + }); + } + + _renderRow(e) { + const body = this.panel.querySelector('.rc-body'); + const row = document.createElement('div'); + row.style.cssText = 'padding:4px 0;border-bottom:1px dashed #2a2a34;white-space:pre-wrap;word-break:break-word;'; + row.textContent = `${e.ts} ${e.level.padEnd(5)} ${e.msg}${e.data? ' ' + JSON.stringify(e.data): ''}`; + body.appendChild(row); + while (body.children.length > this.cfg.DEBUG_MAX_LINES) body.firstChild.remove(); + body.scrollTop = body.scrollHeight; + } + } + // ---------------------- Platform selectors ---------------------- const PLATFORM_SELECTORS = { 'chat.openai.com': { messages: '[data-message-author-role]', input: '#prompt-textarea, textarea, [contenteditable="true"]', content: '.markdown' }, @@ -294,7 +483,7 @@ return false; } catch (e) { - console.warn('[AI Repo Commander] pasteToComposer failed:', e); + RC_DEBUG?.warn('pasteToComposer failed', { error: String(e) }); return false; } } @@ -332,7 +521,6 @@ } static extractCommandBlock(text) { - // Find ^%$bridge anywhere in the code block, prefer explicit '---' terminator const patterns = [ /^\s*\^%\$bridge[ \t]*\n([\s\S]*?)\n---[ \t]*(?:\n|$)/m, /^\s*\^%\$bridge[ \t]*\n([\s\S]*?)(?=\n\s*$|\n---|\n```|$)/m, @@ -480,10 +668,8 @@ static _extractFilesArray(payload) { const obj = Array.isArray(payload) ? payload[0] : payload; - // Common shapes: { result: { files: [...] } } or { files: [...] } let files = obj?.result?.files ?? obj?.files ?? null; if (!files) { - // Try to sniff: look for any array under result that looks like files const res = obj?.result; if (res) { for (const [k, v] of Object.entries(res)) { @@ -497,7 +683,6 @@ } static _formatFilesListing(files) { - // Accept strings or objects; prefer .path, else join directory/name const pickPath = (f) => { if (typeof f === 'string') return f; if (typeof f?.path === 'string') return f.path; @@ -507,7 +692,6 @@ }; const lines = files.map(pickPath).filter(Boolean).sort(); - // Friendly text block (fits most chat UIs) return '```text\n' + lines.join('\n') + '\n```'; } @@ -521,7 +705,6 @@ details: data.message || 'Operation completed successfully' }); - // Auto-paste handlers if (command.action === 'get_file') { const body = this._extractGetFileBody(data); if (typeof body === 'string' && body.length) { @@ -537,7 +720,6 @@ const listing = this._formatFilesListing(files); await pasteAndMaybeSubmit(listing); } else { - // Fallback: paste the whole payload as JSON for visibility const fallback = '```json\n' + JSON.stringify(data, null, 2) + '\n```'; await pasteAndMaybeSubmit(fallback); GM_notification({ title: 'AI Repo Commander', text: 'list_files succeeded, but response had no obvious files array. Pasted raw JSON.', timeout: 5000 }); @@ -571,22 +753,43 @@ // ---------------------- Monitor ---------------------- class CommandMonitor { constructor() { - this.trackedMessages = new Map(); // id -> { element, originalText, state, lastUpdate } + this.trackedMessages = new Map(); // id -> { element, originalText, state, lastUpdate, startTime } this.history = new CommandHistory(); this.observer = null; this.currentPlatform = null; + this._idCounter = 0; + this.cleanupIntervalId = null; this.initialize(); } + getReadableMessageId(element) { + this._idCounter += 1; + const id = `cmd-${this._idCounter}-${Math.random().toString(36).slice(2,6)}`; + if (element?.dataset) element.dataset.aiRcId = id; + return id; + } + + extractAction(text) { + const m = /(^|\n)\s*action\s*:\s*([A-Za-z_][\w\-]*)/m.exec(text || ''); + return m ? m[2] : 'unknown'; + } + initialize() { this.detectPlatform(); this.startObservation(); this.setupEmergencyStop(); - this.log('AI Repo Commander initialized', CONFIG); + RC_DEBUG?.info('AI Repo Commander initialized', { + ENABLE_API: CONFIG.ENABLE_API, + DEBUG_MODE: CONFIG.DEBUG_MODE, + DEBOUNCE_DELAY: CONFIG.DEBOUNCE_DELAY, + MAX_RETRIES: CONFIG.MAX_RETRIES, + VERSION: CONFIG.VERSION + }); if (CONFIG.ENABLE_API) { - console.warn('[AI Repo Commander] API is enabled β€” you will be prompted for your bridge key on first command.'); + RC_DEBUG?.warn('API is enabled β€” you will be prompted for your bridge key on first command.'); } - setInterval(() => this.cleanupProcessedCommands(), CONFIG.CLEANUP_INTERVAL_MS); + // store interval id so STOP can clear it + this.cleanupIntervalId = setInterval(() => this.cleanupProcessedCommands(), CONFIG.CLEANUP_INTERVAL_MS); } detectPlatform() { @@ -595,30 +798,68 @@ } startObservation() { + // Throttled observer; only rescan if code blocks likely appeared + let scanPending = false; + const scheduleScan = () => { + if (scanPending) return; + scanPending = true; + setTimeout(() => { scanPending = false; this.scanMessages(); }, 100); + }; + this.observer = new MutationObserver((mutations) => { - mutations.forEach((m) => { - m.addedNodes.forEach((node) => { - if (node.nodeType === 1) this.scanNode(node); - }); - }); + for (const m of mutations) { + for (const node of m.addedNodes) { + if (node.nodeType !== 1) continue; + if (node.matches?.('pre, code') || node.querySelector?.('pre, code')) { + scheduleScan(); + break; + } + } + } }); this.observer.observe(document.body, { childList: true, subtree: true }); - // Always do one delayed scan to catch initial render, even with PROCESS_EXISTING=false - setTimeout(() => { - this.log('Initial scan after page load'); - this.scanMessages(); - }, 2000); - + // Respect PROCESS_EXISTING on initial scan if (CONFIG.PROCESS_EXISTING) { setTimeout(() => { - this.log('Deep scan of existing messages (PROCESS_EXISTING=true)'); + RC_DEBUG?.info('Initial scan after page load (PROCESS_EXISTING=true)'); this.scanMessages(); - }, 5000); + }, 1000); + } else { + RC_DEBUG?.info('Initial scan skipped (PROCESS_EXISTING=false)'); } } - scanNode() { this.scanMessages(); } + scanMessages() { + if (CONFIG.RUNTIME.PAUSED) { RC_DEBUG?.logLoop('loop', 'scan paused'); return; } + + const messages = document.querySelectorAll(this.currentPlatform.messages); + let skipped = 0, found = 0; + + messages.forEach((el) => { + if (!this.isAssistantMessage(el)) return; + if (el.dataset.aiRcProcessed) return; + + const hit = this.findCommandInCodeBlock(el); + if (!hit) return; + + const cmdText = hit.text; + + if (this.history.has(cmdText)) { + el.dataset.aiRcProcessed = '1'; + skipped++; + return; + } + + el.dataset.aiRcProcessed = '1'; + const id = this.getReadableMessageId(el); + this.trackMessage(el, cmdText, id); + found++; + }); + + if (skipped) RC_DEBUG?.logLoop('loop', `skipped already-executed (${skipped})`); + if (found) RC_DEBUG?.info(`Found ${found} new command(s)`); + } isAssistantMessage(el) { if (!CONFIG.ASSISTANT_ONLY) return true; @@ -634,11 +875,12 @@ return true; } + // **HARDENED**: require header + action: to avoid partials findCommandInCodeBlock(el) { const blocks = el.querySelectorAll('pre code, pre, code'); for (const b of blocks) { const txt = (b.textContent || '').trim(); - if (/(^|\n)\s*\^%\$bridge\b/m.test(txt)) { + if (/(^|\n)\s*\^%\$bridge\b/m.test(txt) && /(^|\n)\s*action\s*:/m.test(txt)) { return { blockElement: b, text: txt }; } } @@ -646,10 +888,8 @@ } getMessageId(element) { - if (element.dataset && element.dataset.aiRcId) return element.dataset.aiRcId; - const id = element.id || `${element.className}-${element.childElementCount}-${Date.now()}-${Math.random().toString(36).slice(2,6)}`; - if (element.dataset) element.dataset.aiRcId = id; - return id; + // kept for compatibility + return this.getReadableMessageId(element); } reextractCommandText(element) { @@ -657,29 +897,8 @@ return hit ? hit.text : ''; } - scanMessages() { - const messages = document.querySelectorAll(this.currentPlatform.messages); - messages.forEach((el) => { - if (!this.isAssistantMessage(el)) return; - if (el.dataset.aiRcProcessed) return; - - const hit = this.findCommandInCodeBlock(el); - if (!hit) return; - - const cmdText = hit.text; - - if (this.history.has(cmdText)) { - this.log('Skipping already-executed command (persistent history)'); - return; - } - - el.dataset.aiRcProcessed = '1'; - this.trackMessage(el, cmdText, this.getMessageId(el)); - }); - } - trackMessage(element, text, messageId) { - this.log('New command detected in code block:', { messageId, preview: text.substring(0, 120) }); + RC_DEBUG?.info('New command detected', { messageId, preview: text.substring(0, 120) }); this.trackedMessages.set(messageId, { element, originalText: text, state: COMMAND_STATES.DETECTED, startTime: Date.now(), lastUpdate: Date.now() }); @@ -690,16 +909,21 @@ updateState(messageId, state) { const msg = this.trackedMessages.get(messageId); if (!msg) return; + const old = msg.state; msg.state = state; msg.lastUpdate = Date.now(); this.trackedMessages.set(messageId, msg); - this.log(`Message ${messageId} state updated to: ${state}`); + RC_DEBUG?.command(this.extractAction(msg.originalText), state, { + messageId, transition: `${old} -> ${state}` + }); } async processCommand(messageId) { + if (CONFIG.RUNTIME.PAUSED) { RC_DEBUG?.info('process paused, skipping', { messageId }); return; } + const started = Date.now(); try { const message = this.trackedMessages.get(messageId); - if (!message) return; + if (!message) { RC_DEBUG?.error('Message not found', { messageId }); return; } let parsed = CommandParser.parseYAMLCommand(message.originalText); this.updateState(messageId, COMMAND_STATES.VALIDATING); @@ -712,40 +936,40 @@ await this.debounce(); const after = this.reextractCommandText(message.element); - - if (!after) { - this.log('Command removed during debounce - aborting'); - this.updateState(messageId, COMMAND_STATES.ERROR); - return; - } + if (!after) { this.updateState(messageId, COMMAND_STATES.ERROR); return; } if (after !== before) { - this.log('Command changed during debounce - updating and re-debouncing once'); + RC_DEBUG?.info('Command changed during debounce (re-validate)', { messageId }); message.originalText = after; await this.debounce(); const finalTxt = this.reextractCommandText(message.element); - if (!finalTxt) { - this.log('Command removed after re-debounce - aborting'); - this.updateState(messageId, COMMAND_STATES.ERROR); - return; - } + if (!finalTxt) { this.updateState(messageId, COMMAND_STATES.ERROR); return; } message.originalText = finalTxt; parsed = CommandParser.parseYAMLCommand(finalTxt); validation = CommandParser.validateStructure(parsed); if (!validation.isValid) throw new Error(`Final validation failed: ${validation.errors.join(', ')}`); } + // **HARDENED**: pre-mark to avoid duplicate runs if DOM churns + this.history.mark(message.originalText); + this.updateState(messageId, COMMAND_STATES.EXECUTING); await ExecutionManager.executeCommand(parsed, message.element); - this.history.mark(message.originalText); + const duration = Date.now() - started; + if (duration < 100) RC_DEBUG?.warn('Command completed suspiciously fast', { messageId, duration }); + if (duration > 30000) RC_DEBUG?.warn('Command took unusually long', { messageId, duration }); + this.updateState(messageId, COMMAND_STATES.COMPLETE); } catch (error) { - this.log(`Command processing error: ${error.message}`); + const duration = Date.now() - started; + RC_DEBUG?.error(`Command processing error: ${error.message}`, { messageId, duration }); this.updateState(messageId, COMMAND_STATES.ERROR); const message = this.trackedMessages.get(messageId); + // Silent ignore for non-commands/partials to avoid noisy inline errors + if (/No valid command block|Missing required field:\s*action/i.test(error.message)) return; if (message) { UIFeedback.appendStatus(message.element, 'ERROR', { action: 'Command', details: error.message }); } @@ -756,32 +980,52 @@ cleanupProcessedCommands() { const now = Date.now(); + let count = 0; for (const [id, msg] of this.trackedMessages.entries()) { if ((msg.state === COMMAND_STATES.COMPLETE || msg.state === COMMAND_STATES.ERROR) && now - (msg.lastUpdate || now) > CONFIG.CLEANUP_AFTER_MS) { this.trackedMessages.delete(id); + count++; } } + if (count) RC_DEBUG?.info(`Cleaned ${count} processed entries`); } stopAllProcessing() { this.trackedMessages.clear(); if (this.observer) this.observer.disconnect(); + if (this.cleanupIntervalId) { + clearInterval(this.cleanupIntervalId); + this.cleanupIntervalId = null; + } } setupEmergencyStop() { window.AI_REPO_STOP = () => { + // Critical: stop API + pause runtime + cancel inflight + clear interval CONFIG.ENABLE_API = false; + CONFIG.RUNTIME.PAUSED = true; + + for (const [id, msg] of this.trackedMessages.entries()) { + if (msg.state === COMMAND_STATES.EXECUTING || msg.state === COMMAND_STATES.DEBOUNCING) { + RC_DEBUG?.error('Emergency stop - cancelling command', { messageId: id }); + this.updateState(id, COMMAND_STATES.ERROR); + } + } + this.stopAllProcessing(); - this.log('EMERGENCY STOP ACTIVATED'); - GM_notification({ text: 'AI Repo Commander Emergency Stop Activated', title: 'Safety System', timeout: 5000 }); + RC_DEBUG?.error('🚨 EMERGENCY STOP ACTIVATED 🚨'); + GM_notification({ text: 'All command processing stopped', title: 'Emergency Stop', timeout: 5000 }); }; } - log(...args) { if (CONFIG.DEBUG_MODE) console.log('[AI Repo Commander]', ...args); } + log(...args) { + const [msg, data] = (typeof args[0] === 'string') ? [args[0], args[1]] : ['(log)', args]; + RC_DEBUG?.verbose(msg, data); + } } - // ---------------------- Test commands ---------------------- + // ---------------------- Test commands (unchanged) ---------------------- const TEST_COMMANDS = { validUpdate: `\ @@ -821,6 +1065,8 @@ path: . // ---------------------- Init ---------------------- let commandMonitor; function initializeRepoCommander() { + if (!RC_DEBUG) RC_DEBUG = new DebugConsole(CONFIG); + if (!commandMonitor) { commandMonitor = new CommandMonitor(); window.AI_REPO_COMMANDER = { @@ -831,10 +1077,10 @@ path: . history: commandMonitor.history, submitComposer // expose for quick testing }; - console.log('AI Repo Commander fully initialized'); - console.log('API Enabled:', CONFIG.ENABLE_API); - console.log('Test commands available in window.AI_REPO_COMMANDER.test'); - console.log('Reset history: window.AI_REPO_COMMANDER.history.reset() or AI_REPO_CLEAR_HISTORY()'); + RC_DEBUG?.info('AI Repo Commander fully initialized'); + RC_DEBUG?.info('API Enabled:', { value: CONFIG.ENABLE_API }); + RC_DEBUG?.info('Test commands available in window.AI_REPO_COMMANDER.test'); + RC_DEBUG?.info('Reset history with window.AI_REPO_COMMANDER.history.reset() or AI_REPO_CLEAR_HISTORY()'); } }