Minimal Self‑Hosted Personal Assistant with OpenMuse

A lightweight Python clone of OpenMuse using Flask and SQLite

Welcome to another official UVF IT guide! In this tutorial we’ll build a tiny personal‑assistant web service inspired by OpenMuse, using only free, open‑source tools you can run on your laptop.

In about 45 minutes you’ll have a Flask API that stores tasks in SQLite, a minimal HTML UI, and the ability to add and list notes – the core of a personal assistant.

Before You Start

Component Overview
LOCAL STACK reads/writes HTTP requests Flask App API + UI SQLite DB HTML UI Browser
1

Phase 1: Scaffold the Project

Create a virtual environment and install Flask; this gives us an isolated runtime.

We also set up a basic folder structure so later files have a predictable place.

mkdir openmuse_clone && cd openmuse_clone
python -m venv venv
# Activate the environment
# Windows:
#   venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate
pip install --upgrade pip
pip install Flask
2

Phase 2: Define the Data Model

We’ll use SQLite via Python’s built‑in sqlite3 module; a single table stores notes with timestamps.

The script creates the database file and the notes table if they don’t already exist.

init_db.py

import sqlite3, os
DB_PATH = os.path.join(os.path.dirname(__file__), 'notes.db')
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute('''
CREATE TABLE IF NOT EXISTS notes (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    content TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()
conn.close()
print(f'Database initialized at {DB_PATH}')
3

Phase 3: Build the Flask API & UI

In this step we create a single Flask application that both serves a tiny HTML UI and provides JSON endpoints for managing notes. The UI lets you add a note via a form and displays all saved notes, while the API offers CRUD operations for programmatic access. Storing data in SQLite keeps the clone lightweight and requires no external services. By keeping everything in one file we make the project easy to understand and extend.

app.py

import sqlite3
from flask import Flask, request, jsonify, render_template_string, g

app = Flask(__name__)
DB_PATH = 'notes.db'

# ---------- Database helpers ----------

def get_db():
    if 'db' not in g:
        g.db = sqlite3.connect(DB_PATH)
        g.db.row_factory = sqlite3.Row
    return g.db

@app.teardown_appcontext
def close_db(exception):
    db = g.pop('db', None)
    if db is not None:
        db.close()

def init_db():
    db = get_db()
    db.execute('''CREATE TABLE IF NOT EXISTS notes (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    content TEXT NOT NULL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                )''')
    db.commit()

# Initialize DB on first request
@app.before_first_request
def setup():
    init_db()

# ---------- API routes ----------
@app.route('/api/notes', methods=['GET'])
def list_notes():
    db = get_db()
    notes = db.execute('SELECT id, content, created_at FROM notes ORDER BY created_at DESC').fetchall()
    return jsonify([dict(note) for note in notes])

@app.route('/api/notes', methods=['POST'])
def add_note():
    content = request.json.get('content') if request.is_json else request.form.get('content')
    if not content:
        return jsonify({'error': 'Content required'}), 400
    db = get_db()
    db.execute('INSERT INTO notes (content) VALUES (?)', (content,))
    db.commit()
    return jsonify({'status': 'added'}), 201

# ---------- UI route ----------
HTML_TEMPLATE = '''
<!doctype html>
<title>My Notes</title>
<h1>My Notes</h1>
<form method="post" action="/api/notes">
  <input name="content" placeholder="Write a note" required>
  <button type="submit">Add</button>
</form>
<ul>
{% for note in notes %}
  <li>{{ note.content }} <small>({{ note.created_at }})</small></li>
{% else %}
  <li>No notes yet.</li>
{% endfor %}
</ul>
'''

@app.route('/')
def index():
    db = get_db()
    notes = db.execute('SELECT content, created_at FROM notes ORDER BY created_at DESC').fetchall()
    return render_template_string(HTML_TEMPLATE, notes=notes)

if __name__ == '__main__':
    app.run(debug=True)
4

Phase 4: Run Locally

Now that the code is in place we need to create the SQLite file and launch the Flask development server. The first request will automatically create the database if it doesn't exist, so simply starting the app is enough. Running with `debug=True` gives helpful error messages while you experiment.

Make sure you are inside the virtual environment where Flask is installed before executing the command.

export FLASK_APP=app.py
flask run
5

Phase 5: Verify & Extend

Open your browser at http://127.0.0.1:5000 and you should see the "My Notes" page with an empty list. Use the form to add a note; the page will refresh showing the new entry. To confirm the API, request the JSON endpoint and you’ll see the same data in machine‑readable form.

From here you can extend the clone: add tags, implement search, or protect the routes with simple authentication. The structure is intentionally minimal so you can iterate quickly.

curl -s http://127.0.0.1:5000/api/notes | python -m json.tool

Verifying the Stack

CheckCommand / Why it matters
Virtual environment activated and Flask installedpip list | grep Flask shows version 2.3.2
Database file createdls notes.db shows the file after running init_db.py
Web UI loads with empty listVisit http://127.0.0.1:5000 and see 'My Notes' with no items
Adding a note updates UI and APISubmit form, then curl http://127.0.0.1:5000/api/notes shows JSON with the new entry

You Did It!

Your minimal OpenMuse‑style personal assistant is up and running. Feel free to experiment – add authentication, integrate a free LLM API, or containerize with Docker for true self‑hosting.