Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
"mcpServers": {
"mastra": {
"command": "npx",
"args": [
"-y",
"@mastra/mcp-docs-server@beta"
]
"args": ["-y", "@mastra/mcp-docs-server@beta"],
"alwaysAllow": ["mastraDocs"]
}
}
}
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ Auguste is an open-source, AI-powered meal planning application built with Node.
# Development server (via Mastra)
pnpm run dev

# Run Mastra Studio (Visual Interface)
pnpm run studio

# Build all packages
pnpm run build

Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ cp .env.example .env
# Add your OPENROUTER_API_KEY to .env
```

### Running Mastra Studio

To visualize your agents and workflows, run the local studio:

```bash
pnpm run studio
```

This will start the studio at [http://localhost:4111/studio](http://localhost:4111/studio).

### Database Management

Auguste uses Drizzle ORM. Detailed documentation on schema management, migrations, and seeding can be found in [docs/database-management.md](docs/database-management.md).
Expand Down
117 changes: 110 additions & 7 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import express from 'express';
import cors from 'cors';
import { mastra } from '@auguste/core';
import {
mastra,
RequestContext,
createFamily,
getFamilyById,
getMembersByFamilyId,
getAvailabilityByFamilyId,
getPlannerSettingsByFamilyId,
} from '@auguste/core';
import dotenv from 'dotenv';
import path from 'path';
import { fileURLToPath } from 'url';
Expand All @@ -16,40 +24,135 @@ const port = process.env.PORT || 3001;
app.use(cors());
app.use(express.json());

// Request logging middleware
app.use((req, res, next) => {
const start = Date.now();
const { method, url } = req;

console.log(`→ ${method} ${url}`, req.body ? JSON.stringify(req.body).slice(0, 200) : '');

res.on('finish', () => {
const duration = Date.now() - start;
console.log(`← ${method} ${url} ${res.statusCode} (${duration}ms)`);
});

next();
});

app.get('/health', (_req, res) => {
res.json({ status: 'ok' });
});

app.post('/api/chat', async (req, res) => {
const { messages, agentId = 'onboardingAgent', threadId, resourceId } = req.body;
const { message, agentId = 'onboardingAgent', threadId, resourceId, familyId } = req.body;

try {
const agent = mastra.getAgent(agentId);

// Set headers for plain text streaming
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.setHeader('Transfer-Encoding', 'chunked');
// Set headers for SSE streaming
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');

const result = await agent.stream(messages[messages.length - 1].content, {
const requestContext = new RequestContext();
if (familyId) {
requestContext.set('familyId', familyId);
}

const result = await agent.stream(message, {
threadId,
resourceId,
requestContext,
});

// Stream text chunks as SSE events
for await (const chunk of result.textStream) {
res.write(chunk);
res.write(`data: ${JSON.stringify({ type: 'text', content: chunk })}\n\n`);
}

// Send done event
res.write(`data: ${JSON.stringify({ type: 'done' })}\n\n`);
res.end();
} catch (error) {
console.error('Chat error:', error);
if (!res.headersSent) {
res.status(500).json({ error: 'Failed to generate response' });
} else {
res.write(
`data: ${JSON.stringify({ type: 'error', content: 'Failed to generate response' })}\n\n`,
);
res.end();
}
}
});

app.post('/api/family', async (req, res) => {
try {
const { name, country, language } = req.body;
if (!name || !country || !language) {
return res.status(400).json({ error: 'Name, country, and language are required' });
}

const family = await createFamily({ name, country, language });
res.json(family);
} catch (error) {
console.error('Error creating family:', error);
res.status(500).json({ error: 'Failed to create family' });
}
});

// Family data endpoints
app.get('/api/family/:id', async (req, res) => {
try {
const { id } = req.params;
const family = await getFamilyById(id);

if (!family) {
return res.status(404).json({ error: 'Family not found' });
}

res.json(family);
} catch (error) {
console.error('Error fetching family:', error);
res.status(500).json({ error: 'Failed to fetch family' });
}
});

app.get('/api/family/:id/members', async (req, res) => {
try {
const { id } = req.params;
const members = await getMembersByFamilyId(id);
res.json(members);
} catch (error) {
console.error('Error fetching members:', error);
res.status(500).json({ error: 'Failed to fetch members' });
}
});

app.get('/api/family/:id/availability', async (req, res) => {
try {
const { id } = req.params;
const availability = await getAvailabilityByFamilyId(id);
res.json(availability);
} catch (error) {
console.error('Error fetching availability:', error);
res.status(500).json({ error: 'Failed to fetch availability' });
}
});

app.get('/api/family/:id/settings', async (req, res) => {
try {
const { id } = req.params;
const settings = await getPlannerSettingsByFamilyId(id);

// Return null if no settings exist - UI can show empty state
res.json(settings);
} catch (error) {
console.error('Error fetching settings:', error);
res.status(500).json({ error: 'Failed to fetch settings' });
}
});

app.listen(port, () => {
console.log(`API running at http://localhost:${port}`);
});
6 changes: 6 additions & 0 deletions apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/jpeg" href="/favicon.jpg" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Playfair+Display:wght@600;700&display=swap"
rel="stylesheet"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Auguste | AI Culinary Assistant</title>
<meta
Expand Down
Loading