<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Arya Ajgaonkar</title>
    <description>The latest articles on DEV Community by Arya Ajgaonkar (@arya_2005).</description>
    <link>https://dev.to/arya_2005</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4162025%2Fb7be6693-5952-4035-a780-8c6509ef8aef.png</url>
      <title>DEV Community: Arya Ajgaonkar</title>
      <link>https://dev.to/arya_2005</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arya_2005"/>
    <language>en</language>
    <item>
      <title>FriendOS — A private AI memory for someone I care about</title>
      <dc:creator>Arya Ajgaonkar</dc:creator>
      <pubDate>Mon, 05 Oct 2026 06:48:31 +0000</pubDate>
      <link>https://dev.to/arya_2005/friendos-a-private-ai-memory-for-someone-i-care-about-1hfe</link>
      <guid>https://dev.to/arya_2005/friendos-a-private-ai-memory-for-someone-i-care-about-1hfe</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built FriendOS for a friend who constantly has important things scattered across messages, notes, and random conversations.&lt;/p&gt;

&lt;p&gt;The idea is simple: give the app something my friend said, and it turns the chaos into useful memories — tasks, dates, commitments, and things worth remembering. You can then ask questions like “When is my interview?” or “What did I say I needed to finish?”&lt;/p&gt;

&lt;p&gt;The important part is that this isn't another AI app that sends someone's personal life to a server they don't control.&lt;/p&gt;

&lt;p&gt;FriendOS is built around open-source AI and local inference. The model can run on the user's own machine, which means their memories can stay on their computer. There's no required API key, no per-request bill, and no dependency on a proprietary AI provider.&lt;/p&gt;

&lt;p&gt;That's why open innovation mattered for this project.&lt;/p&gt;

&lt;p&gt;A friend's messages and personal memories are exactly the kind of data I don't want to blindly upload somewhere. With an open model, I can choose the model, run it locally, swap it out, inspect how the system works, and eventually fine-tune or customize it for the person using it.&lt;/p&gt;

&lt;p&gt;I deliberately kept the project small. This weekend's challenge was “Build for a Friend,” so I wanted to build something for one real person rather than another generic AI demo.&lt;/p&gt;

&lt;p&gt;The goal wasn't to build the smartest memory system.&lt;/p&gt;

&lt;p&gt;It was to build something my friend could actually use.&lt;/p&gt;

&lt;p&gt;And then hand it to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;app.py&lt;br&gt;
import json&lt;br&gt;
import urllib.request&lt;br&gt;
from http.server import BaseHTTPRequestHandler, HTTPServer&lt;br&gt;
from pathlib import Path&lt;/p&gt;

&lt;p&gt;PORT = 8000&lt;br&gt;
OLLAMA_URL = "&lt;a href="http://localhost:11434/api/generate" rel="noopener noreferrer"&gt;http://localhost:11434/api/generate&lt;/a&gt;"&lt;br&gt;
MODEL = "qwen2.5:3b"&lt;/p&gt;

&lt;p&gt;BASE_DIR = Path(&lt;strong&gt;file&lt;/strong&gt;).parent&lt;br&gt;
MEMORY_FILE = BASE_DIR / "memories.json"&lt;br&gt;
HTML_FILE = BASE_DIR / "index.html"&lt;/p&gt;

&lt;p&gt;def load_memories():&lt;br&gt;
    try:&lt;br&gt;
        return json.loads(MEMORY_FILE.read_text())&lt;br&gt;
    except Exception:&lt;br&gt;
        return []&lt;/p&gt;

&lt;p&gt;def save_memories(memories):&lt;br&gt;
    MEMORY_FILE.write_text(json.dumps(memories, indent=2))&lt;/p&gt;

&lt;p&gt;def ask_ollama(prompt):&lt;br&gt;
    payload = json.dumps({&lt;br&gt;
        "model": MODEL,&lt;br&gt;
        "prompt": prompt,&lt;br&gt;
        "stream": False&lt;br&gt;
    }).encode()&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request = urllib.request.Request(
    OLLAMA_URL,
    data=payload,
    headers={"Content-Type": "application/json"}
)

with urllib.request.urlopen(request, timeout=120) as response:
    data = json.loads(response.read().decode())

return data["response"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;def extract_memories(text):&lt;br&gt;
    prompt = f"""&lt;br&gt;
You are FriendMemory, a private personal memory assistant.&lt;/p&gt;

&lt;p&gt;Analyze the following message from a person's friend.&lt;/p&gt;

&lt;p&gt;Extract only information that would actually be useful to remember later.&lt;/p&gt;

&lt;p&gt;Return ONLY valid JSON in this exact format:&lt;/p&gt;

&lt;p&gt;[&lt;br&gt;
  {{&lt;br&gt;
    "type": "task",&lt;br&gt;
    "title": "short title",&lt;br&gt;
    "detail": "useful detail"&lt;br&gt;
  }}&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;Allowed types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;task&lt;/li&gt;
&lt;li&gt;date&lt;/li&gt;
&lt;li&gt;person&lt;/li&gt;
&lt;li&gt;preference&lt;/li&gt;
&lt;li&gt;commitment&lt;/li&gt;
&lt;li&gt;important&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Message:&lt;/p&gt;

&lt;p&gt;{text}&lt;br&gt;
"""&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;response = ask_ollama(prompt)

# Handle models that wrap JSON in markdown.
response = response.strip()

if response.startswith("
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        response = response.replace("```

json", "").replace("

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;br&gt;
", "").strip()&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try:
    result = json.loads(response)

    if not isinstance(result, list):
        return []

    return result

except Exception:
    return []
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;def answer_question(question, memories):&lt;br&gt;
    memory_text = "\n".join(&lt;br&gt;
        f"- {m.get('type', 'memory')}: {m.get('title', '')} — {m.get('detail', '')}"&lt;br&gt;
        for m in memories&lt;br&gt;
    )&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prompt = f"""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;You are a private memory assistant.&lt;/p&gt;

&lt;p&gt;Answer the user's question using ONLY the memories below.&lt;/p&gt;

&lt;p&gt;If the answer isn't present, say:&lt;br&gt;
"I don't have that in my memories."&lt;/p&gt;

&lt;p&gt;Keep the answer short and natural.&lt;/p&gt;

&lt;p&gt;Memories:&lt;br&gt;
{memory_text}&lt;/p&gt;

&lt;p&gt;Question:&lt;br&gt;
{question}&lt;br&gt;
"""&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return ask_ollama(prompt).strip()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;class Handler(BaseHTTPRequestHandler):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def send_json(self, data, status=200):
    body = json.dumps(data).encode()

    self.send_response(status)
    self.send_header("Content-Type", "application/json")
    self.send_header("Access-Control-Allow-Origin", "*")
    self.send_header("Content-Length", str(len(body)))
    self.end_headers()
    self.wfile.write(body)

def do_GET(self):
    if self.path == "/":
        body = HTML_FILE.read_bytes()

        self.send_response(200)
        self.send_header("Content-Type", "text/html")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    elif self.path == "/memories":
        self.send_json(load_memories())

    else:
        self.send_error(404)

def do_POST(self):
    length = int(self.headers.get("Content-Length", 0))
    body = self.rfile.read(length)

    try:
        data = json.loads(body.decode())
    except Exception:
        self.send_json({"error": "Invalid JSON"}, 400)
        return

    if self.path == "/remember":
        text = data.get("text", "").strip()

        if not text:
            self.send_json({"error": "No text provided"}, 400)
            return

        try:
            extracted = extract_memories(text)
        except Exception as e:
            self.send_json({
                "error": f"Could not contact Ollama: {e}"
            }, 500)
            return

        memories = load_memories()

        for memory in extracted:
            memory["source"] = text
            memories.append(memory)

        save_memories(memories)

        self.send_json({
            "added": extracted,
            "all": memories
        })

    elif self.path == "/ask":
        question = data.get("question", "").strip()

        if not question:
            self.send_json({"error": "No question provided"}, 400)
            return

        try:
            answer = answer_question(question, load_memories())
        except Exception as e:
            self.send_json({
                "error": f"Could not contact Ollama: {e}"
            }, 500)
            return

        self.send_json({"answer": answer})

    elif self.path == "/clear":
        save_memories([])
        self.send_json({"success": True})

    else:
        self.send_error(404)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;print(f"FriendMemory running at &lt;a href="http://localhost:%7BPORT%7D%22" rel="noopener noreferrer"&gt;http://localhost:{PORT}"&lt;/a&gt;)&lt;br&gt;
HTTPServer(("localhost", PORT), Handler).serve_forever()&lt;/p&gt;

&lt;p&gt;index.html&lt;br&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;


FriendMemory

&lt;ul&gt;
&lt;li&gt;{
box-sizing: border-box;
}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;body {&lt;br&gt;
    margin: 0;&lt;br&gt;
    font-family: Inter, system-ui, sans-serif;&lt;br&gt;
    background: #0b1020;&lt;br&gt;
    color: #f8fafc;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.container {&lt;br&gt;
    max-width: 900px;&lt;br&gt;
    margin: auto;&lt;br&gt;
    padding: 50px 20px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.hero {&lt;br&gt;
    margin-bottom: 35px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.badge {&lt;br&gt;
    display: inline-block;&lt;br&gt;
    background: #172554;&lt;br&gt;
    color: #93c5fd;&lt;br&gt;
    padding: 7px 12px;&lt;br&gt;
    border-radius: 999px;&lt;br&gt;
    font-size: 13px;&lt;br&gt;
    margin-bottom: 15px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;h1 {&lt;br&gt;
    font-size: 48px;&lt;br&gt;
    margin: 0 0 10px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.subtitle {&lt;br&gt;
    color: #94a3b8;&lt;br&gt;
    font-size: 18px;&lt;br&gt;
    line-height: 1.6;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.card {&lt;br&gt;
    background: #111827;&lt;br&gt;
    border: 1px solid #1f2937;&lt;br&gt;
    border-radius: 18px;&lt;br&gt;
    padding: 22px;&lt;br&gt;
    margin-bottom: 20px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;textarea,&lt;br&gt;
input {&lt;br&gt;
    width: 100%;&lt;br&gt;
    background: #020617;&lt;br&gt;
    color: white;&lt;br&gt;
    border: 1px solid #334155;&lt;br&gt;
    border-radius: 12px;&lt;br&gt;
    padding: 15px;&lt;br&gt;
    font-size: 15px;&lt;br&gt;
    outline: none;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;textarea {&lt;br&gt;
    min-height: 150px;&lt;br&gt;
    resize: vertical;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;textarea:focus,&lt;br&gt;
input:focus {&lt;br&gt;
    border-color: #6366f1;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;button {&lt;br&gt;
    border: 0;&lt;br&gt;
    border-radius: 10px;&lt;br&gt;
    padding: 12px 18px;&lt;br&gt;
    background: #6366f1;&lt;br&gt;
    color: white;&lt;br&gt;
    font-weight: 700;&lt;br&gt;
    cursor: pointer;&lt;br&gt;
    margin-top: 12px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;button:hover {&lt;br&gt;
    background: #4f46e5;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.secondary {&lt;br&gt;
    background: #1e293b;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.memories {&lt;br&gt;
    display: grid;&lt;br&gt;
    gap: 12px;&lt;br&gt;
    margin-top: 18px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.memory {&lt;br&gt;
    background: #020617;&lt;br&gt;
    border: 1px solid #1e293b;&lt;br&gt;
    padding: 16px;&lt;br&gt;
    border-radius: 12px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.memory-type {&lt;br&gt;
    color: #818cf8;&lt;br&gt;
    text-transform: uppercase;&lt;br&gt;
    font-size: 11px;&lt;br&gt;
    font-weight: bold;&lt;br&gt;
    letter-spacing: 1px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.memory-title {&lt;br&gt;
    font-size: 17px;&lt;br&gt;
    font-weight: 700;&lt;br&gt;
    margin-top: 5px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.memory-detail {&lt;br&gt;
    color: #94a3b8;&lt;br&gt;
    margin-top: 5px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.answer {&lt;br&gt;
    margin-top: 15px;&lt;br&gt;
    padding: 15px;&lt;br&gt;
    background: #172554;&lt;br&gt;
    border-radius: 12px;&lt;br&gt;
    color: #bfdbfe;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.status {&lt;br&gt;
    color: #94a3b8;&lt;br&gt;
    margin-top: 10px;&lt;br&gt;
    min-height: 20px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.privacy {&lt;br&gt;
    text-align: center;&lt;br&gt;
    color: #64748b;&lt;br&gt;
    margin-top: 30px;&lt;br&gt;
    font-size: 13px;&lt;br&gt;
}&lt;/p&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    LOCAL AI · OPEN WEIGHT · PRIVATE

    &amp;lt;h1&amp;gt;🧠 FriendMemory&amp;lt;/h1&amp;gt;


        A tiny AI memory assistant for someone you care about.
        Turn messy messages into memories you can actually find later.




    &amp;lt;h2&amp;gt;💬 Remember something&amp;lt;/h2&amp;gt;



    Remember this





    &amp;lt;h2&amp;gt;🔎 Ask your memories&amp;lt;/h2&amp;gt;



    Ask





    &amp;lt;h2&amp;gt;💭 Your memories&amp;lt;/h2&amp;gt;




        Clear everything




    🔒 Your memories stay on your computer.
    AI inference happens locally through Ollama.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;async function remember() {&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const text = document.getElementById("message").value;
const status = document.getElementById("status");

if (!text.trim()) return;

status.textContent = "Thinking...";

try {

    const response = await fetch("/remember", {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({ text })
    });

    const data = await response.json();

    if (data.error) {
        status.textContent = data.error;
        return;
    }

    status.textContent =
        `Added ${data.added.length} memory${data.added.length === 1 ? "" : "ies"}.`;

    document.getElementById("message").value = "";

    renderMemories(data.all);

} catch (error) {
    status.textContent =
        "Could not connect to the local AI server.";
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;async function ask() {&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const question = document.getElementById("question").value;
const answerBox = document.getElementById("answer");

if (!question.trim()) return;

answerBox.innerHTML = "Thinking...";

try {

    const response = await fetch("/ask", {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({ question })
    });

    const data = await response.json();

    answerBox.innerHTML =
        `&amp;amp;lt;div class="answer"&amp;amp;gt;${escapeHtml(data.answer)}&amp;amp;lt;/div&amp;amp;gt;`;

} catch (error) {

    answerBox.innerHTML =
        `&amp;amp;lt;div class="answer"&amp;amp;gt;Something went wrong.&amp;amp;lt;/div&amp;amp;gt;`;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;async function loadMemories() {&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const response = await fetch("/memories");
const memories = await response.json();

renderMemories(memories);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;function renderMemories(memories) {&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const container = document.getElementById("memories");

if (!memories.length) {

    container.innerHTML =
        `&amp;amp;lt;div class="memory"&amp;amp;gt;
            No memories yet. Add something above.
        &amp;amp;lt;/div&amp;amp;gt;`;

    return;
}

container.innerHTML = memories
    .slice()
    .reverse()
    .map(memory =&amp;amp;gt; `
        &amp;amp;lt;div class="memory"&amp;amp;gt;
            &amp;amp;lt;div class="memory-type"&amp;amp;gt;
                ${escapeHtml(memory.type || "memory")}
            &amp;amp;lt;/div&amp;amp;gt;

            &amp;amp;lt;div class="memory-title"&amp;amp;gt;
                ${escapeHtml(memory.title || "Memory")}
            &amp;amp;lt;/div&amp;amp;gt;

            &amp;amp;lt;div class="memory-detail"&amp;amp;gt;
                ${escapeHtml(memory.detail || "")}
            &amp;amp;lt;/div&amp;amp;gt;
        &amp;amp;lt;/div&amp;amp;gt;
    `)
    .join("");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;async function clearMemories() {&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (!confirm("Delete all memories?")) return;

await fetch("/clear", {
    method: "POST"
});

loadMemories();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;function escapeHtml(text) {&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const div = document.createElement("div");
div.textContent = text;

return div.innerHTML;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;loadMemories();&lt;/p&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Frontend: plain HTML/CSS/JS&lt;/p&gt;

&lt;p&gt;AI: Ollama&lt;br&gt;
Model: Qwen2.5 3B (open-weight)&lt;br&gt;
Backend: Python standard library&lt;br&gt;
Storage: local JSON&lt;br&gt;
Internet: not required once the model is installed&lt;br&gt;
API keys: none&lt;/p&gt;

&lt;p&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;


FriendMemory

&lt;ul&gt;
&lt;li&gt;{
box-sizing: border-box;
}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;body {&lt;br&gt;
    margin: 0;&lt;br&gt;
    font-family: Inter, system-ui, sans-serif;&lt;br&gt;
    background: #0b1020;&lt;br&gt;
    color: #f8fafc;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.container {&lt;br&gt;
    max-width: 900px;&lt;br&gt;
    margin: auto;&lt;br&gt;
    padding: 50px 20px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.hero {&lt;br&gt;
    margin-bottom: 35px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.badge {&lt;br&gt;
    display: inline-block;&lt;br&gt;
    background: #172554;&lt;br&gt;
    color: #93c5fd;&lt;br&gt;
    padding: 7px 12px;&lt;br&gt;
    border-radius: 999px;&lt;br&gt;
    font-size: 13px;&lt;br&gt;
    margin-bottom: 15px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;h1 {&lt;br&gt;
    font-size: 48px;&lt;br&gt;
    margin: 0 0 10px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.subtitle {&lt;br&gt;
    color: #94a3b8;&lt;br&gt;
    font-size: 18px;&lt;br&gt;
    line-height: 1.6;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.card {&lt;br&gt;
    background: #111827;&lt;br&gt;
    border: 1px solid #1f2937;&lt;br&gt;
    border-radius: 18px;&lt;br&gt;
    padding: 22px;&lt;br&gt;
    margin-bottom: 20px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;textarea,&lt;br&gt;
input {&lt;br&gt;
    width: 100%;&lt;br&gt;
    background: #020617;&lt;br&gt;
    color: white;&lt;br&gt;
    border: 1px solid #334155;&lt;br&gt;
    border-radius: 12px;&lt;br&gt;
    padding: 15px;&lt;br&gt;
    font-size: 15px;&lt;br&gt;
    outline: none;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;textarea {&lt;br&gt;
    min-height: 150px;&lt;br&gt;
    resize: vertical;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;textarea:focus,&lt;br&gt;
input:focus {&lt;br&gt;
    border-color: #6366f1;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;button {&lt;br&gt;
    border: 0;&lt;br&gt;
    border-radius: 10px;&lt;br&gt;
    padding: 12px 18px;&lt;br&gt;
    background: #6366f1;&lt;br&gt;
    color: white;&lt;br&gt;
    font-weight: 700;&lt;br&gt;
    cursor: pointer;&lt;br&gt;
    margin-top: 12px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;button:hover {&lt;br&gt;
    background: #4f46e5;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.secondary {&lt;br&gt;
    background: #1e293b;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.memories {&lt;br&gt;
    display: grid;&lt;br&gt;
    gap: 12px;&lt;br&gt;
    margin-top: 18px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.memory {&lt;br&gt;
    background: #020617;&lt;br&gt;
    border: 1px solid #1e293b;&lt;br&gt;
    padding: 16px;&lt;br&gt;
    border-radius: 12px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.memory-type {&lt;br&gt;
    color: #818cf8;&lt;br&gt;
    text-transform: uppercase;&lt;br&gt;
    font-size: 11px;&lt;br&gt;
    font-weight: bold;&lt;br&gt;
    letter-spacing: 1px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.memory-title {&lt;br&gt;
    font-size: 17px;&lt;br&gt;
    font-weight: 700;&lt;br&gt;
    margin-top: 5px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.memory-detail {&lt;br&gt;
    color: #94a3b8;&lt;br&gt;
    margin-top: 5px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.answer {&lt;br&gt;
    margin-top: 15px;&lt;br&gt;
    padding: 15px;&lt;br&gt;
    background: #172554;&lt;br&gt;
    border-radius: 12px;&lt;br&gt;
    color: #bfdbfe;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.status {&lt;br&gt;
    color: #94a3b8;&lt;br&gt;
    margin-top: 10px;&lt;br&gt;
    min-height: 20px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.privacy {&lt;br&gt;
    text-align: center;&lt;br&gt;
    color: #64748b;&lt;br&gt;
    margin-top: 30px;&lt;br&gt;
    font-size: 13px;&lt;br&gt;
}&lt;/p&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    LOCAL AI · OPEN WEIGHT · PRIVATE

    &amp;lt;h1&amp;gt;🧠 FriendMemory&amp;lt;/h1&amp;gt;


        A tiny AI memory assistant for someone you care about.
        Turn messy messages into memories you can actually find later.




    &amp;lt;h2&amp;gt;💬 Remember something&amp;lt;/h2&amp;gt;



    Remember this





    &amp;lt;h2&amp;gt;🔎 Ask your memories&amp;lt;/h2&amp;gt;



    Ask





    &amp;lt;h2&amp;gt;💭 Your memories&amp;lt;/h2&amp;gt;




        Clear everything




    🔒 Your memories stay on your computer.
    AI inference happens locally through Ollama.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;async function remember() {&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const text = document.getElementById("message").value;
const status = document.getElementById("status");

if (!text.trim()) return;

status.textContent = "Thinking...";

try {

    const response = await fetch("/remember", {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({ text })
    });

    const data = await response.json();

    if (data.error) {
        status.textContent = data.error;
        return;
    }

    status.textContent =
        `Added ${data.added.length} memory${data.added.length === 1 ? "" : "ies"}.`;

    document.getElementById("message").value = "";

    renderMemories(data.all);

} catch (error) {
    status.textContent =
        "Could not connect to the local AI server.";
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;async function ask() {&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const question = document.getElementById("question").value;
const answerBox = document.getElementById("answer");

if (!question.trim()) return;

answerBox.innerHTML = "Thinking...";

try {

    const response = await fetch("/ask", {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({ question })
    });

    const data = await response.json();

    answerBox.innerHTML =
        `&amp;amp;lt;div class="answer"&amp;amp;gt;${escapeHtml(data.answer)}&amp;amp;lt;/div&amp;amp;gt;`;

} catch (error) {

    answerBox.innerHTML =
        `&amp;amp;lt;div class="answer"&amp;amp;gt;Something went wrong.&amp;amp;lt;/div&amp;amp;gt;`;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;async function loadMemories() {&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const response = await fetch("/memories");
const memories = await response.json();

renderMemories(memories);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;function renderMemories(memories) {&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const container = document.getElementById("memories");

if (!memories.length) {

    container.innerHTML =
        `&amp;amp;lt;div class="memory"&amp;amp;gt;
            No memories yet. Add something above.
        &amp;amp;lt;/div&amp;amp;gt;`;

    return;
}

container.innerHTML = memories
    .slice()
    .reverse()
    .map(memory =&amp;amp;gt; `
        &amp;amp;lt;div class="memory"&amp;amp;gt;
            &amp;amp;lt;div class="memory-type"&amp;amp;gt;
                ${escapeHtml(memory.type || "memory")}
            &amp;amp;lt;/div&amp;amp;gt;

            &amp;amp;lt;div class="memory-title"&amp;amp;gt;
                ${escapeHtml(memory.title || "Memory")}
            &amp;amp;lt;/div&amp;amp;gt;

            &amp;amp;lt;div class="memory-detail"&amp;amp;gt;
                ${escapeHtml(memory.detail || "")}
            &amp;amp;lt;/div&amp;amp;gt;
        &amp;amp;lt;/div&amp;amp;gt;
    `)
    .join("");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;async function clearMemories() {&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (!confirm("Delete all memories?")) return;

await fetch("/clear", {
    method: "POST"
});

loadMemories();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;function escapeHtml(text) {&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const div = document.createElement("div");
div.textContent = text;

return div.innerHTML;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;loadMemories();&lt;/p&gt;



&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;Best Use of Arduino&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
