const runBtn = document.getElementById("runBtn");
const abortBtn = document.getElementById("abortBtn");
const taskSelect = document.getElementById("taskSelect");
const envSelect = document.getElementById("envSelect");
const profileSelect = document.getElementById("profileSelect");
const outputEl = document.getElementById("output");
const statusEl = document.getElementById("status");
const postingCountEl = document.getElementById("postingCount");
const promptCountEl = document.getElementById("promptCount");
const settingsBtn = document.getElementById("settingsBtn");
const copyRenderedBtn = document.getElementById("copyRenderedBtn");
const copyRawBtn = document.getElementById("copyRawBtn");
const clearOutputBtn = document.getElementById("clearOutputBtn");
const OUTPUT_STORAGE_KEY = "lastOutput";
const AUTO_RUN_KEY = "autoRunDefaultTask";
const LAST_TASK_KEY = "lastSelectedTaskId";
const LAST_ENV_KEY = "lastSelectedEnvId";
const LAST_PROFILE_KEY = "lastSelectedProfileId";
const unknownSiteState = document.getElementById("unknownSiteState");
const extractionReviewState = document.getElementById("extractionReviewState");
const normalExecutionState = document.getElementById("normalExecutionState");
const partialTextPaste = document.getElementById("partialTextPaste");
const extractFullBtn = document.getElementById("extractFullBtn");
const extractedPreview = document.getElementById("extractedPreview");
const urlPatternInput = document.getElementById("urlPatternInput");
const retryExtractBtn = document.getElementById("retryExtractBtn");
const confirmSiteBtn = document.getElementById("confirmSiteBtn");
const currentWorkspaceName = document.getElementById("currentWorkspaceName");
const state = {
siteText: "",
tasks: [],
envs: [],
profiles: [],
sites: [],
workspaces: [],
currentSite: null,
currentWorkspace: null,
port: null,
isAnalyzing: false,
outputRaw: "",
autoRunPending: false,
selectedTaskId: "",
selectedEnvId: "",
selectedProfileId: ""
};
async function switchState(stateName) {
unknownSiteState.classList.add("hidden");
extractionReviewState.classList.add("hidden");
normalExecutionState.classList.add("hidden");
if (stateName === "unknown") {
unknownSiteState.classList.remove("hidden");
} else if (stateName === "review") {
extractionReviewState.classList.remove("hidden");
} else if (stateName === "normal") {
normalExecutionState.classList.remove("hidden");
}
await chrome.storage.local.set({ lastPopupState: stateName });
}
function matchUrl(url, pattern) {
if (!pattern) return false;
const regex = new RegExp("^" + pattern.split("*").join(".*") + "$");
try {
const urlObj = new URL(url);
const target = urlObj.hostname + urlObj.pathname;
return regex.test(target);
} catch {
return false;
}
}
async function detectSite(url) {
const { sites = [], workspaces = [] } = await getStorage(["sites", "workspaces"]);
state.sites = sites;
state.workspaces = workspaces;
const site = sites.find(s => matchUrl(url, s.urlPattern));
if (site) {
state.currentSite = site;
state.currentWorkspace = workspaces.find(w => w.id === site.workspaceId) || { name: "Global", id: "global" };
currentWorkspaceName.textContent = state.currentWorkspace.name;
switchState("normal");
return true;
}
switchState("unknown");
return false;
}
function getStorage(keys) {
return new Promise((resolve) => chrome.storage.local.get(keys, resolve));
}
function buildUserMessage(profileText, taskText, siteText) {
return [
"=== Profile ===",
profileText || "",
"",
"=== Task ===",
taskText || "",
"",
"=== Site Text ===",
siteText || ""
].join("\n");
}
function escapeHtml(text) {
return text
.replace(/&/g, "&")
.replace(//g, ">");
}
function escapeAttribute(text) {
return text.replace(/&/g, "&").replace(/"/g, """);
}
function sanitizeUrl(url) {
const trimmed = url.trim().replace(/&/g, "&");
if (/^https?:\/\//i.test(trimmed)) return trimmed;
return "";
}
function applyInline(text) {
if (!text) return "";
const codeSpans = [];
let output = text.replace(/`([^`]+)`/g, (_match, code) => {
const id = codeSpans.length;
codeSpans.push(code);
return `@@CODESPAN${id}@@`;
});
output = output.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, url) => {
const safeUrl = sanitizeUrl(url);
if (!safeUrl) return label;
return `${label}`;
});
output = output.replace(/\*\*([^*]+)\*\*/g, "$1");
output = output.replace(/\*([^*]+)\*/g, "$1");
output = output.replace(/_([^_]+)_/g, "$1");
output = output.replace(/@@CODESPAN(\d+)@@/g, (_match, id) => {
const code = codeSpans[Number(id)] || "";
return `${code}`;
});
return output;
}
function renderMarkdown(rawText) {
const { text, blocks } = (() => {
const escaped = escapeHtml(rawText || "");
const codeBlocks = [];
const replaced = escaped.replace(/```([\s\S]*?)```/g, (_match, code) => {
let content = code;
if (content.startsWith("\n")) content = content.slice(1);
const firstLine = content.split("\n")[0] || "";
if (/^[a-z0-9+.#-]+$/i.test(firstLine.trim()) && content.includes("\n")) {
content = content.split("\n").slice(1).join("\n");
}
const id = codeBlocks.length;
codeBlocks.push(content);
return `@@CODEBLOCK${id}@@`;
});
return { text: replaced, blocks: codeBlocks };
})();
const lines = text.split(/\r?\n/);
const result = [];
let paragraph = [];
let listType = null;
let inBlockquote = false;
let quoteLines = [];
const flushParagraph = () => {
if (!paragraph.length) return;
result.push(`
${applyInline(paragraph.join("
"))}
${applyInline(quoteLines.join("`); inBlockquote = false; quoteLines = []; }; for (const line of lines) { const trimmed = line.trim(); const isQuoteLine = /^\s*>\s?/.test(line); if (trimmed === "") { flushParagraph(); closeList(); closeBlockquote(); continue; } if (inBlockquote && !isQuoteLine) { closeBlockquote(); } if (/^@@CODEBLOCK\d+@@$/.test(trimmed)) { flushParagraph(); closeList(); closeBlockquote(); result.push(trimmed); continue; } const headingMatch = line.match(/^(#{1,6})\s+(.*)$/); if (headingMatch) { flushParagraph(); closeList(); closeBlockquote(); const level = headingMatch[1].length; result.push(`
"))}
${code}`;
});
}
function renderOutput() {
outputEl.innerHTML = renderMarkdown(state.outputRaw);
outputEl.scrollTop = outputEl.scrollHeight;
}
function persistOutputNow() {
return chrome.storage.local.set({ [OUTPUT_STORAGE_KEY]: state.outputRaw });
}
function setStatus(message) {
statusEl.textContent = message;
}
function applyTheme(theme) {
const value = theme || "system";
document.documentElement.dataset.theme = value;
}
function setAnalyzing(isAnalyzing) {
state.isAnalyzing = isAnalyzing;
runBtn.disabled = isAnalyzing;
abortBtn.disabled = !isAnalyzing;
runBtn.classList.toggle("hidden", isAnalyzing);
abortBtn.classList.toggle("hidden", !isAnalyzing);
updateTaskSelectState();
updateEnvSelectState();
updateProfileSelectState();
}
function updateSiteTextCount() {
postingCountEl.textContent = `Site Text: ${state.siteText.length} chars`;
}
function updatePromptCount(count) {
promptCountEl.textContent = `Task: ${count} chars`;
}
function renderTasks(tasks) {
state.tasks = tasks;
taskSelect.innerHTML = "";
if (!tasks.length) {
const option = document.createElement("option");
option.textContent = "No tasks configured";
option.value = "";
taskSelect.appendChild(option);
updateTaskSelectState();
return;
}
for (const task of tasks) {
const option = document.createElement("option");
option.value = task.id;
option.textContent = task.name || "Untitled task";
taskSelect.appendChild(option);
}
updateTaskSelectState();
}
function renderEnvironments(envs) {
state.envs = envs;
envSelect.innerHTML = "";
if (!envs.length) {
const option = document.createElement("option");
option.textContent = "No environments configured";
option.value = "";
envSelect.appendChild(option);
updateEnvSelectState();
return;
}
for (const env of envs) {
const option = document.createElement("option");
option.value = env.id;
option.textContent = env.name || "Default";
envSelect.appendChild(option);
}
updateEnvSelectState();
}
function updateTaskSelectState() {
const hasTasks = state.tasks.length > 0;
taskSelect.disabled = state.isAnalyzing || !hasTasks;
}
function updateEnvSelectState() {
const hasEnvs = state.envs.length > 0;
envSelect.disabled = state.isAnalyzing || !hasEnvs;
}
function renderProfiles(profiles) {
state.profiles = profiles;
profileSelect.innerHTML = "";
if (!profiles.length) {
const option = document.createElement("option");
option.textContent = "No profiles configured";
option.value = "";
profileSelect.appendChild(option);
updateProfileSelectState();
return;
}
for (const profile of profiles) {
const option = document.createElement("option");
option.value = profile.id;
option.textContent = profile.name || "Default";
profileSelect.appendChild(option);
}
updateProfileSelectState();
}
function updateProfileSelectState() {
const hasProfiles = state.profiles.length > 0;
profileSelect.disabled = state.isAnalyzing || !hasProfiles;
}
function getTaskDefaultEnvId(task) {
return task?.defaultEnvId || state.envs[0]?.id || "";
}
function getTaskDefaultProfileId(task) {
return task?.defaultProfileId || state.profiles[0]?.id || "";
}
function setEnvironmentSelection(envId) {
const target =
envId && state.envs.some((env) => env.id === envId)
? envId
: state.envs[0]?.id || "";
if (target) {
envSelect.value = target;
}
state.selectedEnvId = target;
}
function setProfileSelection(profileId) {
const target =
profileId && state.profiles.some((profile) => profile.id === profileId)
? profileId
: state.profiles[0]?.id || "";
if (target) {
profileSelect.value = target;
}
state.selectedProfileId = target;
}
function selectTask(taskId, { resetEnv } = { resetEnv: false }) {
if (!taskId) return;
taskSelect.value = taskId;
state.selectedTaskId = taskId;
const task = state.tasks.find((item) => item.id === taskId);
if (resetEnv) {
setEnvironmentSelection(getTaskDefaultEnvId(task));
setProfileSelection(getTaskDefaultProfileId(task));
}
}
async function persistSelections() {
await chrome.storage.local.set({
[LAST_TASK_KEY]: state.selectedTaskId,
[LAST_ENV_KEY]: state.selectedEnvId,
[LAST_PROFILE_KEY]: state.selectedProfileId
});
}
function sendToActiveTab(message) {
return new Promise((resolve, reject) => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const tab = tabs[0];
if (!tab?.id) {
reject(new Error("No active tab found."));
return;
}
chrome.tabs.sendMessage(tab.id, message, (response) => {
const error = chrome.runtime.lastError;
if (error) {
const msg =
error.message && error.message.includes("Receiving end does not exist")
? "Couldn't reach the page. Try refreshing and retry."
: error.message;
reject(new Error(msg));
return;
}
resolve(response);
});
});
});
}
function ensurePort() {
if (state.port) return state.port;
const port = chrome.runtime.connect({ name: "analysis" });
port.onMessage.addListener((message) => {
if (message?.type === "DELTA") {
state.outputRaw += message.text;
renderOutput();
return;
}
if (message?.type === "SYNC") {
state.outputRaw = message.text || "";
renderOutput();
if (message.streaming) {
setAnalyzing(true);
setStatus("Analyzing...");
}
return;
}
if (message?.type === "DONE") {
setAnalyzing(false);
setStatus("Done");
return;
}
if (message?.type === "ABORTED") {
setAnalyzing(false);
setStatus("Aborted.");
return;
}
if (message?.type === "ERROR") {
setAnalyzing(false);
setStatus(message.message || "Error during analysis.");
}
});
port.onDisconnect.addListener(() => {
state.port = null;
setAnalyzing(false);
});
state.port = port;
return port;
}
async function loadConfig() {
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
const currentUrl = tabs[0]?.url || "";
const { lastPopupState } = await getStorage(["lastPopupState"]);
await detectSite(currentUrl);
if (lastPopupState && lastPopupState !== "unknown") {
// If we had a state like 'review', we might want to stay there,
// but detectSite might have switched to 'normal' if it matched.
// AGENTS.md says popup state must be persisted.
if (state.currentSite && lastPopupState === "normal") {
await switchState("normal");
} else if (!state.currentSite && (lastPopupState === "unknown" || lastPopupState === "review")) {
await switchState(lastPopupState);
}
}
const stored = await getStorage([
"tasks",
"envConfigs",
"profiles",
LAST_TASK_KEY,
LAST_ENV_KEY,
LAST_PROFILE_KEY
]);
const tasks = Array.isArray(stored.tasks) ? stored.tasks : [];
const envs = Array.isArray(stored.envConfigs) ? stored.envConfigs : [];
const profiles = Array.isArray(stored.profiles) ? stored.profiles : [];
renderTasks(tasks);
renderEnvironments(envs);
renderProfiles(profiles);
if (!tasks.length) {
state.selectedTaskId = "";
setEnvironmentSelection(envs[0]?.id || "");
setProfileSelection(profiles[0]?.id || "");
return;
}
const storedTaskId = stored[LAST_TASK_KEY];
const storedEnvId = stored[LAST_ENV_KEY];
const storedProfileId = stored[LAST_PROFILE_KEY];
const initialTaskId = tasks.some((task) => task.id === storedTaskId)
? storedTaskId
: tasks[0].id;
selectTask(initialTaskId, { resetEnv: false });
const task = tasks.find((item) => item.id === initialTaskId);
if (storedEnvId && envs.some((env) => env.id === storedEnvId)) {
setEnvironmentSelection(storedEnvId);
} else {
setEnvironmentSelection(getTaskDefaultEnvId(task));
}
if (storedProfileId && profiles.some((profile) => profile.id === storedProfileId)) {
setProfileSelection(storedProfileId);
} else {
setProfileSelection(getTaskDefaultProfileId(task));
}
if (
storedTaskId !== state.selectedTaskId ||
storedEnvId !== state.selectedEnvId ||
storedProfileId !== state.selectedProfileId
) {
await persistSelections();
}
maybeRunDefaultTask();
}
async function loadTheme() {
const { theme = "system" } = await getStorage(["theme"]);
applyTheme(theme);
}
async function handleExtract() {
setStatus("Extracting...");
try {
const response = await sendToActiveTab({ type: "EXTRACT_FULL" });
if (!response?.ok) {
setStatus(response?.error || "No text detected.");
return false;
}
state.siteText = response.extracted || "";
updateSiteTextCount();
updatePromptCount(0);
setStatus("Text extracted.");
return true;
} catch (error) {
setStatus(error.message || "Unable to extract text.");
return false;
}
}
async function handleAnalyze() {
if (!state.siteText) {
setStatus("Extract site text first.");
return;
}
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
const tab = tabs[0];
if (!tab?.url) {
setStatus("Open a page to run tasks.");
return;
}
const taskId = taskSelect.value;
const task = state.tasks.find((item) => item.id === taskId);
if (!task) {
setStatus("Select a task.");
return;
}
const {
apiKeys = [],
activeApiKeyId = "",
apiConfigs = [],
activeApiConfigId = "",
envConfigs = [],
profiles = [],
apiBaseUrl,
apiKeyHeader,
apiKeyPrefix,
model,
systemPrompt,
resume
} = await getStorage([
"apiKeys",
"activeApiKeyId",
"apiConfigs",
"activeApiConfigId",
"envConfigs",
"profiles",
"apiBaseUrl",
"apiKeyHeader",
"apiKeyPrefix",
"model",
"systemPrompt",
"resume"
]);
const resolvedConfigs = Array.isArray(apiConfigs) ? apiConfigs : [];
const resolvedEnvs = Array.isArray(envConfigs) ? envConfigs : [];
const resolvedProfiles = Array.isArray(profiles) ? profiles : [];
const selectedEnvId = envSelect.value;
const activeEnv =
resolvedEnvs.find((entry) => entry.id === selectedEnvId) ||
resolvedEnvs[0];
if (!activeEnv) {
setStatus("Add an environment in Settings.");
return;
}
const resolvedSystemPrompt =
activeEnv.systemPrompt ?? systemPrompt ?? "";
const resolvedApiConfigId =
activeEnv.apiConfigId || activeApiConfigId || resolvedConfigs[0]?.id || "";
const activeConfig =
resolvedConfigs.find((entry) => entry.id === resolvedApiConfigId) ||
resolvedConfigs[0];
if (!activeConfig) {
setStatus("Add an API configuration in Settings.");
return;
}
const selectedProfileId = profileSelect.value;
const activeProfile =
resolvedProfiles.find((entry) => entry.id === selectedProfileId) ||
resolvedProfiles[0];
const profileText = activeProfile?.text || resume || "";
const isAdvanced = Boolean(activeConfig?.advanced);
const resolvedApiUrl = activeConfig?.apiUrl || "";
const resolvedTemplate = activeConfig?.requestTemplate || "";
const resolvedApiBaseUrl = activeConfig?.apiBaseUrl || apiBaseUrl || "";
const resolvedApiKeyHeader = activeConfig?.apiKeyHeader ?? apiKeyHeader ?? "";
const resolvedApiKeyPrefix = activeConfig?.apiKeyPrefix ?? apiKeyPrefix ?? "";
const resolvedModel = activeConfig?.model || model || "";
const resolvedKeys = Array.isArray(apiKeys) ? apiKeys : [];
const resolvedKeyId =
activeConfig?.apiKeyId || activeApiKeyId || resolvedKeys[0]?.id || "";
const activeKey = resolvedKeys.find((entry) => entry.id === resolvedKeyId);
const apiKey = activeKey?.key || "";
if (isAdvanced) {
if (!resolvedApiUrl) {
setStatus("Set an API URL in Settings.");
return;
}
if (!resolvedTemplate) {
setStatus("Set a request template in Settings.");
return;
}
const needsKey =
Boolean(resolvedApiKeyHeader) ||
resolvedTemplate.includes("API_KEY_GOES_HERE");
if (needsKey && !apiKey) {
setStatus("Add an API key in Settings.");
return;
}
} else {
if (!resolvedApiBaseUrl) {
setStatus("Set an API base URL in Settings.");
return;
}
if (resolvedApiKeyHeader && !apiKey) {
setStatus("Add an API key in Settings.");
return;
}
if (!resolvedModel) {
setStatus("Set a model name in Settings.");
return;
}
}
const promptText = buildUserMessage(
profileText,
task.text || "",
state.siteText
);
updatePromptCount(promptText.length);
state.outputRaw = "";
renderOutput();
setAnalyzing(true);
setStatus("Analyzing...");
const port = ensurePort();
port.postMessage({
type: "START_ANALYSIS",
payload: {
apiKey,
apiMode: isAdvanced ? "advanced" : "basic",
apiUrl: resolvedApiUrl,
requestTemplate: resolvedTemplate,
apiBaseUrl: resolvedApiBaseUrl,
apiKeyHeader: resolvedApiKeyHeader,
apiKeyPrefix: resolvedApiKeyPrefix,
model: resolvedModel,
systemPrompt: resolvedSystemPrompt,
profileText,
taskText: task.text || "",
siteText: state.siteText,
tabId: tab.id
}
});
}
async function handleExtractAndAnalyze() {
const extracted = await handleExtract();
if (!extracted) return;
await handleAnalyze();
}
function handleAbort() {
if (!state.port) return;
state.port.postMessage({ type: "ABORT_ANALYSIS" });
setAnalyzing(false);
setStatus("Aborted.");
}
async function handleClearOutput() {
state.outputRaw = "";
renderOutput();
await persistOutputNow();
setStatus("Output cleared.");
}
async function copyTextToClipboard(text, label) {
try {
await navigator.clipboard.writeText(text);
setStatus(`${label} copied.`);
} catch (error) {
setStatus(`Unable to copy ${label.toLowerCase()}.`);
}
}
function handleCopyRendered() {
const text = outputEl.innerText || "";
if (!text.trim()) {
setStatus("Nothing to copy.");
return;
}
void copyTextToClipboard(text, "Output");
}
function handleCopyRaw() {
const text = state.outputRaw || "";
if (!text.trim()) {
setStatus("Nothing to copy.");
return;
}
void copyTextToClipboard(text, "Markdown");
}
partialTextPaste.addEventListener("input", async () => {
const text = partialTextPaste.value.trim();
if (text.length < 5) return;
setStatus("Finding scope...");
try {
const response = await sendToActiveTab({ type: "FIND_SCOPE", text });
if (response?.ok) {
state.siteText = response.extracted;
extractedPreview.textContent = state.siteText;
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
const url = new URL(tabs[0].url);
urlPatternInput.value = url.hostname + url.pathname + "*";
switchState("review");
setStatus("Review extraction.");
}
} catch (error) {
setStatus("Error finding scope.");
}
});
extractFullBtn.addEventListener("click", async () => {
setStatus("Extracting full text...");
try {
const response = await sendToActiveTab({ type: "EXTRACT_FULL" });
if (response?.ok) {
state.siteText = response.extracted;
extractedPreview.textContent = state.siteText;
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
const url = new URL(tabs[0].url);
urlPatternInput.value = url.hostname + url.pathname + "*";
switchState("review");
setStatus("Review extraction.");
}
} catch (error) {
setStatus("Error extracting text.");
}
});
retryExtractBtn.addEventListener("click", () => {
switchState("unknown");
partialTextPaste.value = "";
setStatus("Ready.");
});
confirmSiteBtn.addEventListener("click", async () => {
const pattern = urlPatternInput.value.trim();
if (!pattern) {
setStatus("Enter a URL pattern.");
return;
}
// AGENTS.md: No URL pattern may be a substring of another.
const conflict = state.sites.find(s => s.urlPattern.includes(pattern) || pattern.includes(s.urlPattern));
if (conflict) {
setStatus("URL pattern conflict.");
return;
}
const newSite = {
id: `site-${Date.now()}`,
urlPattern: pattern,
workspaceId: "global" // Default to global for now
};
state.sites.push(newSite);
await chrome.storage.local.set({ sites: state.sites });
state.currentSite = newSite;
state.currentWorkspace = { name: "Global", id: "global" };
currentWorkspaceName.textContent = "Global";
switchState("normal");
updateSiteTextCount();
setStatus("Site saved.");
});
runBtn.addEventListener("click", handleExtractAndAnalyze);
abortBtn.addEventListener("click", handleAbort);
settingsBtn.addEventListener("click", () => chrome.runtime.openOptionsPage());
copyRenderedBtn.addEventListener("click", handleCopyRendered);
copyRawBtn.addEventListener("click", handleCopyRaw);
clearOutputBtn.addEventListener("click", () => void handleClearOutput());
taskSelect.addEventListener("change", () => {
selectTask(taskSelect.value, { resetEnv: true });
void persistSelections();
});
envSelect.addEventListener("change", () => {
setEnvironmentSelection(envSelect.value);
void persistSelections();
});
profileSelect.addEventListener("change", () => {
setProfileSelection(profileSelect.value);
void persistSelections();
});
updateSiteTextCount();
updatePromptCount(0);
renderOutput();
setAnalyzing(false);
loadConfig();
loadTheme();
async function loadSavedOutput() {
const stored = await getStorage([OUTPUT_STORAGE_KEY]);
state.outputRaw = stored[OUTPUT_STORAGE_KEY] || "";
renderOutput();
}
async function loadAutoRunRequest() {
const stored = await getStorage([AUTO_RUN_KEY]);
if (stored[AUTO_RUN_KEY]) {
state.autoRunPending = true;
await chrome.storage.local.remove(AUTO_RUN_KEY);
}
maybeRunDefaultTask();
}
function maybeRunDefaultTask() {
if (!state.autoRunPending) return;
if (state.isAnalyzing) return;
if (!state.tasks.length) return;
selectTask(state.tasks[0].id, { resetEnv: true });
void persistSelections();
state.autoRunPending = false;
void handleExtractAndAnalyze();
}
loadSavedOutput();
loadAutoRunRequest();
ensurePort();
chrome.storage.onChanged.addListener((changes) => {
if (changes[AUTO_RUN_KEY]?.newValue) {
state.autoRunPending = true;
void chrome.storage.local.remove(AUTO_RUN_KEY);
maybeRunDefaultTask();
}
if (changes[OUTPUT_STORAGE_KEY]?.newValue !== undefined) {
if (!state.isAnalyzing || !state.port) {
state.outputRaw = changes[OUTPUT_STORAGE_KEY].newValue || "";
renderOutput();
}
}
if (changes.theme) {
applyTheme(changes.theme.newValue || "system");
}
});