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()');
}
}