From c51382a76e6285dab8c1554576e2c57d411a06ce Mon Sep 17 00:00:00 2001 From: Till JS Date: Mon, 13 Apr 2026 22:48:15 +0200 Subject: [PATCH] feat(brain): add domain events + tools for habits, journal, notes, contacts, body Extends the Companion Brain to 10 modules (from 5). Adds semantic domain events and LLM tools for the next 5 most valuable modules. New domain events (15 types): - Habits: HabitLogged, HabitCreated, HabitDeleted - Journal: JournalEntryCreated, JournalMoodSet, JournalEntryDeleted - Notes: NoteCreated, NoteDeleted - Contacts: ContactCreated, ContactDeleted - Body: WorkoutStarted, WorkoutFinished, SetLogged, MeasurementLogged, EnergyCheckLogged New tools (12 tools): - Habits: log_habit, get_habits, create_habit - Journal: create_journal_entry, set_mood - Notes: create_note - Contacts: create_contact, get_contacts - Body: start_workout, finish_workout, log_measurement Totals: 35 event types, 25 tools across 10 modules. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../apps/web/src/lib/data/events/catalog.ts | 137 ++++++++++++++++++ apps/mana/apps/web/src/lib/data/tools/init.ts | 10 ++ .../lib/modules/body/stores/body.svelte.ts | 34 +++++ .../apps/web/src/lib/modules/body/tools.ts | 71 +++++++++ .../contacts/stores/contacts.svelte.ts | 12 ++ .../web/src/lib/modules/contacts/tools.ts | 50 +++++++ .../modules/habits/stores/habits.svelte.ts | 16 ++ .../apps/web/src/lib/modules/habits/tools.ts | 55 +++++++ .../modules/journal/stores/journal.svelte.ts | 10 ++ .../apps/web/src/lib/modules/journal/tools.ts | 54 +++++++ .../lib/modules/notes/stores/notes.svelte.ts | 6 + .../apps/web/src/lib/modules/notes/tools.ts | 25 ++++ 12 files changed, 480 insertions(+) create mode 100644 apps/mana/apps/web/src/lib/modules/body/tools.ts create mode 100644 apps/mana/apps/web/src/lib/modules/contacts/tools.ts create mode 100644 apps/mana/apps/web/src/lib/modules/habits/tools.ts create mode 100644 apps/mana/apps/web/src/lib/modules/journal/tools.ts create mode 100644 apps/mana/apps/web/src/lib/modules/notes/tools.ts diff --git a/apps/mana/apps/web/src/lib/data/events/catalog.ts b/apps/mana/apps/web/src/lib/data/events/catalog.ts index c719fabfb..f2f9be0ad 100644 --- a/apps/mana/apps/web/src/lib/data/events/catalog.ts +++ b/apps/mana/apps/web/src/lib/data/events/catalog.ts @@ -226,6 +226,118 @@ export type PlacesEventType = | 'TrackingStarted' | 'TrackingStopped'; +// ── Habits ────────────────────────────────────────── + +export interface HabitLoggedPayload { + logId: string; + habitId: string; + habitTitle: string; + note?: string; +} + +export interface HabitCreatedPayload { + habitId: string; + title: string; +} + +export interface HabitDeletedPayload { + habitId: string; + title: string; +} + +export type HabitsEventType = 'HabitLogged' | 'HabitCreated' | 'HabitDeleted'; + +// ── Journal ───────────────────────────────────────── + +export interface JournalEntryCreatedPayload { + entryId: string; + title?: string; + mood?: string; + hasContent: boolean; +} + +export interface JournalMoodSetPayload { + entryId: string; + mood: string; +} + +export interface JournalEntryDeletedPayload { + entryId: string; +} + +export type JournalEventType = 'JournalEntryCreated' | 'JournalMoodSet' | 'JournalEntryDeleted'; + +// ── Notes ─────────────────────────────────────────── + +export interface NoteCreatedPayload { + noteId: string; + title?: string; +} + +export interface NoteDeletedPayload { + noteId: string; +} + +export type NotesEventType = 'NoteCreated' | 'NoteDeleted'; + +// ── Contacts ──────────────────────────────────────── + +export interface ContactCreatedPayload { + contactId: string; + firstName: string; + lastName?: string; +} + +export interface ContactDeletedPayload { + contactId: string; + name: string; +} + +export type ContactsEventType = 'ContactCreated' | 'ContactDeleted'; + +// ── Body ──────────────────────────────────────────── + +export interface WorkoutStartedPayload { + workoutId: string; + title?: string; + routineId?: string; +} + +export interface WorkoutFinishedPayload { + workoutId: string; + title: string; + durationMinutes: number; + setCount: number; +} + +export interface SetLoggedPayload { + setId: string; + workoutId: string; + exerciseId: string; + reps: number; + weight: number; +} + +export interface MeasurementLoggedPayload { + measurementId: string; + type: string; + value: number; + unit: string; +} + +export interface EnergyCheckLoggedPayload { + checkId: string; + energy?: number; + mood?: number; +} + +export type BodyEventType = + | 'WorkoutStarted' + | 'WorkoutFinished' + | 'SetLogged' + | 'MeasurementLogged' + | 'EnergyCheckLogged'; + // ── System Events (Goals, Companion) ──────────────── export interface GoalReachedPayload { @@ -253,6 +365,11 @@ export type ManaEventType = | DrinkEventType | NutriphiEventType | PlacesEventType + | HabitsEventType + | JournalEventType + | NotesEventType + | ContactsEventType + | BodyEventType | SystemEventType; /** @@ -291,6 +408,26 @@ export type ManaEvent = | DomainEvent<'LocationLogged', LocationLoggedPayload> | DomainEvent<'TrackingStarted', TrackingStartedPayload> | DomainEvent<'TrackingStopped', TrackingStoppedPayload> + // Habits + | DomainEvent<'HabitLogged', HabitLoggedPayload> + | DomainEvent<'HabitCreated', HabitCreatedPayload> + | DomainEvent<'HabitDeleted', HabitDeletedPayload> + // Journal + | DomainEvent<'JournalEntryCreated', JournalEntryCreatedPayload> + | DomainEvent<'JournalMoodSet', JournalMoodSetPayload> + | DomainEvent<'JournalEntryDeleted', JournalEntryDeletedPayload> + // Notes + | DomainEvent<'NoteCreated', NoteCreatedPayload> + | DomainEvent<'NoteDeleted', NoteDeletedPayload> + // Contacts + | DomainEvent<'ContactCreated', ContactCreatedPayload> + | DomainEvent<'ContactDeleted', ContactDeletedPayload> + // Body + | DomainEvent<'WorkoutStarted', WorkoutStartedPayload> + | DomainEvent<'WorkoutFinished', WorkoutFinishedPayload> + | DomainEvent<'SetLogged', SetLoggedPayload> + | DomainEvent<'MeasurementLogged', MeasurementLoggedPayload> + | DomainEvent<'EnergyCheckLogged', EnergyCheckLoggedPayload> // System | DomainEvent<'GoalReached', GoalReachedPayload> | DomainEvent<'GoalProgress', GoalProgressPayload>; diff --git a/apps/mana/apps/web/src/lib/data/tools/init.ts b/apps/mana/apps/web/src/lib/data/tools/init.ts index 0caf514c6..39cabaffa 100644 --- a/apps/mana/apps/web/src/lib/data/tools/init.ts +++ b/apps/mana/apps/web/src/lib/data/tools/init.ts @@ -9,6 +9,11 @@ import { calendarTools } from '$lib/modules/calendar/tools'; import { drinkTools } from '$lib/modules/drink/tools'; import { nutriphiTools } from '$lib/modules/nutriphi/tools'; import { placesTools } from '$lib/modules/places/tools'; +import { habitsTools } from '$lib/modules/habits/tools'; +import { journalTools } from '$lib/modules/journal/tools'; +import { notesTools } from '$lib/modules/notes/tools'; +import { contactsTools } from '$lib/modules/contacts/tools'; +import { bodyTools } from '$lib/modules/body/tools'; let initialized = false; @@ -19,5 +24,10 @@ export function initTools(): void { registerTools(drinkTools); registerTools(nutriphiTools); registerTools(placesTools); + registerTools(habitsTools); + registerTools(journalTools); + registerTools(notesTools); + registerTools(contactsTools); + registerTools(bodyTools); initialized = true; } diff --git a/apps/mana/apps/web/src/lib/modules/body/stores/body.svelte.ts b/apps/mana/apps/web/src/lib/modules/body/stores/body.svelte.ts index 46d229f8d..0cd801527 100644 --- a/apps/mana/apps/web/src/lib/modules/body/stores/body.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/body/stores/body.svelte.ts @@ -13,6 +13,7 @@ */ import { encryptRecord } from '$lib/data/crypto'; +import { emitDomainEvent } from '$lib/data/events'; import { createBlock, updateBlock, deleteBlock } from '$lib/data/time-blocks/service'; import { bodyExerciseTable, @@ -183,6 +184,11 @@ export const bodyStore = { const snapshot = toBodyWorkout({ ...newLocal }); await encryptRecord('bodyWorkouts', newLocal); await bodyWorkoutTable.add(newLocal); + emitDomainEvent('WorkoutStarted', 'body', 'bodyWorkouts', workoutId, { + workoutId, + title, + routineId: input.routineId, + }); return snapshot; }, @@ -204,6 +210,16 @@ export const bodyStore = { if (workout?.timeBlockId) { await updateBlock(workout.timeBlockId, { endDate: now }); } + const sets = await bodySetTable.where('workoutId').equals(id).toArray(); + const durationMs = workout?.startedAt + ? Date.now() - new Date(workout.startedAt as string).getTime() + : 0; + emitDomainEvent('WorkoutFinished', 'body', 'bodyWorkouts', id, { + workoutId: id, + title: (workout?.title as string) ?? 'Workout', + durationMinutes: Math.round(durationMs / 60000), + setCount: sets.filter((s) => !s.deletedAt).length, + }); }, async updateWorkout( @@ -262,6 +278,13 @@ export const bodyStore = { const snapshot = toBodySet({ ...newLocal }); await encryptRecord('bodySets', newLocal); await bodySetTable.add(newLocal); + emitDomainEvent('SetLogged', 'body', 'bodySets', newLocal.id, { + setId: newLocal.id, + workoutId: input.workoutId, + exerciseId: input.exerciseId, + reps: input.reps, + weight: input.weight, + }); return snapshot; }, @@ -299,6 +322,12 @@ export const bodyStore = { const snapshot = toBodyMeasurement({ ...newLocal }); await encryptRecord('bodyMeasurements', newLocal); await bodyMeasurementTable.add(newLocal); + emitDomainEvent('MeasurementLogged', 'body', 'bodyMeasurements', newLocal.id, { + measurementId: newLocal.id, + type: input.type, + value: input.value, + unit: input.unit, + }); return snapshot; }, @@ -361,6 +390,11 @@ export const bodyStore = { const snapshot = toBodyCheck({ ...newLocal }); await encryptRecord('bodyChecks', newLocal); await bodyCheckTable.add(newLocal); + emitDomainEvent('EnergyCheckLogged', 'body', 'bodyChecks', newLocal.id, { + checkId: newLocal.id, + energy: input.energy, + mood: input.mood, + }); return snapshot; }, diff --git a/apps/mana/apps/web/src/lib/modules/body/tools.ts b/apps/mana/apps/web/src/lib/modules/body/tools.ts new file mode 100644 index 000000000..fcb08684c --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/body/tools.ts @@ -0,0 +1,71 @@ +import type { ModuleTool } from '$lib/data/tools/types'; + +export const bodyTools: ModuleTool[] = [ + { + name: 'start_workout', + module: 'body', + description: 'Startet ein neues Workout', + parameters: [ + { name: 'title', type: 'string', description: 'Name des Workouts', required: false }, + ], + async execute(params) { + const { bodyStore } = await import('./stores/body.svelte'); + const workout = await bodyStore.startWorkout({ + title: (params.title as string) ?? 'Workout', + }); + return { + success: true, + data: workout, + message: `Workout "${params.title ?? 'Workout'}" gestartet`, + }; + }, + }, + { + name: 'finish_workout', + module: 'body', + description: 'Beendet das aktuelle Workout', + parameters: [ + { name: 'workoutId', type: 'string', description: 'ID des Workouts', required: true }, + ], + async execute(params) { + const { bodyStore } = await import('./stores/body.svelte'); + await bodyStore.finishWorkout(params.workoutId as string); + return { success: true, message: 'Workout beendet' }; + }, + }, + { + name: 'log_measurement', + module: 'body', + description: 'Loggt eine Koerpermessung (Gewicht, Koerperfett, etc.)', + parameters: [ + { + name: 'type', + type: 'string', + description: 'Art der Messung', + required: true, + enum: ['weight', 'bodyFat', 'chest', 'waist', 'hips', 'biceps', 'thighs'], + }, + { name: 'value', type: 'number', description: 'Messwert', required: true }, + { + name: 'unit', + type: 'string', + description: 'Einheit', + required: false, + enum: ['kg', 'lbs', 'percent', 'cm', 'in'], + }, + ], + async execute(params) { + const { bodyStore } = await import('./stores/body.svelte'); + const measurement = await bodyStore.logMeasurement({ + type: params.type as 'weight', + value: params.value as number, + unit: (params.unit as 'kg') ?? 'kg', + }); + return { + success: true, + data: measurement, + message: `${params.type}: ${params.value} ${params.unit ?? 'kg'}`, + }; + }, + }, +]; diff --git a/apps/mana/apps/web/src/lib/modules/contacts/stores/contacts.svelte.ts b/apps/mana/apps/web/src/lib/modules/contacts/stores/contacts.svelte.ts index 428918425..47d67b16e 100644 --- a/apps/mana/apps/web/src/lib/modules/contacts/stores/contacts.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/contacts/stores/contacts.svelte.ts @@ -10,6 +10,7 @@ import { toContact } from '../queries'; import { createArchiveOps } from '@mana/shared-stores'; import { ContactsEvents } from '@mana/shared-utils/analytics'; import { encryptRecord, decryptRecord } from '$lib/data/crypto'; +import { emitDomainEvent } from '$lib/data/events'; import type { LocalContact, Contact } from '../types'; import type { UserProfile } from '$lib/api/profile'; @@ -47,6 +48,11 @@ export const contactsStore = { const plaintextSnapshot = toContact(newLocal); await encryptRecord('contacts', newLocal); await contactTable.add(newLocal); + emitDomainEvent('ContactCreated', 'contacts', 'contacts', newLocal.id, { + contactId: newLocal.id, + firstName: data.firstName ?? '', + lastName: data.lastName, + }); ContactsEvents.contactCreated(); return plaintextSnapshot; }, @@ -89,10 +95,16 @@ export const contactsStore = { }, async deleteContact(id: string) { + const local = await contactTable.get(id); + const decrypted = local ? await decryptRecord('contacts', { ...local }) : null; await contactTable.update(id, { deletedAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); + emitDomainEvent('ContactDeleted', 'contacts', 'contacts', id, { + contactId: id, + name: [decrypted?.firstName, decrypted?.lastName].filter(Boolean).join(' ') || '', + }); ContactsEvents.contactDeleted(); }, diff --git a/apps/mana/apps/web/src/lib/modules/contacts/tools.ts b/apps/mana/apps/web/src/lib/modules/contacts/tools.ts new file mode 100644 index 000000000..45068175c --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/contacts/tools.ts @@ -0,0 +1,50 @@ +import type { ModuleTool } from '$lib/data/tools/types'; +import { contactsStore } from './stores/contacts.svelte'; +import { contactTable } from './collections'; +import { decryptRecords } from '$lib/data/crypto'; +import { toContact } from './queries'; +import type { LocalContact } from './types'; + +export const contactsTools: ModuleTool[] = [ + { + name: 'create_contact', + module: 'contacts', + description: 'Erstellt einen neuen Kontakt', + parameters: [ + { name: 'firstName', type: 'string', description: 'Vorname', required: true }, + { name: 'lastName', type: 'string', description: 'Nachname', required: false }, + { name: 'email', type: 'string', description: 'E-Mail', required: false }, + { name: 'phone', type: 'string', description: 'Telefon', required: false }, + ], + async execute(params) { + const contact = await contactsStore.createContact({ + firstName: params.firstName as string, + lastName: params.lastName as string | undefined, + email: params.email as string | undefined, + phone: params.phone as string | undefined, + }); + return { success: true, data: contact, message: `Kontakt "${params.firstName}" erstellt` }; + }, + }, + { + name: 'get_contacts', + module: 'contacts', + description: 'Gibt alle Kontakte zurueck', + parameters: [], + async execute() { + const all = await contactTable.toArray(); + const active = all.filter((c) => !c.deletedAt && !c.isArchived); + const decrypted = await decryptRecords('contacts', active); + const contacts = decrypted.map(toContact); + return { + success: true, + data: contacts.map((c) => ({ + id: c.id, + name: [c.firstName, c.lastName].filter(Boolean).join(' '), + company: c.company, + })), + message: `${contacts.length} Kontakte`, + }; + }, + }, +]; diff --git a/apps/mana/apps/web/src/lib/modules/habits/stores/habits.svelte.ts b/apps/mana/apps/web/src/lib/modules/habits/stores/habits.svelte.ts index 9e05766bd..8582de7c7 100644 --- a/apps/mana/apps/web/src/lib/modules/habits/stores/habits.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/habits/stores/habits.svelte.ts @@ -5,6 +5,7 @@ * All reads are handled by liveQuery hooks in queries.ts. */ +import { emitDomainEvent } from '$lib/data/events'; import { habitTable, habitLogTable } from '../collections'; import { toHabit } from '../queries'; import { @@ -101,6 +102,10 @@ export const habitsStore = { }; await habitTable.add(newLocal); + emitDomainEvent('HabitCreated', 'habits', 'habits', newLocal.id, { + habitId: newLocal.id, + title: data.title, + }); return toHabit(newLocal); }, @@ -120,10 +125,15 @@ export const habitsStore = { }, async deleteHabit(id: string) { + const habit = await habitTable.get(id); await habitTable.update(id, { deletedAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); + emitDomainEvent('HabitDeleted', 'habits', 'habits', id, { + habitId: id, + title: habit?.title ?? '', + }); // Also soft-delete all logs and their timeBlocks const logs = await habitLogTable.where('habitId').equals(id).toArray(); const now = new Date().toISOString(); @@ -233,6 +243,12 @@ export const habitsStore = { }; await habitLogTable.add(newLog); + emitDomainEvent('HabitLogged', 'habits', 'habitLogs', logId, { + logId, + habitId, + habitTitle: habit?.title ?? '', + note, + }); return newLog; }, diff --git a/apps/mana/apps/web/src/lib/modules/habits/tools.ts b/apps/mana/apps/web/src/lib/modules/habits/tools.ts new file mode 100644 index 000000000..0233ec50c --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/habits/tools.ts @@ -0,0 +1,55 @@ +import type { ModuleTool } from '$lib/data/tools/types'; +import { habitsStore } from './stores/habits.svelte'; +import { habitTable } from './collections'; + +export const habitsTools: ModuleTool[] = [ + { + name: 'log_habit', + module: 'habits', + description: 'Loggt ein Habit (z.B. Sport, Meditation, Lesen)', + parameters: [ + { name: 'habitId', type: 'string', description: 'ID des Habits', required: true }, + { name: 'note', type: 'string', description: 'Optionale Notiz', required: false }, + ], + async execute(params) { + const log = await habitsStore.logHabit( + params.habitId as string, + params.note as string | undefined + ); + return { success: true, data: log, message: 'Habit geloggt' }; + }, + }, + { + name: 'get_habits', + module: 'habits', + description: 'Gibt alle aktiven Habits zurueck', + parameters: [], + async execute() { + const all = await habitTable.toArray(); + const active = all.filter((h) => !h.deletedAt && !h.isArchived); + return { + success: true, + data: active.map((h) => ({ id: h.id, title: h.title, icon: h.icon, color: h.color })), + message: `${active.length} Habits`, + }; + }, + }, + { + name: 'create_habit', + module: 'habits', + description: 'Erstellt ein neues Habit', + parameters: [ + { name: 'title', type: 'string', description: 'Name des Habits', required: true }, + { name: 'icon', type: 'string', description: 'Emoji-Icon', required: false }, + { name: 'color', type: 'string', description: 'Hex-Farbe', required: false }, + ], + async execute(params) { + const habit = await habitsStore.createHabit({ + title: params.title as string, + icon: (params.icon as string) ?? 'star', + color: (params.color as string) ?? '#6366f1', + }); + return { success: true, data: habit, message: `Habit "${habit.title}" erstellt` }; + }, + }, +]; diff --git a/apps/mana/apps/web/src/lib/modules/journal/stores/journal.svelte.ts b/apps/mana/apps/web/src/lib/modules/journal/stores/journal.svelte.ts index 81143de04..174c90056 100644 --- a/apps/mana/apps/web/src/lib/modules/journal/stores/journal.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/journal/stores/journal.svelte.ts @@ -8,6 +8,7 @@ import { journalEntryTable } from '../collections'; import { toJournalEntry } from '../queries'; import { encryptRecord } from '$lib/data/crypto'; +import { emitDomainEvent } from '$lib/data/events'; import { transcribeAudio } from '$lib/voice/transcribe'; import type { JournalEntry, JournalMood, LocalJournalEntry } from '../types'; @@ -48,6 +49,12 @@ export const journalStore = { const plaintextSnapshot = toJournalEntry(newLocal); await encryptRecord('journalEntries', newLocal); await journalEntryTable.add(newLocal); + emitDomainEvent('JournalEntryCreated', 'journal', 'journalEntries', newLocal.id, { + entryId: newLocal.id, + title: data.title ?? undefined, + mood: data.mood ?? undefined, + hasContent: content.length > 0, + }); return plaintextSnapshot; }, @@ -128,6 +135,7 @@ export const journalStore = { deletedAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); + emitDomainEvent('JournalEntryDeleted', 'journal', 'journalEntries', id, { entryId: id }); }, async togglePin(id: string) { @@ -153,6 +161,8 @@ export const journalStore = { mood, updatedAt: new Date().toISOString(), }); + if (mood) + emitDomainEvent('JournalMoodSet', 'journal', 'journalEntries', id, { entryId: id, mood }); }, async archiveEntry(id: string) { diff --git a/apps/mana/apps/web/src/lib/modules/journal/tools.ts b/apps/mana/apps/web/src/lib/modules/journal/tools.ts new file mode 100644 index 000000000..95671b52a --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/journal/tools.ts @@ -0,0 +1,54 @@ +import type { ModuleTool } from '$lib/data/tools/types'; +import { journalStore } from './stores/journal.svelte'; +import type { JournalMood } from './types'; + +const MOOD_ENUM = [ + 'dankbar', + 'glücklich', + 'zufrieden', + 'neutral', + 'nachdenklich', + 'traurig', + 'gestresst', + 'wütend', +]; + +export const journalTools: ModuleTool[] = [ + { + name: 'create_journal_entry', + module: 'journal', + description: + 'Erstellt einen neuen Journal-Eintrag mit optionaler Stimmung (dankbar, glücklich, zufrieden, neutral, nachdenklich, traurig, gestresst, wütend)', + parameters: [ + { name: 'content', type: 'string', description: 'Inhalt des Eintrags', required: true }, + { name: 'title', type: 'string', description: 'Optionaler Titel', required: false }, + { name: 'mood', type: 'string', description: 'Stimmung', required: false, enum: MOOD_ENUM }, + ], + async execute(params) { + const entry = await journalStore.createEntry({ + content: params.content as string, + title: params.title as string | undefined, + mood: params.mood as JournalMood | undefined, + }); + return { + success: true, + data: entry, + message: `Journal-Eintrag erstellt${params.mood ? ` (Stimmung: ${params.mood})` : ''}`, + }; + }, + }, + { + name: 'set_mood', + module: 'journal', + description: 'Erstellt einen Journal-Eintrag mit Stimmung', + parameters: [ + { name: 'mood', type: 'string', description: 'Stimmung', required: true, enum: MOOD_ENUM }, + ], + async execute(params) { + const entry = await journalStore.createEntry({ + mood: params.mood as JournalMood, + }); + return { success: true, data: entry, message: `Stimmung: ${params.mood}` }; + }, + }, +]; diff --git a/apps/mana/apps/web/src/lib/modules/notes/stores/notes.svelte.ts b/apps/mana/apps/web/src/lib/modules/notes/stores/notes.svelte.ts index 6b334dc54..8707ef8e8 100644 --- a/apps/mana/apps/web/src/lib/modules/notes/stores/notes.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/notes/stores/notes.svelte.ts @@ -18,6 +18,7 @@ import { noteTable } from '../collections'; import { toNote } from '../queries'; import type { LocalNote, Note } from '../types'; import { encryptRecord } from '$lib/data/crypto'; +import { emitDomainEvent } from '$lib/data/events'; import { transcribeAudio } from '$lib/voice/transcribe'; export const notesStore = { @@ -36,6 +37,10 @@ export const notesStore = { const plaintextSnapshot = toNote(newLocal); await encryptRecord('notes', newLocal); await noteTable.add(newLocal); + emitDomainEvent('NoteCreated', 'notes', 'notes', newLocal.id, { + noteId: newLocal.id, + title: data.title, + }); return plaintextSnapshot; }, @@ -103,6 +108,7 @@ export const notesStore = { deletedAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); + emitDomainEvent('NoteDeleted', 'notes', 'notes', id, { noteId: id }); }, async togglePin(id: string) { diff --git a/apps/mana/apps/web/src/lib/modules/notes/tools.ts b/apps/mana/apps/web/src/lib/modules/notes/tools.ts new file mode 100644 index 000000000..d9de5ce84 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/notes/tools.ts @@ -0,0 +1,25 @@ +import type { ModuleTool } from '$lib/data/tools/types'; +import { notesStore } from './stores/notes.svelte'; + +export const notesTools: ModuleTool[] = [ + { + name: 'create_note', + module: 'notes', + description: 'Erstellt eine neue Notiz', + parameters: [ + { name: 'title', type: 'string', description: 'Titel', required: false }, + { name: 'content', type: 'string', description: 'Inhalt', required: true }, + ], + async execute(params) { + const note = await notesStore.createNote({ + title: params.title as string | undefined, + content: params.content as string, + }); + return { + success: true, + data: note, + message: `Notiz "${note.title || 'Unbenannt'}" erstellt`, + }; + }, + }, +];