Files
idriz/CLAUDE.md
idriz 00b4525588 Dodaj CLAUDE.md: vodič za pravljenje novih mini-app-ova
Detaljan vodič (orijentacija, dva procesa, arhitektura plugina, AppSpec/AppTool/
AppContext, inline vs background, korak-po-korak skelet novog app-a sa admin
Blueprint-om, i zlatna pravila: jezgro app-agnostično, admin bez openai, tanak
AppContext, smoke test poslije svakog commita).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LzKpYaEsuWDevuNnvSjcFW
2026-09-11 14:20:13 +02:00

8.2 KiB

CLAUDE.md — vodič za rad na idrizu

Idriz je glasovni pomoćnik za djecu (OpenAI Realtime, izlaz na printer + govor). Komunikacija sa korisnikom je isključivo na bosanskom (ijekavica), i sam idriz priča samo bosanski. Kod, komentari i promptovi su na bosanskom.

Repo: ~/src/idriz, backup na Gitea ssh://git@192.168.88.2:221/senaduka/idriz.git (grana main). NE piši ni u jedan drugi repo. Sav aplikacioni kod ide u ovaj repo. Commit trailer: Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> (+ Claude-Session).

Dva procesa (korisnički systemd servisi)

  • idriz.service — asistent (asyncio, OpenAI Realtime). venv: ~/src/idriz/.venv. Ima openai. Pokreće python -m idriz.
  • idriz-admin.service — LAN web panel (Flask + waitress) na :8080, PAM login. venv: ~/src/idriz/admin/.venv. NEMA openai. Pokreće admin/app.py.

Restart nakon izmjena i smoke test:

systemctl --user restart idriz idriz-admin
systemctl --user is-active idriz idriz-admin
journalctl --user -u idriz -n 15 --no-pager   # traži "povezan na ..." i greške
curl -s -o /dev/null -w "%{http_code}\n" http://192.168.1.10:8080/login   # 200

Arhitektura: mini-app-ovi (plugini)

Jezgro idriz/assistant.py je app-agnostično: audio petlje, realtime sesija, generički pokretač alata (_run_inline / _run_background), notify_model, _nudge. Prompt, alati, pozdrav i dispatch se sklapaju iz registra app-ova — jezgro ne sadrži nijedno ime app-a.

Svaki app je folder u idriz/apps/<ime>/. Registruje se na dva mjesta:

  • idriz/apps/__init__.py → load_apps() (za asistenta),
  • admin/app.py → ADMIN_APPS (samo ako app ima admin stranicu).

Tipovi su u idriz/apps/base.py:

  • AppSpec(name, title, instructions="", greeting="", tools=[])
  • AppTool(schema, handler, mode="inline") — handler je async (ctx, args) -> str
  • AppContext (uski most ka runtime-u; držati ga tankim):
    • ctx.client — AsyncOpenAI
    • ctx.cfg — Config (npr. ctx.cfg.game_model, text_model, image_model, out_dir, printer, dry_run, max_pages)
    • ctx.get_state(key) / ctx.set_state(key, val) — per-razgovor stanje app-a
    • await ctx.notify(text) — ubaci sistemsku poruku modelu i traži novi odgovor
    • await ctx.nudge(instructions) — kratko podstakni model (npr. filler)
    • ctx.background(coro) — pokreni praćeni pozadinski task

mode: inline vs background

  • inline — brzo (≲ par sekundi). Rezultat handlera ide ODMAH nazad modelu kao function_call_output i model ga izgovori. Koristi za igre, brze upite. Handler MORA biti brz; ako treba dug posao, pokreni ga preko ctx.background(...) i handler odmah vrati kratku potvrdu (vidi superheroji izbor→auto-presuda).
  • background — dug posao (štampanje, generisanje slike). Runtime modelu odmah kaže "radim na tome", pa kad handler završi pošalje rezultat preko notify_model (GOTOVO. <tvoj return> ... ili GREŠKA. ...). Handler samo vrati rezultat-tekst.

schema (OpenAI Realtime function tool)

SCHEMA = {
    "type": "function",
    "name": "<ime_alata>",        # jedinstveno u cijelom sistemu
    "description": "Šta radi i KADA ga zvati. Piši jasno, na bosanskom.",
    "parameters": {
        "type": "object",
        "properties": { "arg": {"type": "string", "description": "..."} },
        "required": ["arg"],
    },
}

Kako napraviti NOVI app (korak po korak)

Primjer: app kviz.

  1. Napravi folder i prazan __init__.py (mora biti lagan — bez openai):
mkdir -p idriz/apps/kviz
touch idriz/apps/kviz/__init__.py
  1. idriz/apps/kviz/manifest.py — prompt fragment, sheme, handleri, SPEC:
"""Mini-app 'Kviz'."""
from ..base import AppContext, AppSpec, AppTool

INSTRUCTIONS = """KVIZ (alat: kviz_start): ... opiši djetetu razumljivo tok i pravila ...
- (pravila, kad zvati alat, kako se ponašati)"""

START_SCHEMA = {
    "type": "function", "name": "kviz_start",
    "description": "Započinje kviz. Pozvati kad dijete kaže da želi kviz.",
    "parameters": {"type": "object", "properties": {
        "tema": {"type": "string", "description": "Tema kviza."}}, "required": ["tema"]},
}

async def start(ctx: AppContext, args: dict) -> str:
    # ctx.set_state("kviz", <stanje>)  ako treba pamtiti kroz razgovor
    # ctx.client / ctx.cfg.text_model za LLM pozive
    return "Kviz je počeo! ..."   # inline: model odmah izgovori

SPEC = AppSpec(
    name="kviz", title="Kviz",
    instructions=INSTRUCTIONS,
    greeting="igrati kviz",              # ubaci se u pozdrav ("a možeš i: ..., igrati kviz")
    tools=[AppTool(START_SCHEMA, start, "inline")],
)
  1. Registruj u idriz/apps/__init__.py (LIJENI uvoz unutar funkcije!):
def load_apps():
    from .bojanka import manifest as bojanka
    from .sazetak import manifest as sazetak
    from .superheroji import manifest as superheroji
    from .kviz import manifest as kviz          # <—
    return [sazetak.SPEC, bojanka.SPEC, superheroji.SPEC, kviz.SPEC]
  1. (Opciono) Logika/stanje: logic.py (smije openai) i/ili store.py. store.py mora ostati bez openai ako ga uvozi admin. Za trajno stanje/config koristi JSON pod ~/.local/share/idriz/ ili ~/.config/idriz/, uz env-override imena putanje (vidi superheroji/store.py: IDRIZ_HEROES_PATH, IDRIZ_GAME_CONFIG_PATH).

  2. (Opciono) Admin stranica: idriz/apps/kviz/admin.py (samo lagani uvozi):

from flask import Blueprint, render_template, request, redirect, url_for, flash
from . import store   # bez openai!

BP = Blueprint("kviz", __name__, template_folder="templates", url_prefix="/kviz")
NAV = {"endpoint": "kviz.index", "page": "kviz", "title": "Kviz", "icon": "❓"}

@BP.route("/", methods=["GET", "POST"])
def index():
    # POST: sačuvaj config; GET: render
    return render_template("kviz.html", active_page="kviz")

Template ide u idriz/apps/kviz/templates/kviz.html i {% extends "base.html" %} (base je u admin/templates/; auth štiti globalni before_request). Zatim u admin/app.py:

from idriz.apps.kviz import admin as _kviz_admin
ADMIN_APPS = [_superheroji_admin, _kviz_admin]
  1. Testiraj (bez štampanja/pravih poziva gdje se može), pa commit + smoke test:
.venv/bin/python -c "import os; os.environ.setdefault('OPENAI_API_KEY','x'); \
  from idriz.config import Config; from idriz.assistant import Assistant; \
  a=Assistant(Config()); print([t['name'] for t in a.tool_schemas])"
admin/.venv/bin/python -c "import sys,pathlib; sys.path.insert(0,'.'); import app; print('admin ok')"
systemctl --user restart idriz idriz-admin && systemctl --user is-active idriz idriz-admin

Zlatna pravila (da se ne pogriješi)

  1. Jezgro ostaje app-agnostično. Ne dodaji app-imena ni app-prompt u assistant.py/INSTRUCTIONS. Sve app-specifično ide u apps/<ime>/.
  2. Admin nema openai. Šta admin uvozi (admin.py, store.py, apps/__init__.py, apps/base.py) NE smije povlačiti openai. Teški dio (manifest.py, logic.py) uvozi samo asistent. Zato je uvoz manifesta u load_apps() LIJEN. Provjera (iz ~/src/idriz): admin/.venv/bin/python -c "import sys; sys.path.insert(0,'.'); from idriz.apps.<ime> import store; print('openai' not in sys.modules)" → mora True.
  3. AppContext drži tankim — par metoda. Ako počne bujati, to je znak da nešto pripada samom app-u, a ne mostu.
  4. inline handler mora biti brz. Dug posao → ctx.background(...) + kratka potvrda odmah; rezultat kasnije preko ctx.notify(...).
  5. Ime alata jedinstveno u cijelom sistemu (dispatch je globalni rječnik).
  6. Poslije SVAKOG commita: restart servisa + smoke test (import, dispatch, is-active, admin 200). Radi u malim koracima.
  7. Realtime specifičnosti: jedan odgovor u isto vrijeme — programski response.create ide kroz notify_model/_nudge koji čekaju _wait_idle. Ne šalji sirovi response.create iz app-a.
  8. Bosanski svugdje; sadržaj primjeren djeci.

Mreža / hardver (ukratko)

IP mašine 192.168.1.10; Gitea 192.168.88.2:221 (drugi subnet, ruta preko 192.168.1.1). Printer: CUPS idriz-printer. Detalji u memoriji (~/.claude/projects/-home-idriz/memory/, indeks MEMORY.md).