document.addEventListener('DOMContentLoaded', function() { const closeSettingsBtn = document.getElementById('closeSettings'); const advancedModeToggle = document.getElementById('advancedModeToggle'); const webhookUrlInput = document.getElementById('webhookUrl'); const webhookPayloadInput = document.getElementById('webhookPayload'); const debugModeToggle = document.getElementById('debugModeToggle'); const webhookStatus = document.getElementById('webhookStatus'); const testWebhookBtn = document.getElementById('testWebhook'); const mcpServerUrlInput = document.getElementById('mcpServerUrl'); const mcpApiKeyInput = document.getElementById('mcpApiKey'); const toggleMcpKeyBtn = document.getElementById('toggleMcpKey'); const mcpStatus = document.getElementById('mcpStatus'); const testMcpBtn = document.getElementById('testMcp'); const cloudProviderInput = document.getElementById('cloudProvider'); const cloudEndpointInput = document.getElementById('cloudEndpoint'); const cloudStatus = document.getElementById('cloudStatus'); const testCloudBtn = document.getElementById('testCloud'); const gcalToolNameInput = document.getElementById('gcalToolName'); const gcalCalendarIdInput = document.getElementById('gcalCalendarId'); const gcalMaxResultsInput = document.getElementById('gcalMaxResults'); const gcalStatus = document.getElementById('gcalStatus'); const fetchCalendarBtn = document.getElementById('fetchCalendar'); const gcalOutput = document.getElementById('gcalOutput'); const refreshMcpToolsBtn = document.getElementById('refreshMcpTools'); const mcpToolsStatus = document.getElementById('mcpToolsStatus'); const mcpToolSelect = document.getElementById('mcpToolSelect'); const mcpToolArgs = document.getElementById('mcpToolArgs'); const runMcpToolBtn = document.getElementById('runMcpTool'); const mcpToolOutput = document.getElementById('mcpToolOutput'); const presetNameInput = document.getElementById('presetName'); const presetDescriptionInput = document.getElementById('presetDescription'); const presetTagsInput = document.getElementById('presetTags'); const presetToolNameInput = document.getElementById('presetToolName'); const presetArgsInput = document.getElementById('presetArgs'); const savePresetBtn = document.getElementById('savePreset'); const clearPresetBtn = document.getElementById('clearPreset'); const presetStatus = document.getElementById('presetStatus'); const presetSelect = document.getElementById('presetSelect'); const runPresetBtn = document.getElementById('runPreset'); const deletePresetBtn = document.getElementById('deletePreset'); const presetOutput = document.getElementById('presetOutput'); const automationList = document.getElementById('automationList'); const addAutomationBtn = document.getElementById('addAutomation'); const automationEditor = document.getElementById('automationEditor'); const automationEditorStatus = document.getElementById('automationEditorStatus'); const automationNameInput = document.getElementById('automationName'); const automationTypeSelect = document.getElementById('automationType'); const automationEnabledToggle = document.getElementById('automationEnabled'); const automationTriggerStart = document.getElementById('automationTriggerStart'); const automationTriggerEnd = document.getElementById('automationTriggerEnd'); const automationTriggerManual = document.getElementById('automationTriggerManual'); const automationRequireApproval = document.getElementById('automationRequireApproval'); const automationActionsSection = document.getElementById('automationActionsSection'); const automationStandupSection = document.getElementById('automationStandupSection'); const automationActionLabel = document.getElementById('automationActionLabel'); const automationActionType = document.getElementById('automationActionType'); const automationActionMcpFields = document.getElementById('automationActionMcpFields'); const automationActionWebhookFields = document.getElementById('automationActionWebhookFields'); const automationActionTool = document.getElementById('automationActionTool'); const automationActionArgs = document.getElementById('automationActionArgs'); const automationActionWebhookUrl = document.getElementById('automationActionWebhookUrl'); const automationActionWebhookMethod = document.getElementById('automationActionWebhookMethod'); const automationActionWebhookHeaders = document.getElementById('automationActionWebhookHeaders'); const automationActionWebhookBody = document.getElementById('automationActionWebhookBody'); const automationActionWebhookRetryCount = document.getElementById('automationActionWebhookRetryCount'); const addAutomationActionBtn = document.getElementById('addAutomationAction'); const automationActionSelect = document.getElementById('automationActionSelect'); const removeAutomationActionBtn = document.getElementById('removeAutomationAction'); const standupDiscordTool = document.getElementById('standupDiscordTool'); const standupDiscordArgs = document.getElementById('standupDiscordArgs'); const standupNextcloudTool = document.getElementById('standupNextcloudTool'); const standupNextcloudArgs = document.getElementById('standupNextcloudArgs'); const saveAutomationBtn = document.getElementById('saveAutomation'); const testAutomationBtn = document.getElementById('testAutomation'); const runAutomationNowBtn = document.getElementById('runAutomationNow'); const deleteAutomationBtn = document.getElementById('deleteAutomation'); const automationRunStatus = document.getElementById('automationRunStatus'); const automationOutput = document.getElementById('automationOutput'); const placeholderStatus = document.getElementById('placeholderStatus'); const DEFAULT_SETTINGS = { advancedMode: false, debugMode: false, webhookUrl: '', webhookPayload: '', mcpServerUrl: '', mcpApiKey: '', cloudProvider: '', cloudEndpoint: '', gcalToolName: '', gcalCalendarId: 'primary', gcalMaxResults: 5, customToolPresets: [], automations: [] }; let activeAutomationId = null; let activeAutomationActionId = ''; let lastFocusedInput = null; chrome.storage.sync.get(['advancedSettings'], (result) => { const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) }; if (advancedModeToggle) advancedModeToggle.checked = Boolean(settings.advancedMode); if (debugModeToggle) debugModeToggle.checked = Boolean(settings.debugMode); if (webhookUrlInput) webhookUrlInput.value = settings.webhookUrl || ''; if (webhookPayloadInput) webhookPayloadInput.value = settings.webhookPayload || ''; if (mcpServerUrlInput) mcpServerUrlInput.value = settings.mcpServerUrl || ''; if (mcpApiKeyInput) mcpApiKeyInput.value = settings.mcpApiKey || ''; if (cloudProviderInput) cloudProviderInput.value = settings.cloudProvider || ''; if (cloudEndpointInput) cloudEndpointInput.value = settings.cloudEndpoint || ''; if (gcalToolNameInput) gcalToolNameInput.value = settings.gcalToolName || ''; if (gcalCalendarIdInput) gcalCalendarIdInput.value = settings.gcalCalendarId || 'primary'; if (gcalMaxResultsInput) gcalMaxResultsInput.value = settings.gcalMaxResults || 5; updatePresetSelect(settings.customToolPresets || []); hydrateAutomations(settings.automations || []); }); function saveSettings(partial) { chrome.storage.sync.get(['advancedSettings'], (result) => { const nextSettings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}), ...partial }; chrome.storage.sync.set({ advancedSettings: nextSettings }); }); } function isValidUrl(value) { try { const url = new URL(value); return Boolean(url.protocol && url.hostname); } catch (error) { return false; } } function setStatus(el, text, type) { if (!el) return; el.textContent = text; el.className = 'status-message'; if (type) el.classList.add(type); } function setOutput(el, text) { if (!el) return; el.textContent = text || ''; } function setPlaceholderStatus(text, type) { if (!placeholderStatus) return; placeholderStatus.textContent = text; placeholderStatus.className = 'status-message'; if (type) placeholderStatus.classList.add(type); } function updatePresetSelect(presets) { if (!presetSelect) return; presetSelect.innerHTML = ''; if (!presets || presets.length === 0) { const option = document.createElement('option'); option.value = ''; option.textContent = 'No presets saved'; presetSelect.appendChild(option); return; } presets.forEach((preset) => { const option = document.createElement('option'); option.value = preset.id; option.textContent = preset.name; presetSelect.appendChild(option); }); } function hydrateAutomations(automations) { renderAutomationList(automations); if (!activeAutomationId && automations.length > 0) { activeAutomationId = automations[0].id; } renderAutomationEditor(automations); } function renderAutomationList(automations) { if (!automationList) return; automationList.innerHTML = ''; if (!automations.length) { const empty = document.createElement('div'); empty.className = 'status-message'; empty.textContent = 'No automations yet.'; automationList.appendChild(empty); return; } automations.forEach((automation) => { const item = document.createElement('button'); item.type = 'button'; item.className = `automation-item${automation.id === activeAutomationId ? ' active' : ''}`; item.textContent = automation.name || 'Untitled Automation'; item.addEventListener('click', () => { activeAutomationId = automation.id; renderAutomationList(automations); renderAutomationEditor(automations); }); automationList.appendChild(item); }); } function renderAutomationEditor(automations) { if (!automationEditor || !automationEditorStatus) return; const automation = automations.find((item) => item.id === activeAutomationId); if (!automation) { automationEditor.style.display = 'none'; automationEditorStatus.textContent = 'Select an automation to edit.'; return; } automationEditor.style.display = 'block'; automationEditorStatus.textContent = ''; if (automationNameInput) automationNameInput.value = automation.name || ''; if (automationTypeSelect) automationTypeSelect.value = automation.kind || 'actions'; if (automationEnabledToggle) automationEnabledToggle.checked = Boolean(automation.enabled); if (automationTriggerStart) automationTriggerStart.checked = Boolean(automation.triggers?.sessionStart); if (automationTriggerEnd) automationTriggerEnd.checked = Boolean(automation.triggers?.sessionEnd); if (automationTriggerManual) automationTriggerManual.checked = Boolean(automation.triggers?.manual); if (automationRequireApproval) automationRequireApproval.checked = Boolean(automation.requireApproval); const isStandup = automation.kind === 'standup'; if (automationActionsSection) automationActionsSection.style.display = isStandup ? 'none' : 'block'; if (automationStandupSection) automationStandupSection.style.display = isStandup ? 'block' : 'none'; if (isStandup) { if (standupDiscordTool) standupDiscordTool.value = automation.standup?.discordToolName || ''; if (standupDiscordArgs) standupDiscordArgs.value = automation.standup?.discordArgsTemplate || ''; if (standupNextcloudTool) standupNextcloudTool.value = automation.standup?.nextcloudToolName || ''; if (standupNextcloudArgs) standupNextcloudArgs.value = automation.standup?.nextcloudArgsTemplate || ''; activeAutomationActionId = ''; clearAutomationActionForm(); } else { updateAutomationActionSelect(automation.actions || []); populateAutomationActionEditor(automation.actions || []); } } function updateAutomationActionSelect(actions) { if (!automationActionSelect) return; automationActionSelect.innerHTML = ''; if (!actions || actions.length === 0) { activeAutomationActionId = ''; const option = document.createElement('option'); option.value = ''; option.textContent = 'No actions configured'; automationActionSelect.appendChild(option); return; } actions.forEach((action) => { const option = document.createElement('option'); option.value = action.id; const typeLabel = action.type === 'webhook' ? 'Webhook' : 'MCP'; option.textContent = `${action.label} (${typeLabel})`; automationActionSelect.appendChild(option); }); const selectedStillExists = actions.some((action) => action.id === activeAutomationActionId); activeAutomationActionId = selectedStillExists ? activeAutomationActionId : actions[0].id; automationActionSelect.value = activeAutomationActionId; } function populateAutomationActionEditor(actions) { if (!actions || !actions.length || !automationActionSelect) { clearAutomationActionForm(); return; } const selectedId = automationActionSelect.value || activeAutomationActionId; const action = actions.find((item) => item.id === selectedId) || actions[0]; if (!action) { clearAutomationActionForm(); return; } activeAutomationActionId = action.id; if (automationActionSelect.value !== action.id) { automationActionSelect.value = action.id; } if (automationActionLabel) automationActionLabel.value = action.label || ''; if (automationActionType) automationActionType.value = action.type || 'mcp'; if (action.type === 'webhook') { if (automationActionWebhookUrl) automationActionWebhookUrl.value = action.webhookUrl || ''; if (automationActionWebhookMethod) automationActionWebhookMethod.value = action.method || 'POST'; if (automationActionWebhookHeaders) automationActionWebhookHeaders.value = JSON.stringify(action.headers || {}, null, 2); if (automationActionWebhookBody) automationActionWebhookBody.value = action.bodyTemplate || ''; if (automationActionWebhookRetryCount) automationActionWebhookRetryCount.value = String(action.retryCount ?? 0); if (automationActionTool) automationActionTool.value = ''; if (automationActionArgs) automationActionArgs.value = ''; } else { if (automationActionTool) automationActionTool.value = action.toolName || ''; if (automationActionArgs) automationActionArgs.value = JSON.stringify(action.args || {}, null, 2); if (automationActionWebhookUrl) automationActionWebhookUrl.value = ''; if (automationActionWebhookMethod) automationActionWebhookMethod.value = 'POST'; if (automationActionWebhookHeaders) automationActionWebhookHeaders.value = ''; if (automationActionWebhookBody) automationActionWebhookBody.value = ''; if (automationActionWebhookRetryCount) automationActionWebhookRetryCount.value = '0'; } updateActionTypeUI(); } function updateAutomation(automationId, updater) { chrome.storage.sync.get(['advancedSettings'], (result) => { const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) }; const automations = Array.isArray(settings.automations) ? settings.automations : []; const index = automations.findIndex((item) => item.id === automationId); if (index === -1) return; const updated = updater(automations[index]); automations[index] = updated; settings.automations = automations; chrome.storage.sync.set({ advancedSettings: settings }, () => { hydrateAutomations(automations); }); }); } function persistCurrentAutomationFromForm() { if (!activeAutomationId) return; updateAutomation(activeAutomationId, (automation) => { const next = { ...automation }; if (automationNameInput) next.name = automationNameInput.value.trim(); if (automationTypeSelect) next.kind = automationTypeSelect.value; if (automationEnabledToggle) next.enabled = automationEnabledToggle.checked; next.triggers = { sessionStart: Boolean(automationTriggerStart?.checked), sessionEnd: Boolean(automationTriggerEnd?.checked), manual: Boolean(automationTriggerManual?.checked) }; next.requireApproval = Boolean(automationRequireApproval?.checked); if (next.kind === 'standup') { next.standup = { discordToolName: standupDiscordTool?.value.trim() || '', discordArgsTemplate: standupDiscordArgs?.value || '', nextcloudToolName: standupNextcloudTool?.value.trim() || '', nextcloudArgsTemplate: standupNextcloudArgs?.value || '' }; } return next; }); } function persistSelectedAutomationActionFromForm() { if (!activeAutomationId || !activeAutomationActionId) return; updateAutomation(activeAutomationId, (automation) => { const actions = Array.isArray(automation.actions) ? [...automation.actions] : []; const index = actions.findIndex((action) => action.id === activeAutomationActionId); if (index === -1) return automation; const existing = actions[index]; const type = automationActionType ? automationActionType.value : existing.type || 'mcp'; const label = automationActionLabel ? automationActionLabel.value.trim() : existing.label || ''; if (type === 'webhook') { let headers = existing.headers || {}; if (automationActionWebhookHeaders) { const parsedHeaders = readJsonArgs( automationActionWebhookHeaders.value || '', automationRunStatus, 'Webhook headers must be valid JSON.' ); if (parsedHeaders !== null) { headers = parsedHeaders; } } const retryRaw = Number(automationActionWebhookRetryCount ? automationActionWebhookRetryCount.value : existing.retryCount || 0); const retryCount = Number.isFinite(retryRaw) ? Math.max(0, Math.min(5, Math.floor(retryRaw))) : 0; actions[index] = { ...existing, type: 'webhook', label, webhookUrl: automationActionWebhookUrl ? automationActionWebhookUrl.value.trim() : existing.webhookUrl || '', method: automationActionWebhookMethod ? automationActionWebhookMethod.value : existing.method || 'POST', headers, bodyTemplate: automationActionWebhookBody ? automationActionWebhookBody.value : existing.bodyTemplate || '', retryCount }; } else { let args = existing.args || {}; if (automationActionArgs) { const parsedArgs = readJsonArgs( automationActionArgs.value || '', automationRunStatus, 'Action args must be valid JSON.' ); if (parsedArgs !== null) { args = parsedArgs; } } actions[index] = { ...existing, type: 'mcp', label, toolName: automationActionTool ? automationActionTool.value.trim() : existing.toolName || '', args }; } return { ...automation, actions }; }); } function readJsonArgs(text, statusEl, errorMessage) { if (!text.trim()) return {}; try { return JSON.parse(text); } catch (error) { setStatus(statusEl, errorMessage, 'error'); return null; } } function validateJsonField(inputEl, statusEl, label) { if (!inputEl) return; const value = inputEl.value.trim(); if (!value) { setStatus(statusEl, '', ''); return; } try { JSON.parse(value); setStatus(statusEl, `${label} JSON is valid.`, 'success'); } catch (error) { setStatus(statusEl, `${label} JSON is invalid.`, 'error'); } } function updateActionTypeUI() { const type = automationActionType ? automationActionType.value : 'mcp'; if (automationActionMcpFields) automationActionMcpFields.style.display = type === 'mcp' ? 'block' : 'none'; if (automationActionWebhookFields) automationActionWebhookFields.style.display = type === 'webhook' ? 'block' : 'none'; } function clearAutomationActionForm() { if (automationActionLabel) automationActionLabel.value = ''; if (automationActionType) automationActionType.value = 'mcp'; if (automationActionTool) automationActionTool.value = ''; if (automationActionArgs) automationActionArgs.value = ''; if (automationActionWebhookUrl) automationActionWebhookUrl.value = ''; if (automationActionWebhookMethod) automationActionWebhookMethod.value = 'POST'; if (automationActionWebhookHeaders) automationActionWebhookHeaders.value = ''; if (automationActionWebhookBody) automationActionWebhookBody.value = ''; if (automationActionWebhookRetryCount) automationActionWebhookRetryCount.value = '0'; updateActionTypeUI(); } if (advancedModeToggle) { advancedModeToggle.addEventListener('change', function() { saveSettings({ advancedMode: this.checked }); }); } if (debugModeToggle) { debugModeToggle.addEventListener('change', function() { saveSettings({ debugMode: this.checked }); }); } if (webhookUrlInput) { webhookUrlInput.addEventListener('input', function() { saveSettings({ webhookUrl: this.value.trim() }); if (this.value.trim() && !isValidUrl(this.value.trim())) { setStatus(webhookStatus, 'Invalid URL format.', 'error'); } else { setStatus(webhookStatus, '', ''); } }); } if (webhookPayloadInput) { webhookPayloadInput.addEventListener('input', function() { saveSettings({ webhookPayload: this.value }); validateJsonField(webhookPayloadInput, webhookStatus, 'Webhook payload'); }); } if (mcpServerUrlInput) { mcpServerUrlInput.addEventListener('input', function() { saveSettings({ mcpServerUrl: this.value.trim() }); if (this.value.trim() && !isValidUrl(this.value.trim())) { setStatus(mcpStatus, 'Invalid URL format.', 'error'); } else { setStatus(mcpStatus, '', ''); } }); } if (mcpApiKeyInput) { mcpApiKeyInput.addEventListener('input', function() { saveSettings({ mcpApiKey: this.value.trim() }); }); } if (toggleMcpKeyBtn && mcpApiKeyInput) { toggleMcpKeyBtn.addEventListener('click', function() { const isHidden = mcpApiKeyInput.type === 'password'; mcpApiKeyInput.type = isHidden ? 'text' : 'password'; toggleMcpKeyBtn.textContent = isHidden ? 'Hide' : 'Show'; }); } if (cloudProviderInput) { cloudProviderInput.addEventListener('input', function() { saveSettings({ cloudProvider: this.value.trim() }); }); } if (cloudEndpointInput) { cloudEndpointInput.addEventListener('input', function() { saveSettings({ cloudEndpoint: this.value.trim() }); if (this.value.trim() && !isValidUrl(this.value.trim())) { setStatus(cloudStatus, 'Invalid URL format.', 'error'); } else { setStatus(cloudStatus, '', ''); } }); } if (gcalToolNameInput) { gcalToolNameInput.addEventListener('input', function() { saveSettings({ gcalToolName: this.value.trim() }); }); } if (gcalCalendarIdInput) { gcalCalendarIdInput.addEventListener('input', function() { saveSettings({ gcalCalendarId: this.value.trim() || 'primary' }); }); } if (gcalMaxResultsInput) { gcalMaxResultsInput.addEventListener('input', function() { const parsed = Number(this.value); saveSettings({ gcalMaxResults: Number.isFinite(parsed) && parsed > 0 ? parsed : 5 }); }); } if (testWebhookBtn) { testWebhookBtn.addEventListener('click', async function() { const url = webhookUrlInput ? webhookUrlInput.value.trim() : ''; if (!isValidUrl(url)) { setStatus(webhookStatus, 'Enter a valid webhook URL first.', 'error'); return; } setStatus(webhookStatus, 'Sending test payload...', ''); try { let payload = { type: 'test', source: 'ai-assistant', timestamp: new Date().toISOString() }; if (webhookPayloadInput && webhookPayloadInput.value.trim()) { payload = JSON.parse(webhookPayloadInput.value); } const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const debugEnabled = debugModeToggle ? debugModeToggle.checked : false; if (!response.ok) { const errorText = await response.text(); const message = debugEnabled && errorText ? `Webhook responded with ${response.status}: ${errorText}` : `Webhook responded with ${response.status}.`; setStatus(webhookStatus, message, 'error'); return; } if (debugEnabled) { const responseText = await response.text(); if (responseText) { setStatus(webhookStatus, `Webhook test successful: ${responseText}`, 'success'); return; } } setStatus(webhookStatus, 'Webhook test successful.', 'success'); } catch (error) { const debugEnabled = debugModeToggle ? debugModeToggle.checked : false; const message = debugEnabled && error.stack ? `${error.message}\n${error.stack}` : error.message || 'Webhook test failed.'; setStatus(webhookStatus, message, 'error'); } }); } if (testMcpBtn) { testMcpBtn.addEventListener('click', async function() { const url = mcpServerUrlInput ? mcpServerUrlInput.value.trim() : ''; if (!isValidUrl(url)) { setStatus(mcpStatus, 'Enter a valid MCP server URL first.', 'error'); return; } setStatus(mcpStatus, 'Checking MCP server...', ''); try { let healthUrl = url.replace(/\/$/, ''); if (healthUrl.endsWith('/mcp/health')) { // leave as is } else if (healthUrl.endsWith('/mcp')) { healthUrl = `${healthUrl}/health`; } else if (!healthUrl.endsWith('/health')) { healthUrl = `${healthUrl}/mcp/health`; } const headers = {}; const apiKey = mcpApiKeyInput ? mcpApiKeyInput.value.trim() : ''; if (apiKey) { headers['x-mcp-api-key'] = apiKey; headers.Authorization = `Bearer ${apiKey}`; } const response = await fetch(healthUrl, { method: 'GET', headers }); if (!response.ok) { setStatus(mcpStatus, `MCP server responded with ${response.status}.`, 'error'); return; } setStatus(mcpStatus, 'MCP server is reachable.', 'success'); } catch (error) { setStatus(mcpStatus, error.message || 'MCP test failed.', 'error'); } }); } if (testCloudBtn) { testCloudBtn.addEventListener('click', async function() { const endpoint = cloudEndpointInput ? cloudEndpointInput.value.trim() : ''; if (!endpoint || !isValidUrl(endpoint)) { setStatus(cloudStatus, 'Enter a valid cloud endpoint first.', 'error'); return; } setStatus(cloudStatus, 'Validating cloud endpoint...', ''); try { const response = await fetch(endpoint, { method: 'GET' }); if (!response.ok) { setStatus(cloudStatus, `Cloud endpoint responded with ${response.status}.`, 'error'); return; } setStatus(cloudStatus, 'Cloud endpoint is reachable.', 'success'); } catch (error) { setStatus(cloudStatus, error.message || 'Cloud test failed.', 'error'); } }); } if (fetchCalendarBtn) { fetchCalendarBtn.addEventListener('click', function() { const toolName = gcalToolNameInput ? gcalToolNameInput.value.trim() : ''; if (!toolName) { setStatus(gcalStatus, 'Set the Google Calendar tool name first.', 'error'); return; } const calendarId = gcalCalendarIdInput ? gcalCalendarIdInput.value.trim() || 'primary' : 'primary'; const maxResultsRaw = gcalMaxResultsInput ? Number(gcalMaxResultsInput.value) : 5; const maxResults = Number.isFinite(maxResultsRaw) && maxResultsRaw > 0 ? maxResultsRaw : 5; setStatus(gcalStatus, 'Fetching events...', ''); setOutput(gcalOutput, ''); chrome.runtime.sendMessage( { action: 'mcp:callTool', toolName, args: { calendarId, maxResults, timeMin: new Date().toISOString() } }, (response) => { if (chrome.runtime.lastError || !response) { setStatus(gcalStatus, 'Failed to fetch events.', 'error'); return; } if (!response.success) { setStatus(gcalStatus, response.error || 'Failed to fetch events.', 'error'); return; } setStatus(gcalStatus, 'Events loaded.', 'success'); setOutput(gcalOutput, JSON.stringify(response.result, null, 2)); } ); }); } if (refreshMcpToolsBtn) { refreshMcpToolsBtn.addEventListener('click', function() { setStatus(mcpToolsStatus, 'Fetching tools...', ''); chrome.runtime.sendMessage({ action: 'mcp:listTools' }, (response) => { if (chrome.runtime.lastError || !response) { setStatus(mcpToolsStatus, 'Failed to fetch tools.', 'error'); return; } if (!response.success) { setStatus(mcpToolsStatus, response.error || 'Failed to fetch tools.', 'error'); return; } if (mcpToolSelect) { mcpToolSelect.innerHTML = ''; const tools = response.tools || []; if (!tools.length) { const option = document.createElement('option'); option.value = ''; option.textContent = 'No tools available'; mcpToolSelect.appendChild(option); } else { tools.forEach((tool) => { const option = document.createElement('option'); option.value = tool.name || ''; option.textContent = tool.name || 'Unnamed tool'; mcpToolSelect.appendChild(option); }); } } setStatus(mcpToolsStatus, `Loaded ${response.tools?.length || 0} tools.`, 'success'); }); }); } if (runMcpToolBtn) { runMcpToolBtn.addEventListener('click', function() { const toolName = mcpToolSelect ? mcpToolSelect.value : ''; if (!toolName) { setStatus(mcpToolsStatus, 'Select a tool first.', 'error'); return; } let args = {}; if (mcpToolArgs && mcpToolArgs.value.trim()) { try { args = JSON.parse(mcpToolArgs.value); } catch (error) { setStatus(mcpToolsStatus, 'Arguments must be valid JSON.', 'error'); return; } } setStatus(mcpToolsStatus, 'Running tool...', ''); setOutput(mcpToolOutput, ''); chrome.runtime.sendMessage({ action: 'mcp:callTool', toolName, args }, (response) => { if (chrome.runtime.lastError || !response) { setStatus(mcpToolsStatus, 'Tool call failed.', 'error'); return; } if (!response.success) { setStatus(mcpToolsStatus, response.error || 'Tool call failed.', 'error'); return; } setStatus(mcpToolsStatus, 'Tool executed.', 'success'); setOutput(mcpToolOutput, JSON.stringify(response.result, null, 2)); }); }); } if (mcpToolArgs) { mcpToolArgs.addEventListener('input', function() { validateJsonField(mcpToolArgs, mcpToolsStatus, 'Tool args'); }); } if (savePresetBtn) { savePresetBtn.addEventListener('click', function() { const name = presetNameInput ? presetNameInput.value.trim() : ''; const toolName = presetToolNameInput ? presetToolNameInput.value.trim() : ''; if (!name || !toolName) { setStatus(presetStatus, 'Preset name and tool name are required.', 'error'); return; } let args = {}; if (presetArgsInput && presetArgsInput.value.trim()) { const parsed = readJsonArgs(presetArgsInput.value, presetStatus, 'Preset arguments must be valid JSON.'); if (parsed === null) return; args = parsed; } const tags = presetTagsInput ? presetTagsInput.value.split(',').map((tag) => tag.trim()).filter(Boolean) : []; chrome.storage.sync.get(['advancedSettings'], (result) => { const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) }; const presets = Array.isArray(settings.customToolPresets) ? settings.customToolPresets : []; const existingIndex = presets.findIndex((item) => item.name.toLowerCase() === name.toLowerCase()); const nextPreset = { id: existingIndex >= 0 ? presets[existingIndex].id : `${Date.now()}_${Math.random().toString(16).slice(2)}`, name, description: presetDescriptionInput ? presetDescriptionInput.value.trim() : '', tags, toolName, args }; if (existingIndex >= 0) { presets[existingIndex] = nextPreset; } else { presets.push(nextPreset); } settings.customToolPresets = presets; chrome.storage.sync.set({ advancedSettings: settings }, () => { updatePresetSelect(presets); setStatus(presetStatus, existingIndex >= 0 ? 'Preset updated.' : 'Preset saved.', 'success'); }); }); }); } if (presetArgsInput) { presetArgsInput.addEventListener('input', function() { validateJsonField(presetArgsInput, presetStatus, 'Preset args'); }); } if (clearPresetBtn) { clearPresetBtn.addEventListener('click', function() { if (presetNameInput) presetNameInput.value = ''; if (presetDescriptionInput) presetDescriptionInput.value = ''; if (presetTagsInput) presetTagsInput.value = ''; if (presetToolNameInput) presetToolNameInput.value = ''; if (presetArgsInput) presetArgsInput.value = ''; setStatus(presetStatus, '', ''); }); } if (presetSelect) { presetSelect.addEventListener('change', function() { const id = presetSelect.value; if (!id) return; chrome.storage.sync.get(['advancedSettings'], (result) => { const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) }; const presets = Array.isArray(settings.customToolPresets) ? settings.customToolPresets : []; const preset = presets.find((item) => item.id === id); if (!preset) return; if (presetNameInput) presetNameInput.value = preset.name; if (presetDescriptionInput) presetDescriptionInput.value = preset.description || ''; if (presetTagsInput) presetTagsInput.value = Array.isArray(preset.tags) ? preset.tags.join(', ') : ''; if (presetToolNameInput) presetToolNameInput.value = preset.toolName; if (presetArgsInput) presetArgsInput.value = JSON.stringify(preset.args || {}, null, 2); }); }); } if (runPresetBtn) { runPresetBtn.addEventListener('click', function() { const id = presetSelect ? presetSelect.value : ''; if (!id) { setStatus(presetStatus, 'Select a preset first.', 'error'); return; } chrome.storage.sync.get(['advancedSettings'], (result) => { const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) }; const presets = Array.isArray(settings.customToolPresets) ? settings.customToolPresets : []; const preset = presets.find((item) => item.id === id); if (!preset) { setStatus(presetStatus, 'Preset not found.', 'error'); return; } setStatus(presetStatus, 'Running preset...', ''); setOutput(presetOutput, ''); chrome.runtime.sendMessage( { action: 'mcp:callTool', toolName: preset.toolName, args: preset.args || {} }, (response) => { if (chrome.runtime.lastError || !response) { setStatus(presetStatus, 'Preset failed.', 'error'); return; } if (!response.success) { setStatus(presetStatus, response.error || 'Preset failed.', 'error'); return; } setStatus(presetStatus, 'Preset executed.', 'success'); setOutput(presetOutput, JSON.stringify(response.result, null, 2)); } ); }); }); } if (deletePresetBtn) { deletePresetBtn.addEventListener('click', function() { const id = presetSelect ? presetSelect.value : ''; if (!id) { setStatus(presetStatus, 'Select a preset first.', 'error'); return; } chrome.storage.sync.get(['advancedSettings'], (result) => { const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) }; const presets = Array.isArray(settings.customToolPresets) ? settings.customToolPresets : []; const nextPresets = presets.filter((item) => item.id !== id); settings.customToolPresets = nextPresets; chrome.storage.sync.set({ advancedSettings: settings }, () => { updatePresetSelect(nextPresets); if (presetNameInput) presetNameInput.value = ''; if (presetDescriptionInput) presetDescriptionInput.value = ''; if (presetTagsInput) presetTagsInput.value = ''; if (presetToolNameInput) presetToolNameInput.value = ''; if (presetArgsInput) presetArgsInput.value = ''; setOutput(presetOutput, ''); setStatus(presetStatus, 'Preset deleted.', 'success'); }); }); }); } if (addAutomationBtn) { addAutomationBtn.addEventListener('click', function() { chrome.storage.sync.get(['advancedSettings'], (result) => { const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) }; const automations = Array.isArray(settings.automations) ? settings.automations : []; const newAutomation = { id: `${Date.now()}_${Math.random().toString(16).slice(2)}`, name: 'New automation', kind: 'actions', enabled: true, triggers: { sessionStart: false, sessionEnd: true, manual: true }, requireApproval: true, actions: [], standup: { discordToolName: '', discordArgsTemplate: '', nextcloudToolName: '', nextcloudArgsTemplate: '{\n \"path\": \"notes/daily/standup-{{date}}.txt\",\n \"content\": \"Daily Standup - {{date_human}}\\\\n\\\\nSummary\\\\n{{summary_full}}\\\\n\\\\nAction Items\\\\n{{action_items}}\\\\n\\\\nBlockers\\\\n{{blockers}}\\\\n\\\\nDecisions\\\\n{{decisions}}\"\n}' } }; automations.push(newAutomation); settings.automations = automations; chrome.storage.sync.set({ advancedSettings: settings }, () => { activeAutomationId = newAutomation.id; hydrateAutomations(automations); }); }); }); } if (automationNameInput) { automationNameInput.addEventListener('input', function() { if (!activeAutomationId) return; updateAutomation(activeAutomationId, (automation) => ({ ...automation, name: this.value.trim() })); }); } if (automationTypeSelect) { automationTypeSelect.addEventListener('change', function() { if (!activeAutomationId) return; updateAutomation(activeAutomationId, (automation) => ({ ...automation, kind: this.value })); }); } if (automationEnabledToggle) { automationEnabledToggle.addEventListener('change', function() { if (!activeAutomationId) return; updateAutomation(activeAutomationId, (automation) => ({ ...automation, enabled: this.checked })); }); } if (automationTriggerStart) { automationTriggerStart.addEventListener('change', function() { if (!activeAutomationId) return; updateAutomation(activeAutomationId, (automation) => ({ ...automation, triggers: { ...automation.triggers, sessionStart: this.checked } })); }); } if (automationTriggerEnd) { automationTriggerEnd.addEventListener('change', function() { if (!activeAutomationId) return; updateAutomation(activeAutomationId, (automation) => ({ ...automation, triggers: { ...automation.triggers, sessionEnd: this.checked } })); }); } if (automationTriggerManual) { automationTriggerManual.addEventListener('change', function() { if (!activeAutomationId) return; updateAutomation(activeAutomationId, (automation) => ({ ...automation, triggers: { ...automation.triggers, manual: this.checked } })); }); } if (automationRequireApproval) { automationRequireApproval.addEventListener('change', function() { if (!activeAutomationId) return; updateAutomation(activeAutomationId, (automation) => ({ ...automation, requireApproval: this.checked })); }); } if (addAutomationActionBtn) { addAutomationActionBtn.addEventListener('click', function() { if (!activeAutomationId) return; const label = automationActionLabel ? automationActionLabel.value.trim() : ''; const type = automationActionType ? automationActionType.value : 'mcp'; if (!label) { setStatus(automationRunStatus, 'Action label is required.', 'error'); return; } let nextAction = null; if (type === 'mcp') { const toolName = automationActionTool ? automationActionTool.value.trim() : ''; if (!toolName) { setStatus(automationRunStatus, 'MCP tool name is required.', 'error'); return; } const args = readJsonArgs(automationActionArgs?.value || '', automationRunStatus, 'Action args must be valid JSON.'); if (args === null) return; nextAction = { id: `${Date.now()}_${Math.random().toString(16).slice(2)}`, type: 'mcp', label, toolName, args }; } else { const url = automationActionWebhookUrl ? automationActionWebhookUrl.value.trim() : ''; const method = automationActionWebhookMethod ? automationActionWebhookMethod.value : 'POST'; const headers = readJsonArgs( automationActionWebhookHeaders?.value || '', automationRunStatus, 'Webhook headers must be valid JSON.' ); if (headers === null) return; const bodyTemplate = automationActionWebhookBody ? automationActionWebhookBody.value.trim() : ''; const retryRaw = Number(automationActionWebhookRetryCount ? automationActionWebhookRetryCount.value : 0); const retryCount = Number.isFinite(retryRaw) ? Math.max(0, Math.min(5, Math.floor(retryRaw))) : 0; nextAction = { id: `${Date.now()}_${Math.random().toString(16).slice(2)}`, type: 'webhook', label, webhookUrl: url, method, headers, bodyTemplate, retryCount }; } updateAutomation(activeAutomationId, (automation) => { const actions = Array.isArray(automation.actions) ? automation.actions : []; actions.push(nextAction); return { ...automation, actions }; }); activeAutomationActionId = nextAction.id; setStatus(automationRunStatus, 'Action added.', 'success'); }); } if (automationActionArgs) { automationActionArgs.addEventListener('input', function() { validateJsonField(automationActionArgs, automationRunStatus, 'Action args'); }); automationActionArgs.addEventListener('change', function() { persistSelectedAutomationActionFromForm(); }); } if (automationActionWebhookHeaders) { automationActionWebhookHeaders.addEventListener('input', function() { validateJsonField(automationActionWebhookHeaders, automationRunStatus, 'Webhook headers'); }); automationActionWebhookHeaders.addEventListener('change', function() { persistSelectedAutomationActionFromForm(); }); } if (automationActionType) { automationActionType.addEventListener('change', function() { updateActionTypeUI(); persistSelectedAutomationActionFromForm(); }); } if (automationActionLabel) { automationActionLabel.addEventListener('change', function() { persistSelectedAutomationActionFromForm(); }); } if (automationActionTool) { automationActionTool.addEventListener('change', function() { persistSelectedAutomationActionFromForm(); }); } if (automationActionWebhookUrl) { automationActionWebhookUrl.addEventListener('change', function() { persistSelectedAutomationActionFromForm(); }); } if (automationActionWebhookMethod) { automationActionWebhookMethod.addEventListener('change', function() { persistSelectedAutomationActionFromForm(); }); } if (automationActionWebhookBody) { automationActionWebhookBody.addEventListener('change', function() { persistSelectedAutomationActionFromForm(); }); } if (automationActionWebhookRetryCount) { automationActionWebhookRetryCount.addEventListener('change', function() { persistSelectedAutomationActionFromForm(); }); } if (automationActionSelect) { automationActionSelect.addEventListener('change', function() { activeAutomationActionId = this.value || ''; chrome.storage.sync.get(['advancedSettings'], (result) => { const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) }; const automations = Array.isArray(settings.automations) ? settings.automations : []; const automation = automations.find((item) => item.id === activeAutomationId); if (!automation) return; populateAutomationActionEditor(automation.actions || []); }); }); } if (removeAutomationActionBtn) { removeAutomationActionBtn.addEventListener('click', function() { if (!activeAutomationId) return; const id = automationActionSelect ? automationActionSelect.value : ''; if (!id) { setStatus(automationRunStatus, 'Select an action first.', 'error'); return; } updateAutomation(activeAutomationId, (automation) => { const actions = Array.isArray(automation.actions) ? automation.actions : []; return { ...automation, actions: actions.filter((action) => action.id !== id) }; }); activeAutomationActionId = ''; }); } if (standupDiscordTool) { standupDiscordTool.addEventListener('input', function() { if (!activeAutomationId) return; updateAutomation(activeAutomationId, (automation) => ({ ...automation, standup: { ...automation.standup, discordToolName: this.value.trim() } })); }); } if (standupDiscordArgs) { standupDiscordArgs.addEventListener('input', function() { if (!activeAutomationId) return; updateAutomation(activeAutomationId, (automation) => ({ ...automation, standup: { ...automation.standup, discordArgsTemplate: this.value } })); validateJsonField(standupDiscordArgs, automationRunStatus, 'Standup Discord args'); }); } if (standupNextcloudTool) { standupNextcloudTool.addEventListener('input', function() { if (!activeAutomationId) return; updateAutomation(activeAutomationId, (automation) => ({ ...automation, standup: { ...automation.standup, nextcloudToolName: this.value.trim() } })); }); } if (standupNextcloudArgs) { standupNextcloudArgs.addEventListener('input', function() { if (!activeAutomationId) return; updateAutomation(activeAutomationId, (automation) => ({ ...automation, standup: { ...automation.standup, nextcloudArgsTemplate: this.value } })); validateJsonField(standupNextcloudArgs, automationRunStatus, 'Standup Nextcloud args'); }); } if (runAutomationNowBtn) { runAutomationNowBtn.addEventListener('click', function() { if (!activeAutomationId) return; persistCurrentAutomationFromForm(); persistSelectedAutomationActionFromForm(); setStatus(automationRunStatus, 'Running automation...', ''); setOutput(automationOutput, ''); chrome.runtime.sendMessage({ action: 'automation:run', trigger: 'manual', automationId: activeAutomationId }, (response) => { if (chrome.runtime.lastError || !response) { setStatus(automationRunStatus, 'Automation failed.', 'error'); return; } if (!response.success) { setStatus(automationRunStatus, response.error || 'Automation failed.', 'error'); return; } setStatus(automationRunStatus, 'Automation completed.', 'success'); setOutput(automationOutput, JSON.stringify(response.results || response.result || {}, null, 2)); }); }); } if (saveAutomationBtn) { saveAutomationBtn.addEventListener('click', function() { if (!activeAutomationId) return; persistCurrentAutomationFromForm(); persistSelectedAutomationActionFromForm(); setStatus(automationRunStatus, 'Automation saved.', 'success'); }); } if (testAutomationBtn) { testAutomationBtn.addEventListener('click', function() { if (!activeAutomationId) return; persistCurrentAutomationFromForm(); persistSelectedAutomationActionFromForm(); setStatus(automationRunStatus, 'Running test...', ''); setOutput(automationOutput, ''); chrome.runtime.sendMessage( { action: 'automation:run', trigger: 'manual', automationId: activeAutomationId, testMode: true }, (response) => { if (chrome.runtime.lastError || !response) { setStatus(automationRunStatus, 'Test failed.', 'error'); return; } if (!response.success) { setStatus(automationRunStatus, response.error || 'Test failed.', 'error'); return; } setStatus(automationRunStatus, 'Test completed.', 'success'); setOutput(automationOutput, JSON.stringify(response.results || response.result || {}, null, 2)); } ); }); } if (deleteAutomationBtn) { deleteAutomationBtn.addEventListener('click', function() { if (!activeAutomationId) return; chrome.storage.sync.get(['advancedSettings'], (result) => { const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) }; const automations = Array.isArray(settings.automations) ? settings.automations : []; const nextAutomations = automations.filter((item) => item.id !== activeAutomationId); settings.automations = nextAutomations; chrome.storage.sync.set({ advancedSettings: settings }, () => { activeAutomationId = nextAutomations[0]?.id || null; hydrateAutomations(nextAutomations); setStatus(automationRunStatus, 'Automation deleted.', 'success'); setOutput(automationOutput, ''); }); }); }); } document.querySelectorAll('input[type=\"text\"], textarea').forEach((field) => { field.addEventListener('focus', () => { lastFocusedInput = field; }); }); document.querySelectorAll('.placeholder-chip').forEach((chip) => { chip.addEventListener('click', async function() { const placeholder = chip.getAttribute('data-placeholder'); if (!placeholder) return; if (!lastFocusedInput) { if (standupDiscordArgs) { standupDiscordArgs.focus(); lastFocusedInput = standupDiscordArgs; } else if (standupNextcloudArgs) { standupNextcloudArgs.focus(); lastFocusedInput = standupNextcloudArgs; } } if (lastFocusedInput && typeof lastFocusedInput.selectionStart === 'number') { const start = lastFocusedInput.selectionStart; const end = lastFocusedInput.selectionEnd; const value = lastFocusedInput.value || ''; lastFocusedInput.value = value.slice(0, start) + placeholder + value.slice(end); lastFocusedInput.selectionStart = lastFocusedInput.selectionEnd = start + placeholder.length; lastFocusedInput.dispatchEvent(new Event('input', { bubbles: true })); setPlaceholderStatus(`Inserted ${placeholder}.`, 'success'); return; } try { await navigator.clipboard.writeText(placeholder); setPlaceholderStatus(`Copied ${placeholder} to clipboard.`, 'success'); } catch (error) { setPlaceholderStatus('Failed to copy placeholder.', 'error'); } }); }); clearAutomationActionForm(); if (closeSettingsBtn) { closeSettingsBtn.addEventListener('click', function() { window.close(); }); } });