Files
clinera-site/extensions/easymode.ts
T

641 lines
26 KiB
TypeScript

/**
* EasyMode — All-in-one beginner-friendly Pi extension
*
* Built-in features:
* 🎯 Goal Keeper — set a focus so the AI stays on track
* 🛡️ Safety Guard — catches dangerous commands before they run
* 📊 Smart Footer — model, context bar, cost, git branch, tool tally
* 🤖 Agent Presets — switch AI personality with /agent
* 🟢 /menu — visual command picker (no memorization needed)
* 🛑 /stop — abort the current AI action
* 💡 /explain — re-explain the last action in plain English
* 🔖 /bookmark — save + list important moments
* ↩️ /undo — how to revert the last file change
* 📋 /status — full session dashboard
* 📖 /help — quick reference
* 🔄 /quickstart — re-run the welcome wizard
*
* Usage: pi -e extensions/easymode.ts
*
* Or with theme cycling:
* pi -e extensions/easymode.ts -e extensions/theme-cycler.ts
*/
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
import { isToolCallEventType } from "@mariozechner/pi-coding-agent";
import type { AssistantMessage } from "@mariozechner/pi-ai";
import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
import { basename } from "node:path";
import { applyExtensionDefaults } from "./themeMap.ts";
// ═══════════════════════════════════════════════════════════════════════════
// Agent Presets
// ═══════════════════════════════════════════════════════════════════════════
interface AgentPreset {
name: string;
emoji: string;
description: string;
systemPrompt: string;
}
const AGENT_PRESETS: AgentPreset[] = [
{
name: "Default",
emoji: "🤖",
description: "Standard coding assistant — no restrictions",
systemPrompt: "",
},
{
name: "Explain Like I'm 5",
emoji: "👶",
description: "Explains everything in the simplest terms possible",
systemPrompt: `You are a patient teacher. The user is a beginner.
- Explain EVERY concept in simple terms before using it
- Use analogies and real-world comparisons
- Show the "before" and "after" of every change
- Never assume the user knows technical jargon — define it first
- After each code change, add a brief "What just happened?" summary
- If a command could break something, warn the user FIRST`,
},
{
name: "Guided Builder",
emoji: "🏗️",
description: "Step-by-step building with confirmations at each stage",
systemPrompt: `You are a guided builder. Help the user build things step-by-step.
- Break every task into numbered steps and show the plan first
- Ask for confirmation before each major step
- After each change, briefly explain what you did and why
- If there are multiple approaches, list 2-3 options with pros/cons
- Always suggest the safest option as the default
- Show file paths and explain the project structure as you go`,
},
{
name: "Code Reviewer",
emoji: "🔍",
description: "Reviews code and suggests improvements without changing anything",
systemPrompt: `You are a code reviewer. Review, don't modify.
- DO NOT write or edit any files unless explicitly asked
- Point out bugs, security issues, and improvement opportunities
- Rate code quality on a 1-5 scale with specific reasons
- Suggest improvements as clear before/after examples
- Focus on: readability, security, performance, maintainability
- Be encouraging — highlight what's done well too`,
},
{
name: "Debug Detective",
emoji: "🕵️",
description: "Focused on finding and fixing bugs",
systemPrompt: `You are a debug detective. Help find and fix bugs systematically.
- Always ask to see error messages and logs first
- Explain your debugging thought process step by step
- List possible causes ranked by likelihood
- Test hypotheses one at a time
- After fixing, explain what caused the bug and how to prevent it
- Suggest adding error handling or tests to prevent recurrence`,
},
{
name: "Safe Mode",
emoji: "🔒",
description: "Extra cautious — confirms every file modification",
systemPrompt: `You are in Safe Mode. Maximum caution.
- ALWAYS show the exact changes you plan to make BEFORE making them
- Ask for explicit "yes" confirmation before ANY file write, edit, or bash command
- Never run destructive commands (rm, drop, truncate, etc.) without double-confirming
- Create backups before modifying existing files when possible
- After each change, verify the change was correct by reading the file back
- If something goes wrong, immediately suggest how to undo it`,
},
];
// ═══════════════════════════════════════════════════════════════════════════
// Dangerous Command Patterns
// ═══════════════════════════════════════════════════════════════════════════
const DANGEROUS_PATTERNS: { pattern: RegExp; reason: string }[] = [
{ pattern: /rm\s+(-[rRf]+\s+|.*\s+-[rRf]+)/, reason: "Recursive/forced delete detected" },
{ pattern: /rm\s+-rf\s+[\/~]/, reason: "Deleting from root or home directory!" },
{ pattern: /DROP\s+(TABLE|DATABASE|SCHEMA)/i, reason: "SQL DROP command detected" },
{ pattern: /TRUNCATE\s+TABLE/i, reason: "SQL TRUNCATE command detected" },
{ pattern: /DELETE\s+FROM\s+\w+\s*(;|$)/i, reason: "DELETE without WHERE clause" },
{ pattern: />\s*\/dev\/sd[a-z]/, reason: "Writing directly to disk device" },
{ pattern: /mkfs\./, reason: "Filesystem format command detected" },
{ pattern: /dd\s+if=/, reason: "dd command — raw disk copy" },
{ pattern: /chmod\s+-R\s+777/, reason: "Recursive world-writable permissions" },
{ pattern: /:(){ :\|:& };:/, reason: "Fork bomb detected!" },
{ pattern: /curl.*\|\s*(ba)?sh/, reason: "Piping remote script to shell" },
{ pattern: /wget.*\|\s*(ba)?sh/, reason: "Piping remote script to shell" },
{ pattern: /git\s+push\s+.*--force/, reason: "Force push — may overwrite remote history" },
{ pattern: /git\s+reset\s+--hard/, reason: "Hard reset — may lose uncommitted changes" },
{ pattern: /npm\s+publish/, reason: "Publishing to npm registry" },
{ pattern: /docker\s+system\s+prune/, reason: "Docker system prune — removes all unused data" },
];
// ═══════════════════════════════════════════════════════════════════════════
// Extension
// ═══════════════════════════════════════════════════════════════════════════
export default function (pi: ExtensionAPI) {
let purpose: string | undefined;
let activePreset: AgentPreset = AGENT_PRESETS[1]; // Default to "Explain Like I'm 5" for beginners
let activeCtx: ExtensionContext | undefined;
const toolCounts: Record<string, number> = {};
const bookmarks: { timestamp: string; note: string }[] = [];
let lastFileChange: { tool: string; path: string } | null = null;
let toolRunsSinceLastTip = 0;
// ── Tool tracking + contextual tips ───────────────────────────────
pi.on("tool_execution_end", async (event) => {
toolCounts[event.toolName] = (toolCounts[event.toolName] || 0) + 1;
// Show a helpful hint every 5 tool runs
toolRunsSinceLastTip++;
if (activeCtx && toolRunsSinceLastTip >= 5) {
toolRunsSinceLastTip = 0;
const tips = [
"💡 Confused? Type /explain and the AI will break down what just happened.",
"💡 Want to save your progress? Type /bookmark to mark this moment.",
"💡 Type /menu to see everything you can do.",
"💡 Things going wrong? Type /stop to pause the AI immediately.",
"💡 Type /undo to see how to reverse the last file change.",
"💡 Type /status to see a summary of your whole session.",
];
const tip = tips[Math.floor(Math.random() * tips.length)];
activeCtx.ui.notify(tip, "info");
}
});
// ── Track file changes for /undo ──────────────────────────────────
pi.on("tool_call", async (event, _ctx) => {
if (isToolCallEventType("write", event) || isToolCallEventType("edit", event)) {
lastFileChange = { tool: event.toolName, path: event.input.path };
}
return { block: false };
});
// ── Welcome Wizard ────────────────────────────────────────────────
async function runWelcomeWizard(ctx: ExtensionContext) {
// Step 1: Welcome
ctx.ui.notify(
"👋 Welcome to EasyMode!\n\n" +
"This is your AI coding assistant. You type what you want\n" +
"in plain English, and it writes the code for you.\n\n" +
"Let's get you set up in 30 seconds...",
"info"
);
// Step 2: Pick a personality
const personalityOptions = AGENT_PRESETS.map((p) => `${p.emoji} ${p.name}${p.description}`);
const personalityChoice = await ctx.ui.select(
"Step 1 of 2: How should the AI talk to you?",
personalityOptions
);
if (personalityChoice !== undefined) {
const idx = personalityOptions.indexOf(personalityChoice);
activePreset = AGENT_PRESETS[idx];
ctx.ui.setStatus("easymode", `${activePreset.emoji} ${activePreset.name}`);
ctx.ui.notify(`Great choice! ${activePreset.emoji} ${activePreset.name} activated.`, "info");
} else {
ctx.ui.notify(`Using default: ${activePreset.emoji} ${activePreset.name}`, "info");
}
// Step 3: Set a goal
const answer = await ctx.ui.input(
"Step 2 of 2: What do you want to do today?",
"Examples: 'Make a website', 'Fix the bug on line 42', 'Explain this code to me'"
);
if (answer && answer.trim()) {
purpose = answer.trim();
ctx.ui.notify(`🎯 Goal set: ${purpose}`, "info");
setPurposeWidget(ctx);
} else {
ctx.ui.notify("No problem! You can set a goal anytime by typing /goal", "info");
}
// Step 4: Quick orientation
ctx.ui.notify(
"✅ You're all set! Here's what you need to know:\n\n" +
" 📝 Just type what you want in plain English\n" +
" 🟢 /menu — see all available commands\n" +
" 🛑 /stop — stop the AI if it's doing something wrong\n" +
" ❓ /explain — ask the AI to explain what it just did\n\n" +
"⚠️ The AI will ask for your OK before doing anything dangerous.\n\n" +
"Go ahead — type your first message! 🚀",
"info"
);
}
// ── Session start ─────────────────────────────────────────────────
pi.on("session_start", async (_event, ctx) => {
applyExtensionDefaults(import.meta.url, ctx);
activeCtx = ctx;
// Fire-and-forget — don't block TUI startup
void runWelcomeWizard(ctx);
// Footer
ctx.ui.setFooter((tui, theme, footerData) => {
const unsub = footerData.onBranchChange(() => tui.requestRender());
return {
dispose: unsub,
invalidate() {},
render(width: number): string[] {
// Accumulate tokens + cost
let tokIn = 0, tokOut = 0, cost = 0;
for (const entry of ctx.sessionManager.getBranch()) {
if (entry.type === "message" && entry.message.role === "assistant") {
const m = entry.message as AssistantMessage;
tokIn += m.usage.input;
tokOut += m.usage.output;
cost += m.usage.cost.total;
}
}
const fmt = (n: number) => n < 1000 ? `${n}` : `${(n / 1000).toFixed(1)}k`;
const model = ctx.model?.id || "no-model";
const dir = basename(ctx.cwd);
const branch = footerData.getGitBranch();
// Context bar
const usage = ctx.getContextUsage();
const pct = usage?.percent ?? 0;
const filled = Math.round(pct / 10);
const bar = "█".repeat(filled) + "░".repeat(10 - filled);
// Line 1: model + context + agent + cost
const l1Left =
theme.fg("dim", ` ${model} `) +
(pct < 70 ? theme.fg("success", bar) : pct < 90 ? theme.fg("warning", bar) : theme.fg("error", bar)) +
theme.fg("dim", ` ${Math.round(pct)}% `) +
theme.fg("accent", `${activePreset.emoji} ${activePreset.name}`);
const l1Right =
theme.fg("success", fmt(tokIn)) +
theme.fg("dim", "↓ ") +
theme.fg("accent", fmt(tokOut)) +
theme.fg("dim", "↑ ") +
theme.fg("warning", `$${cost.toFixed(4)}`) +
theme.fg("dim", " ");
const pad1 = " ".repeat(Math.max(1, width - visibleWidth(l1Left) - visibleWidth(l1Right)));
const line1 = truncateToWidth(l1Left + pad1 + l1Right, width, "");
// Line 2: cwd + branch + tools
const l2Left =
theme.fg("dim", ` ${dir}`) +
(branch
? theme.fg("dim", " ") + theme.fg("warning", "(") + theme.fg("success", branch) + theme.fg("warning", ")")
: "");
const entries = Object.entries(toolCounts);
const l2Right = entries.length === 0
? theme.fg("dim", "ready ")
: entries
.map(([name, count]) => theme.fg("accent", name) + theme.fg("dim", ":") + theme.fg("success", `${count}`))
.join(theme.fg("dim", " ")) + " ";
const pad2 = " ".repeat(Math.max(1, width - visibleWidth(l2Left) - visibleWidth(l2Right)));
const line2 = truncateToWidth(l2Left + pad2 + l2Right, width, "");
return [line1, line2];
},
};
});
ctx.ui.setStatus("easymode", `${activePreset.emoji} ${activePreset.name}`);
});
pi.on("session_switch", async (_event, ctx) => { activeCtx = ctx; });
// ── Safety guard ──────────────────────────────────────────────────
pi.on("tool_call", async (event, ctx) => {
if (isToolCallEventType("bash", event)) {
const cmd = event.input.command;
for (const { pattern, reason } of DANGEROUS_PATTERNS) {
if (pattern.test(cmd)) {
const confirmed = await ctx.ui.confirm(
"⚠️ Safety Warning",
`${reason}\n\nCommand: ${cmd}\n\nAre you sure you want to run this?`,
{ timeout: 30000 }
);
if (!confirmed) {
ctx.ui.notify(`🛡️ Blocked: ${reason}`, "warning");
ctx.abort();
return {
block: true,
reason: `🛑 BLOCKED by Safety Guard: ${reason}\n\nThe user chose not to run this command. Ask them what they'd like to do instead.`,
};
}
break;
}
}
}
return { block: false };
});
// ── System prompt injection ───────────────────────────────────────
pi.on("before_agent_start", async (event) => {
let extra = "";
if (activePreset.systemPrompt) {
extra += `\n\n<agent-preset name="${activePreset.name}">\n${activePreset.systemPrompt}\n</agent-preset>`;
}
if (purpose) {
extra += `\n\n<user-goal>\nThe user's goal for this session: ${purpose}\nKeep this goal in mind. If the conversation drifts, gently guide back.\n</user-goal>`;
}
if (!extra) return;
return { systemPrompt: event.systemPrompt + extra };
});
// ── Helper ────────────────────────────────────────────────────────
function setPurposeWidget(ctx: ExtensionContext) {
ctx.ui.setWidget("purpose", () => ({
render(width: number): string[] {
const label = " ✅ Working on: ";
const content = purpose!;
const line = truncateToWidth(label + content, width - 2, "…");
return [
" ".repeat(width),
line + " ".repeat(Math.max(0, width - visibleWidth(line))),
" ".repeat(width),
];
},
invalidate() {},
}));
}
// ═══════════════════════════════════════════════════════════════════
// Commands
// ═══════════════════════════════════════════════════════════════════
pi.registerCommand("help", {
description: "Show all EasyMode commands",
handler: async (_args, ctx) => {
ctx.ui.notify(
"📖 EasyMode Commands:\n\n" +
" /menu — 🟢 START HERE — pick a command from a list\n" +
" /help — You're looking at it!\n" +
" /agent — Switch AI personality (beginner, builder, reviewer...)\n" +
" /goal — Set or change your session goal\n" +
" /stop — Immediately stop the current AI action\n" +
" /explain — Ask the AI to explain what it just did\n" +
" /bookmark — Save the current moment with a note\n" +
" /bookmarks — See all saved bookmarks\n" +
" /undo — Show how to undo the last file change\n" +
" /status — Show session overview (goal, agent, stats)\n" +
" /quickstart — Re-run the setup wizard\n" +
"\n💡 Tips:\n" +
" • Just type what you want in plain English — no special syntax needed\n" +
" • The bottom bar shows your context usage and cost\n" +
" • ⚠️ Dangerous commands always ask for your OK first",
"info"
);
},
});
pi.registerCommand("menu", {
description: "Pick a command from a visual menu",
handler: async (_args, ctx) => {
const menuItems = [
"🎯 Set or change my goal",
"🤖 Switch AI personality",
"💡 Explain what just happened",
"🔖 Save a bookmark",
"📋 See all my bookmarks",
"↩️ Undo the last file change",
"📊 Show session status",
"🛑 Stop the AI",
"🔄 Re-run setup wizard",
"📖 Show all commands (help)",
];
const choice = await ctx.ui.select("What would you like to do?", menuItems);
if (choice === undefined) return;
const idx = menuItems.indexOf(choice);
switch (idx) {
case 0: { // Goal
const answer = await ctx.ui.input("🎯 What's your goal?", purpose || "e.g. Fix the login bug...");
if (answer && answer.trim()) {
purpose = answer.trim();
ctx.ui.notify(`🎯 Goal updated: ${purpose}`, "info");
setPurposeWidget(ctx);
}
return;
}
case 1: { // Agent
const options = AGENT_PRESETS.map((p) => `${p.emoji} ${p.name}${p.description}`);
const agentChoice = await ctx.ui.select("Select AI Personality", options);
if (agentChoice !== undefined) {
const agentIdx = options.indexOf(agentChoice);
activePreset = AGENT_PRESETS[agentIdx];
ctx.ui.setStatus("easymode", `${activePreset.emoji} ${activePreset.name}`);
ctx.ui.notify(`Switched to: ${activePreset.emoji} ${activePreset.name}\n${activePreset.description}`, "info");
}
return;
}
case 2: // Explain
pi.sendUserMessage("Please explain what you just did in simple terms that a beginner would understand. Use bullet points and avoid jargon. If you wrote code, explain what each part does.");
return;
case 3: { // Bookmark
const note = await ctx.ui.input("🔖 Bookmark note", "e.g. Got login working, Before refactor...");
if (note && note.trim()) {
const ts = new Date().toLocaleTimeString();
bookmarks.push({ timestamp: ts, note: note.trim() });
pi.appendEntry("easymode-bookmarks", { timestamp: ts, note: note.trim() });
ctx.ui.notify(`🔖 Saved: ${note.trim()} (${ts})`, "info");
}
return;
}
case 4: { // Bookmarks
if (bookmarks.length === 0) return ctx.ui.notify("No bookmarks yet. Use /bookmark to save one.", "info");
const list = bookmarks.map((b, i) => ` ${i + 1}. [${b.timestamp}] ${b.note}`).join("\n");
return ctx.ui.notify(`🔖 Bookmarks:\n\n${list}`, "info");
}
case 5: { // Undo
if (!lastFileChange) {
return ctx.ui.notify("No file changes tracked yet.\n\n💡 You can also tell the AI: \"Please undo the last change\"", "info");
}
return ctx.ui.notify(
`↩️ Last change: ${lastFileChange.tool}${lastFileChange.path}\n\n` +
'Tell the AI: "Please revert the last change"\n' +
`Or run: git checkout -- ${lastFileChange.path}`,
"info"
);
}
case 6: { // Status
const usage = ctx.getContextUsage();
const pct = usage?.percent ?? 0;
let tokIn = 0, tokOut = 0, cost = 0;
for (const entry of ctx.sessionManager.getBranch()) {
if (entry.type === "message" && entry.message.role === "assistant") {
const m = entry.message as AssistantMessage;
tokIn += m.usage.input;
tokOut += m.usage.output;
cost += m.usage.cost.total;
}
}
return ctx.ui.notify(
`📋 Session Status\n\n` +
` 🎯 Goal: ${purpose || "(not set)"}\n` +
` 🤖 Agent: ${activePreset.emoji} ${activePreset.name}\n` +
` 📊 Context: ${Math.round(pct)}% used\n` +
` 💰 Cost: $${cost.toFixed(4)}`,
"info"
);
}
case 7: // Stop
ctx.abort();
return ctx.ui.notify("🛑 Stopped.", "warning");
case 8: // Quickstart
await runWelcomeWizard(ctx);
return;
case 9: // Help
return ctx.ui.notify(
"📖 All commands start with / (slash):\n\n" +
" /menu /goal /agent /explain /bookmark\n" +
" /bookmarks /undo /status /stop /quickstart /help\n\n" +
"Or just type what you want in plain English!",
"info"
);
}
},
});
pi.registerCommand("quickstart", {
description: "Re-run the welcome wizard",
handler: async (_args, ctx) => {
await runWelcomeWizard(ctx);
},
});
pi.registerCommand("agent", {
description: "Switch AI personality preset",
handler: async (_args, ctx) => {
const options = AGENT_PRESETS.map((p) => `${p.emoji} ${p.name}${p.description}`);
const choice = await ctx.ui.select("Select AI Personality", options);
if (choice === undefined) return;
const idx = options.indexOf(choice);
activePreset = AGENT_PRESETS[idx];
ctx.ui.setStatus("easymode", `${activePreset.emoji} ${activePreset.name}`);
ctx.ui.notify(`Switched to: ${activePreset.emoji} ${activePreset.name}\n${activePreset.description}`, "info");
},
});
pi.registerCommand("goal", {
description: "Set or change your session goal",
handler: async (_args, ctx) => {
const answer = await ctx.ui.input("🎯 What's your goal?", purpose || "e.g. Fix the login bug...");
if (answer && answer.trim()) {
purpose = answer.trim();
ctx.ui.notify(`🎯 Goal updated: ${purpose}`, "info");
setPurposeWidget(ctx);
}
},
});
pi.registerCommand("stop", {
description: "Immediately stop the current AI action",
handler: async (args, ctx) => {
const reason = (args || "").trim();
ctx.abort();
ctx.ui.notify(reason ? `🛑 Stopped: ${reason}` : "🛑 Stopped.", "warning");
},
});
pi.registerCommand("explain", {
description: "Ask the AI to explain its last action in simple terms",
handler: async (_args, _ctx) => {
pi.sendUserMessage(
"Please explain what you just did in simple terms that a beginner would understand. " +
"Use bullet points and avoid jargon. If you wrote code, explain what each part does."
);
},
});
pi.registerCommand("bookmark", {
description: "Save a bookmark with a note",
handler: async (_args, ctx) => {
const note = await ctx.ui.input("🔖 Bookmark note", "e.g. Got login working, Before refactor...");
if (note && note.trim()) {
const ts = new Date().toLocaleTimeString();
bookmarks.push({ timestamp: ts, note: note.trim() });
pi.appendEntry("easymode-bookmarks", { timestamp: ts, note: note.trim() });
ctx.ui.notify(`🔖 Saved: ${note.trim()} (${ts})`, "info");
}
},
});
pi.registerCommand("bookmarks", {
description: "Show all saved bookmarks",
handler: async (_args, ctx) => {
if (bookmarks.length === 0) {
ctx.ui.notify("No bookmarks yet. Use /bookmark to save one.", "info");
return;
}
const list = bookmarks.map((b, i) => ` ${i + 1}. [${b.timestamp}] ${b.note}`).join("\n");
ctx.ui.notify(`🔖 Bookmarks:\n\n${list}`, "info");
},
});
pi.registerCommand("undo", {
description: "Show how to undo the last file change",
handler: async (_args, ctx) => {
if (!lastFileChange) {
ctx.ui.notify(
"No file changes tracked yet.\n\n" +
"💡 General undo:\n" +
" git checkout -- <file> revert to last commit\n" +
" git diff see what changed\n" +
" git stash save & revert everything",
"info"
);
return;
}
ctx.ui.notify(
`↩️ Last change: ${lastFileChange.tool}${lastFileChange.path}\n\n` +
"To undo:\n" +
` git checkout -- ${lastFileChange.path}\n\n` +
"See diff:\n" +
` git diff ${lastFileChange.path}\n\n` +
'Or tell the AI: "Please revert the last change"',
"info"
);
},
});
pi.registerCommand("status", {
description: "Show session overview",
handler: async (_args, ctx) => {
const usage = ctx.getContextUsage();
const pct = usage?.percent ?? 0;
let tokIn = 0, tokOut = 0, cost = 0;
for (const entry of ctx.sessionManager.getBranch()) {
if (entry.type === "message" && entry.message.role === "assistant") {
const m = entry.message as AssistantMessage;
tokIn += m.usage.input;
tokOut += m.usage.output;
cost += m.usage.cost.total;
}
}
const entries = Object.entries(toolCounts);
const toolSummary = entries.length === 0
? " None yet"
: entries.map(([n, c]) => ` ${n}: ${c}`).join("\n");
ctx.ui.notify(
`📋 Session Status\n\n` +
` 🎯 Goal: ${purpose || "(not set — use /goal)"}\n` +
` 🤖 Agent: ${activePreset.emoji} ${activePreset.name}\n` +
` 📊 Context: ${Math.round(pct)}% used\n` +
` 💰 Cost: $${cost.toFixed(4)}\n` +
` 📨 Tokens: ${tokIn} in / ${tokOut} out\n` +
` 🔖 Bookmarks: ${bookmarks.length}\n` +
`\n🔧 Tools Used:\n${toolSummary}`,
"info"
);
},
});
}