ralphx 0.2.2__py3-none-any.whl → 0.3.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ralphx/__init__.py +1 -1
- ralphx/api/main.py +9 -1
- ralphx/api/routes/auth.py +730 -65
- ralphx/api/routes/config.py +3 -56
- ralphx/api/routes/export_import.py +795 -0
- ralphx/api/routes/loops.py +4 -4
- ralphx/api/routes/planning.py +19 -5
- ralphx/api/routes/projects.py +84 -2
- ralphx/api/routes/templates.py +115 -2
- ralphx/api/routes/workflows.py +22 -22
- ralphx/cli.py +21 -6
- ralphx/core/auth.py +346 -171
- ralphx/core/database.py +615 -167
- ralphx/core/executor.py +0 -3
- ralphx/core/loop.py +15 -2
- ralphx/core/loop_templates.py +69 -3
- ralphx/core/planning_service.py +109 -21
- ralphx/core/preview.py +9 -25
- ralphx/core/project_db.py +175 -75
- ralphx/core/project_export.py +469 -0
- ralphx/core/project_import.py +670 -0
- ralphx/core/sample_project.py +430 -0
- ralphx/core/templates.py +46 -9
- ralphx/core/workflow_executor.py +35 -5
- ralphx/core/workflow_export.py +606 -0
- ralphx/core/workflow_import.py +1149 -0
- ralphx/examples/sample_project/DESIGN.md +345 -0
- ralphx/examples/sample_project/README.md +37 -0
- ralphx/examples/sample_project/guardrails.md +57 -0
- ralphx/examples/sample_project/stories.jsonl +10 -0
- ralphx/mcp/__init__.py +6 -2
- ralphx/mcp/registry.py +3 -3
- ralphx/mcp/server.py +99 -29
- ralphx/mcp/tools/__init__.py +4 -0
- ralphx/mcp/tools/help.py +204 -0
- ralphx/mcp/tools/workflows.py +114 -32
- ralphx/mcp_server.py +6 -2
- ralphx/static/assets/index-0ovNnfOq.css +1 -0
- ralphx/static/assets/index-CY9s08ZB.js +251 -0
- ralphx/static/assets/index-CY9s08ZB.js.map +1 -0
- ralphx/static/index.html +14 -0
- {ralphx-0.2.2.dist-info → ralphx-0.3.5.dist-info}/METADATA +34 -12
- {ralphx-0.2.2.dist-info → ralphx-0.3.5.dist-info}/RECORD +45 -30
- {ralphx-0.2.2.dist-info → ralphx-0.3.5.dist-info}/WHEEL +0 -0
- {ralphx-0.2.2.dist-info → ralphx-0.3.5.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# Excuse Generator - Design Document
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Excuse Generator is a fun, lightweight web application that helps users generate creative excuses for various life situations. Whether you're late to a meeting, forgot a birthday, or missed a deadline, this app provides entertaining (and sometimes surprisingly convincing) excuses at the click of a button.
|
|
6
|
+
|
|
7
|
+
## Goals
|
|
8
|
+
|
|
9
|
+
1. **Entertainment First** - The app should be fun to use and generate amusing content
|
|
10
|
+
2. **Simple & Fast** - No account required, instant results, mobile-friendly
|
|
11
|
+
3. **Customizable Output** - Users can tune excuses by category and tone
|
|
12
|
+
4. **Memorable** - Save favorites and track history for repeat use
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
### Core Features
|
|
17
|
+
|
|
18
|
+
#### 1. Excuse Generation
|
|
19
|
+
- Select a **category** for the situation:
|
|
20
|
+
- Late to work/meeting
|
|
21
|
+
- Missed deadline
|
|
22
|
+
- Forgot birthday/anniversary
|
|
23
|
+
- Didn't reply to message
|
|
24
|
+
- Skipped event/party
|
|
25
|
+
- Generic/Other
|
|
26
|
+
- Select a **tone** for the excuse:
|
|
27
|
+
- Professional (workplace appropriate, formal)
|
|
28
|
+
- Casual (friendly, conversational)
|
|
29
|
+
- Dramatic (over-the-top, theatrical)
|
|
30
|
+
- Absurd (ridiculous, comedy-focused)
|
|
31
|
+
- Click "Generate" to get a random excuse matching the criteria
|
|
32
|
+
- Each excuse is built from templates with variable substitution for variety
|
|
33
|
+
|
|
34
|
+
#### 2. Favorites System
|
|
35
|
+
- Click a heart icon to save an excuse to favorites
|
|
36
|
+
- View all saved favorites in a dedicated tab
|
|
37
|
+
- Remove excuses from favorites
|
|
38
|
+
- Favorites persist in the database
|
|
39
|
+
|
|
40
|
+
#### 3. Believability Rating
|
|
41
|
+
- After generating an excuse, rate it 1-5 stars for "believability"
|
|
42
|
+
- Ratings are stored and can be viewed in history
|
|
43
|
+
- Optional: Show average rating for excuse templates
|
|
44
|
+
|
|
45
|
+
#### 4. History View
|
|
46
|
+
- Browse all previously generated excuses
|
|
47
|
+
- Search by keyword
|
|
48
|
+
- Filter by category, tone, or date range
|
|
49
|
+
- See when each excuse was generated
|
|
50
|
+
- View ratings given
|
|
51
|
+
|
|
52
|
+
#### 5. Excuse of the Day
|
|
53
|
+
- Homepage displays a featured "Excuse of the Day"
|
|
54
|
+
- Rotates daily (seeded by date for consistency)
|
|
55
|
+
- Pulls from curated high-quality excuse templates
|
|
56
|
+
|
|
57
|
+
#### 6. Copy to Clipboard
|
|
58
|
+
- One-click button to copy excuse text
|
|
59
|
+
- Visual feedback (checkmark animation) on successful copy
|
|
60
|
+
- Works on mobile and desktop
|
|
61
|
+
|
|
62
|
+
## Technical Architecture
|
|
63
|
+
|
|
64
|
+
### Tech Stack
|
|
65
|
+
|
|
66
|
+
| Component | Technology |
|
|
67
|
+
|-----------|------------|
|
|
68
|
+
| Backend | FastAPI (Python 3.10+) |
|
|
69
|
+
| Database | SQLite |
|
|
70
|
+
| Frontend | Vanilla HTML/CSS/JS |
|
|
71
|
+
| Styling | Custom CSS (no framework) |
|
|
72
|
+
|
|
73
|
+
### Project Structure
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
excuse-generator/
|
|
77
|
+
├── main.py # FastAPI application
|
|
78
|
+
├── database.py # SQLite database operations
|
|
79
|
+
├── models.py # Pydantic models
|
|
80
|
+
├── templates.py # Excuse template data
|
|
81
|
+
├── requirements.txt # Python dependencies
|
|
82
|
+
├── data/
|
|
83
|
+
│ └── excuses.db # SQLite database file
|
|
84
|
+
└── static/
|
|
85
|
+
├── index.html # Main SPA page
|
|
86
|
+
├── styles.css # Application styles
|
|
87
|
+
└── app.js # Frontend JavaScript
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Database Schema
|
|
91
|
+
|
|
92
|
+
```sql
|
|
93
|
+
-- Excuse templates (seed data)
|
|
94
|
+
CREATE TABLE excuse_templates (
|
|
95
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
96
|
+
category TEXT NOT NULL,
|
|
97
|
+
tone TEXT NOT NULL,
|
|
98
|
+
template TEXT NOT NULL,
|
|
99
|
+
variables TEXT, -- JSON array of variable names
|
|
100
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
-- Generated excuses (history)
|
|
104
|
+
CREATE TABLE generated_excuses (
|
|
105
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
106
|
+
template_id INTEGER REFERENCES excuse_templates(id),
|
|
107
|
+
excuse_text TEXT NOT NULL,
|
|
108
|
+
category TEXT NOT NULL,
|
|
109
|
+
tone TEXT NOT NULL,
|
|
110
|
+
rating INTEGER, -- 1-5 stars, NULL if not rated
|
|
111
|
+
is_favorite BOOLEAN DEFAULT FALSE,
|
|
112
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
-- Daily excuse selection
|
|
116
|
+
CREATE TABLE daily_excuse (
|
|
117
|
+
date TEXT PRIMARY KEY, -- YYYY-MM-DD format
|
|
118
|
+
template_id INTEGER REFERENCES excuse_templates(id),
|
|
119
|
+
excuse_text TEXT NOT NULL
|
|
120
|
+
);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### API Endpoints
|
|
124
|
+
|
|
125
|
+
#### GET /api/health
|
|
126
|
+
Health check endpoint.
|
|
127
|
+
|
|
128
|
+
**Response:**
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"status": "healthy",
|
|
132
|
+
"version": "1.0.0"
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### GET /api/generate
|
|
137
|
+
Generate a random excuse.
|
|
138
|
+
|
|
139
|
+
**Query Parameters:**
|
|
140
|
+
- `category` (string, optional): Filter by category
|
|
141
|
+
- `tone` (string, optional): Filter by tone
|
|
142
|
+
|
|
143
|
+
**Response:**
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"id": 42,
|
|
147
|
+
"excuse": "My cat accidentally unplugged my alarm clock while chasing a ghost.",
|
|
148
|
+
"category": "late",
|
|
149
|
+
"tone": "absurd",
|
|
150
|
+
"template_id": 15
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### POST /api/excuses/{id}/favorite
|
|
155
|
+
Toggle favorite status for an excuse.
|
|
156
|
+
|
|
157
|
+
**Response:**
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"id": 42,
|
|
161
|
+
"is_favorite": true
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### POST /api/excuses/{id}/rate
|
|
166
|
+
Rate an excuse.
|
|
167
|
+
|
|
168
|
+
**Request Body:**
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"rating": 4
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Response:**
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"id": 42,
|
|
179
|
+
"rating": 4
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### GET /api/favorites
|
|
184
|
+
Get all favorited excuses.
|
|
185
|
+
|
|
186
|
+
**Response:**
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"favorites": [
|
|
190
|
+
{
|
|
191
|
+
"id": 42,
|
|
192
|
+
"excuse": "...",
|
|
193
|
+
"category": "late",
|
|
194
|
+
"tone": "absurd",
|
|
195
|
+
"rating": 4,
|
|
196
|
+
"created_at": "2024-01-15T10:30:00Z"
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### GET /api/history
|
|
203
|
+
Get excuse generation history.
|
|
204
|
+
|
|
205
|
+
**Query Parameters:**
|
|
206
|
+
- `category` (string, optional): Filter by category
|
|
207
|
+
- `tone` (string, optional): Filter by tone
|
|
208
|
+
- `search` (string, optional): Search in excuse text
|
|
209
|
+
- `limit` (int, default 50): Max results
|
|
210
|
+
- `offset` (int, default 0): Pagination offset
|
|
211
|
+
|
|
212
|
+
**Response:**
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"excuses": [...],
|
|
216
|
+
"total": 150,
|
|
217
|
+
"limit": 50,
|
|
218
|
+
"offset": 0
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### GET /api/excuse-of-the-day
|
|
223
|
+
Get today's featured excuse.
|
|
224
|
+
|
|
225
|
+
**Response:**
|
|
226
|
+
```json
|
|
227
|
+
{
|
|
228
|
+
"excuse": "The traffic was terrible because a family of ducks decided to cross the highway.",
|
|
229
|
+
"category": "late",
|
|
230
|
+
"tone": "casual",
|
|
231
|
+
"date": "2024-01-15"
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### GET /api/categories
|
|
236
|
+
Get list of available categories.
|
|
237
|
+
|
|
238
|
+
**Response:**
|
|
239
|
+
```json
|
|
240
|
+
{
|
|
241
|
+
"categories": ["late", "deadline", "birthday", "message", "event", "other"]
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### GET /api/tones
|
|
246
|
+
Get list of available tones.
|
|
247
|
+
|
|
248
|
+
**Response:**
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"tones": ["professional", "casual", "dramatic", "absurd"]
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## User Interface
|
|
256
|
+
|
|
257
|
+
### Layout
|
|
258
|
+
|
|
259
|
+
The app uses a single-page layout with tabs:
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
+------------------------------------------+
|
|
263
|
+
| EXCUSE GENERATOR [History] [*] |
|
|
264
|
+
+------------------------------------------+
|
|
265
|
+
| |
|
|
266
|
+
| Category: [Late ▼] Tone: [Casual ▼] |
|
|
267
|
+
| |
|
|
268
|
+
| +------------------------------------+ |
|
|
269
|
+
| | | |
|
|
270
|
+
| | "Sorry I'm late, my neighbor's | |
|
|
271
|
+
| | parrot learned to mimic my | |
|
|
272
|
+
| | alarm and kept hitting snooze." | |
|
|
273
|
+
| | | |
|
|
274
|
+
| | [Copy] [♡] Rating: ★★★★☆ | |
|
|
275
|
+
| +------------------------------------+ |
|
|
276
|
+
| |
|
|
277
|
+
| [ Generate New Excuse ] |
|
|
278
|
+
| |
|
|
279
|
+
+------------------------------------------+
|
|
280
|
+
| Excuse of the Day: |
|
|
281
|
+
| "My GPS took me through a time warp..." |
|
|
282
|
+
+------------------------------------------+
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Responsive Design
|
|
286
|
+
|
|
287
|
+
- Mobile-first approach
|
|
288
|
+
- Single column on phones (<768px)
|
|
289
|
+
- Centered card layout on tablets/desktop
|
|
290
|
+
- Touch-friendly buttons (44px minimum)
|
|
291
|
+
|
|
292
|
+
### Color Scheme
|
|
293
|
+
|
|
294
|
+
- Primary: #6366F1 (Indigo)
|
|
295
|
+
- Background: #F8FAFC (Light gray)
|
|
296
|
+
- Card: #FFFFFF
|
|
297
|
+
- Text: #1E293B (Slate)
|
|
298
|
+
- Accent: #F59E0B (Amber for favorites)
|
|
299
|
+
|
|
300
|
+
## Excuse Template System
|
|
301
|
+
|
|
302
|
+
Excuses are generated from templates with variable placeholders:
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
templates = [
|
|
306
|
+
{
|
|
307
|
+
"category": "late",
|
|
308
|
+
"tone": "absurd",
|
|
309
|
+
"template": "My {pet} accidentally {action} my {item}.",
|
|
310
|
+
"variables": {
|
|
311
|
+
"pet": ["cat", "dog", "hamster", "goldfish"],
|
|
312
|
+
"action": ["ate", "hid", "unplugged", "sat on"],
|
|
313
|
+
"item": ["car keys", "alarm clock", "shoes", "phone"]
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
]
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The generator:
|
|
320
|
+
1. Filters templates by category and tone
|
|
321
|
+
2. Picks a random template
|
|
322
|
+
3. Substitutes variables with random choices
|
|
323
|
+
4. Returns the complete excuse
|
|
324
|
+
|
|
325
|
+
## Error Handling
|
|
326
|
+
|
|
327
|
+
- All API errors return consistent JSON format:
|
|
328
|
+
```json
|
|
329
|
+
{
|
|
330
|
+
"error": {
|
|
331
|
+
"code": "not_found",
|
|
332
|
+
"message": "Excuse not found"
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
- Frontend shows user-friendly error toasts
|
|
337
|
+
- Database errors are logged server-side
|
|
338
|
+
|
|
339
|
+
## Future Enhancements (Out of Scope)
|
|
340
|
+
|
|
341
|
+
- User accounts and cloud sync
|
|
342
|
+
- Sharing excuses on social media
|
|
343
|
+
- Community-submitted templates
|
|
344
|
+
- AI-generated excuses
|
|
345
|
+
- Multi-language support
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Excuse Generator
|
|
2
|
+
|
|
3
|
+
A fun web app that generates creative excuses for any situation.
|
|
4
|
+
|
|
5
|
+
This is a sample project automatically created by RalphX to help you get started.
|
|
6
|
+
Location: `~/.ralphx/samples/excuse-generator/`
|
|
7
|
+
|
|
8
|
+
## What's Included
|
|
9
|
+
|
|
10
|
+
- **DESIGN.md** - Complete design document with features, architecture, and API specs
|
|
11
|
+
- **stories.jsonl** - 10 user stories ready for implementation
|
|
12
|
+
- **guardrails.md** - Project-specific coding guidelines
|
|
13
|
+
|
|
14
|
+
## How to Use
|
|
15
|
+
|
|
16
|
+
1. Open RalphX dashboard at http://localhost:8765
|
|
17
|
+
2. Click on "Excuse-Generator" project
|
|
18
|
+
3. Open the pre-created workflow "Build Excuse Generator"
|
|
19
|
+
4. Start the Implementation step and watch Claude build your excuse generator!
|
|
20
|
+
|
|
21
|
+
## The App
|
|
22
|
+
|
|
23
|
+
When built, the Excuse Generator will feature:
|
|
24
|
+
|
|
25
|
+
- **Category selection** - Late to work, missed deadline, forgot birthday, etc.
|
|
26
|
+
- **Tone modes** - Professional, casual, dramatic, absurd
|
|
27
|
+
- **Favorites** - Save your best excuses
|
|
28
|
+
- **Believability rating** - Rate how convincing each excuse is
|
|
29
|
+
- **History** - Browse all your generated excuses
|
|
30
|
+
- **Excuse of the day** - Featured excuse on the homepage
|
|
31
|
+
- **Copy to clipboard** - One-click sharing
|
|
32
|
+
|
|
33
|
+
## Tech Stack
|
|
34
|
+
|
|
35
|
+
- **Backend**: FastAPI (Python)
|
|
36
|
+
- **Database**: SQLite
|
|
37
|
+
- **Frontend**: Vanilla HTML/CSS/JS (no framework)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Excuse Generator - Project Guardrails
|
|
2
|
+
|
|
3
|
+
## Content Guidelines
|
|
4
|
+
|
|
5
|
+
- Keep all excuses **PG-rated** and workplace-appropriate
|
|
6
|
+
- No offensive, discriminatory, or harmful content
|
|
7
|
+
- Excuses should be clearly fictional/humorous - avoid anything that could be used maliciously
|
|
8
|
+
- No references to violence, illegal activities, or sensitive topics
|
|
9
|
+
|
|
10
|
+
## Technical Guidelines
|
|
11
|
+
|
|
12
|
+
### Frontend
|
|
13
|
+
|
|
14
|
+
- Use **vanilla JavaScript only** - no React, Vue, or other frameworks
|
|
15
|
+
- No npm dependencies for the frontend
|
|
16
|
+
- Keep JavaScript in a single `app.js` file
|
|
17
|
+
- Use modern ES6+ syntax (const/let, arrow functions, template literals)
|
|
18
|
+
- CSS should be custom - no Tailwind, Bootstrap, or CSS frameworks
|
|
19
|
+
|
|
20
|
+
### Backend
|
|
21
|
+
|
|
22
|
+
- Use **FastAPI** for the Python backend
|
|
23
|
+
- SQLite for database - no PostgreSQL, MySQL, or other databases
|
|
24
|
+
- Keep dependencies minimal - only what's in requirements.txt
|
|
25
|
+
- Use Pydantic models for request/response validation
|
|
26
|
+
|
|
27
|
+
### Code Style
|
|
28
|
+
|
|
29
|
+
- Follow PEP 8 for Python code
|
|
30
|
+
- Use meaningful variable and function names
|
|
31
|
+
- Add docstrings to all public functions
|
|
32
|
+
- Keep functions small and focused (< 30 lines ideally)
|
|
33
|
+
|
|
34
|
+
### Database
|
|
35
|
+
|
|
36
|
+
- All data stored in SQLite at `data/excuses.db`
|
|
37
|
+
- Use parameterized queries to prevent SQL injection
|
|
38
|
+
- Include proper indexes for frequently queried columns
|
|
39
|
+
|
|
40
|
+
### Performance
|
|
41
|
+
|
|
42
|
+
- Page load should be instant (< 1 second)
|
|
43
|
+
- No unnecessary API calls
|
|
44
|
+
- Cache static assets appropriately
|
|
45
|
+
|
|
46
|
+
### Accessibility
|
|
47
|
+
|
|
48
|
+
- Use semantic HTML elements
|
|
49
|
+
- Include ARIA labels where needed
|
|
50
|
+
- Ensure color contrast meets WCAG AA standards
|
|
51
|
+
- All interactive elements must be keyboard accessible
|
|
52
|
+
|
|
53
|
+
### Mobile Support
|
|
54
|
+
|
|
55
|
+
- Mobile-first responsive design
|
|
56
|
+
- Touch targets at least 44x44 pixels
|
|
57
|
+
- No horizontal scrolling on mobile devices
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{"id": "EXC-001", "title": "Set up project structure", "description": "Initialize the FastAPI application with SQLite database and static file serving for the Excuse Generator web app.", "category": "infrastructure", "priority": "high", "acceptance_criteria": ["FastAPI app runs on localhost:8000", "SQLite database file created at data/excuses.db", "Static files served from /static path", "Health check endpoint at /api/health returns 200 OK", "requirements.txt includes fastapi, uvicorn, and other dependencies"], "dependencies": []}
|
|
2
|
+
{"id": "EXC-002", "title": "Create database schema and models", "description": "Design and implement the SQLite database schema with tables for excuse templates, generated excuses, and daily excuse tracking. Create corresponding Pydantic models.", "category": "backend", "priority": "high", "acceptance_criteria": ["excuse_templates table stores category, tone, template text, and variables", "generated_excuses table tracks history with ratings and favorites", "daily_excuse table stores the excuse of the day", "Pydantic models defined for all database entities", "Database initialization creates tables on startup"], "dependencies": ["EXC-001"]}
|
|
3
|
+
{"id": "EXC-003", "title": "Build excuse template system", "description": "Implement the excuse template system with variable substitution. Create a collection of 20+ excuse templates across all categories and tones.", "category": "backend", "priority": "high", "acceptance_criteria": ["Template system supports variable placeholders like {pet}, {action}, {item}", "Variables are randomly substituted from predefined lists", "At least 20 templates covering all 6 categories", "At least 5 templates per tone (professional, casual, dramatic, absurd)", "Templates seeded into database on first run"], "dependencies": ["EXC-002"]}
|
|
4
|
+
{"id": "EXC-004", "title": "Implement generate excuse endpoint", "description": "Create the /api/generate endpoint that returns a randomly generated excuse based on optional category and tone filters.", "category": "backend", "priority": "high", "acceptance_criteria": ["GET /api/generate returns a random excuse", "Optional category query param filters templates", "Optional tone query param filters templates", "Response includes excuse text, category, tone, and ID", "Generated excuse is saved to history"], "dependencies": ["EXC-003"]}
|
|
5
|
+
{"id": "EXC-005", "title": "Create homepage UI", "description": "Build the main homepage with category/tone selectors, generate button, and excuse display card using vanilla HTML/CSS/JS.", "category": "frontend", "priority": "high", "acceptance_criteria": ["Dropdown selectors for category and tone", "Generate button triggers API call", "Excuse displays in a styled card", "Loading state shown while generating", "Responsive design works on mobile and desktop", "Matches color scheme from design doc"], "dependencies": ["EXC-004"]}
|
|
6
|
+
{"id": "EXC-006", "title": "Add favorites functionality", "description": "Implement the ability to save excuses to favorites, view saved favorites, and remove from favorites.", "category": "feature", "priority": "medium", "acceptance_criteria": ["Heart icon toggles favorite status", "POST /api/excuses/{id}/favorite toggles is_favorite", "GET /api/favorites returns all favorited excuses", "Favorites tab shows saved excuses", "Visual indication when excuse is favorited", "Can unfavorite from the favorites view"], "dependencies": ["EXC-005"]}
|
|
7
|
+
{"id": "EXC-007", "title": "Implement believability rating", "description": "Add a 1-5 star rating system for excuses to track how believable users find them.", "category": "feature", "priority": "medium", "acceptance_criteria": ["Star rating component displays below excuse", "Clicking a star submits rating via API", "POST /api/excuses/{id}/rate saves the rating", "Rating persists and shows in history view", "Visual feedback on rating selection"], "dependencies": ["EXC-005"]}
|
|
8
|
+
{"id": "EXC-008", "title": "Build history view", "description": "Create a history tab showing all previously generated excuses with search and filtering capabilities.", "category": "feature", "priority": "medium", "acceptance_criteria": ["GET /api/history returns paginated excuse history", "History tab displays excuses in chronological order", "Search box filters by excuse text", "Dropdown filters for category and tone", "Pagination or infinite scroll for large history", "Shows rating and favorite status for each item"], "dependencies": ["EXC-006", "EXC-007"]}
|
|
9
|
+
{"id": "EXC-009", "title": "Build excuse of the day feature", "description": "Implement the daily featured excuse that rotates each day and displays prominently on the homepage.", "category": "feature", "priority": "medium", "acceptance_criteria": ["GET /api/excuse-of-the-day returns today's featured excuse", "Daily excuse is deterministic based on date (seeded random)", "Excuse of the day displays at bottom of homepage", "Different excuse shown each calendar day", "Uses curated selection of best templates"], "dependencies": ["EXC-003"]}
|
|
10
|
+
{"id": "EXC-010", "title": "Add copy to clipboard", "description": "Implement one-click copy functionality with visual feedback for easy sharing of excuses.", "category": "feature", "priority": "low", "acceptance_criteria": ["Copy button appears on each excuse card", "Clicking copies excuse text to clipboard", "Button shows checkmark animation on success", "Works on both mobile and desktop browsers", "Fallback for browsers without clipboard API"], "dependencies": ["EXC-005"]}
|
ralphx/mcp/__init__.py
CHANGED
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
This module provides a modular MCP server implementation that exposes RalphX
|
|
4
4
|
functionality as tools that Claude Code can use.
|
|
5
5
|
|
|
6
|
-
Usage:
|
|
7
|
-
claude mcp add ralphx -- ralphx mcp
|
|
6
|
+
Usage (Linux/Mac):
|
|
7
|
+
claude mcp add ralphx -e PYTHONDONTWRITEBYTECODE=1 -- "$(which ralphx)" mcp
|
|
8
|
+
# Mac zsh: if "which" fails, run: conda init zsh && source ~/.zshrc
|
|
9
|
+
|
|
10
|
+
Usage (Windows - find path first with: where.exe ralphx):
|
|
11
|
+
claude mcp add ralphx -e PYTHONDONTWRITEBYTECODE=1 -- C:\\path\\to\\ralphx.exe mcp
|
|
8
12
|
"""
|
|
9
13
|
|
|
10
14
|
from ralphx.mcp.server import MCPServer
|
ralphx/mcp/registry.py
CHANGED
|
@@ -39,11 +39,11 @@ class ToolRegistry:
|
|
|
39
39
|
"""Check if a tool is registered."""
|
|
40
40
|
return name in self._tools
|
|
41
41
|
|
|
42
|
-
def call(self,
|
|
42
|
+
def call(self, tool_name: str, **kwargs) -> Any:
|
|
43
43
|
"""Call a tool by name with arguments."""
|
|
44
|
-
tool = self._tools.get(
|
|
44
|
+
tool = self._tools.get(tool_name)
|
|
45
45
|
if not tool:
|
|
46
|
-
raise KeyError(f"Unknown tool: {
|
|
46
|
+
raise KeyError(f"Unknown tool: {tool_name}")
|
|
47
47
|
return tool.handler(**kwargs)
|
|
48
48
|
|
|
49
49
|
def get_definitions(self) -> list[dict]:
|
ralphx/mcp/server.py
CHANGED
|
@@ -16,6 +16,26 @@ from ralphx.mcp.tools import get_all_tools
|
|
|
16
16
|
# Version from pyproject.toml
|
|
17
17
|
VERSION = "0.1.5"
|
|
18
18
|
|
|
19
|
+
# MCP protocol version we support
|
|
20
|
+
PROTOCOL_VERSION = "2025-03-26"
|
|
21
|
+
|
|
22
|
+
# Server instructions for Claude - explains what RalphX is and how to use it
|
|
23
|
+
SERVER_INSTRUCTIONS = """RalphX is an Autonomous AI Loop Orchestration system. It helps you run autonomous AI workflows.
|
|
24
|
+
|
|
25
|
+
Key concepts:
|
|
26
|
+
- Project: A directory registered with RalphX (your codebase)
|
|
27
|
+
- Loop: An autonomous workflow defined in YAML that runs repeatedly
|
|
28
|
+
- Work Item: Data generated/consumed by loops (user stories, tasks, etc.)
|
|
29
|
+
- Workflow: A multi-step pipeline (research → implement → test)
|
|
30
|
+
|
|
31
|
+
Start with ralphx_help to see common workflows and all 67 available tools.
|
|
32
|
+
|
|
33
|
+
Quick start:
|
|
34
|
+
1. ralphx_list_projects - See registered projects
|
|
35
|
+
2. ralphx_list_loops - See available loops in a project
|
|
36
|
+
3. ralphx_start_loop - Run a loop
|
|
37
|
+
4. ralphx_list_runs - Monitor progress"""
|
|
38
|
+
|
|
19
39
|
|
|
20
40
|
class MCPServer:
|
|
21
41
|
"""MCP protocol handler for RalphX.
|
|
@@ -28,13 +48,11 @@ class MCPServer:
|
|
|
28
48
|
"""Initialize the MCP server with all registered tools."""
|
|
29
49
|
self.registry = ToolRegistry()
|
|
30
50
|
self.registry.register_all(get_all_tools())
|
|
51
|
+
self._initialized = False # Track whether initialize handshake completed
|
|
31
52
|
|
|
32
53
|
def run(self) -> None:
|
|
33
54
|
"""Run the MCP server, reading from stdin and writing to stdout."""
|
|
34
|
-
#
|
|
35
|
-
self._send_init()
|
|
36
|
-
|
|
37
|
-
# Process messages
|
|
55
|
+
# Process messages - wait for client to initiate
|
|
38
56
|
try:
|
|
39
57
|
for line in sys.stdin:
|
|
40
58
|
line = line.strip()
|
|
@@ -55,22 +73,6 @@ class MCPServer:
|
|
|
55
73
|
# Exit gracefully
|
|
56
74
|
pass
|
|
57
75
|
|
|
58
|
-
def _send_init(self) -> None:
|
|
59
|
-
"""Send MCP initialization message."""
|
|
60
|
-
self._send({
|
|
61
|
-
"jsonrpc": "2.0",
|
|
62
|
-
"method": "initialized",
|
|
63
|
-
"params": {
|
|
64
|
-
"serverInfo": {
|
|
65
|
-
"name": "ralphx",
|
|
66
|
-
"version": VERSION,
|
|
67
|
-
},
|
|
68
|
-
"capabilities": {
|
|
69
|
-
"tools": True,
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
})
|
|
73
|
-
|
|
74
76
|
def _send(self, message: dict) -> None:
|
|
75
77
|
"""Send a JSON-RPC message to stdout."""
|
|
76
78
|
print(json.dumps(message), flush=True)
|
|
@@ -92,11 +94,23 @@ class MCPServer:
|
|
|
92
94
|
params = message.get("params", {})
|
|
93
95
|
msg_id = message.get("id")
|
|
94
96
|
|
|
97
|
+
# Per MCP spec: ping is allowed before initialization
|
|
98
|
+
if method == "ping":
|
|
99
|
+
return {
|
|
100
|
+
"jsonrpc": "2.0",
|
|
101
|
+
"id": msg_id,
|
|
102
|
+
"result": {},
|
|
103
|
+
}
|
|
104
|
+
|
|
95
105
|
if method == "initialize":
|
|
106
|
+
# Respond to client's initialize request per MCP spec
|
|
107
|
+
# Client will then send notifications/initialized to signal ready
|
|
108
|
+
self._initialized = True
|
|
96
109
|
return {
|
|
97
110
|
"jsonrpc": "2.0",
|
|
98
111
|
"id": msg_id,
|
|
99
112
|
"result": {
|
|
113
|
+
"protocolVersion": PROTOCOL_VERSION,
|
|
100
114
|
"serverInfo": {
|
|
101
115
|
"name": "ralphx",
|
|
102
116
|
"version": VERSION,
|
|
@@ -106,6 +120,19 @@ class MCPServer:
|
|
|
106
120
|
"listChanged": False,
|
|
107
121
|
},
|
|
108
122
|
},
|
|
123
|
+
"instructions": SERVER_INSTRUCTIONS,
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Per MCP spec: most requests require initialization first
|
|
128
|
+
# (ping and initialize are handled above)
|
|
129
|
+
if not self._initialized and msg_id is not None:
|
|
130
|
+
return {
|
|
131
|
+
"jsonrpc": "2.0",
|
|
132
|
+
"id": msg_id,
|
|
133
|
+
"error": {
|
|
134
|
+
"code": -32600,
|
|
135
|
+
"message": "Server not initialized. Send 'initialize' request first.",
|
|
109
136
|
},
|
|
110
137
|
}
|
|
111
138
|
|
|
@@ -123,11 +150,13 @@ class MCPServer:
|
|
|
123
150
|
arguments = params.get("arguments", {})
|
|
124
151
|
|
|
125
152
|
if not self.registry.has(tool_name):
|
|
153
|
+
# Per MCP spec: unknown tool is Invalid params (-32602), not Method not found
|
|
154
|
+
# because tools/call method exists, the tool name is a parameter
|
|
126
155
|
return {
|
|
127
156
|
"jsonrpc": "2.0",
|
|
128
157
|
"id": msg_id,
|
|
129
158
|
"error": {
|
|
130
|
-
"code": -
|
|
159
|
+
"code": -32602,
|
|
131
160
|
"message": f"Unknown tool: {tool_name}",
|
|
132
161
|
},
|
|
133
162
|
}
|
|
@@ -135,11 +164,27 @@ class MCPServer:
|
|
|
135
164
|
try:
|
|
136
165
|
result = self.registry.call(tool_name, **arguments)
|
|
137
166
|
|
|
138
|
-
# Format result
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
167
|
+
# Format result - handle serialization errors gracefully
|
|
168
|
+
try:
|
|
169
|
+
if isinstance(result, dict):
|
|
170
|
+
result_text = json.dumps(result, indent=2)
|
|
171
|
+
else:
|
|
172
|
+
result_text = str(result)
|
|
173
|
+
except (TypeError, ValueError) as serialize_err:
|
|
174
|
+
# Non-JSON-serializable result - return as tool error, not protocol error
|
|
175
|
+
return {
|
|
176
|
+
"jsonrpc": "2.0",
|
|
177
|
+
"id": msg_id,
|
|
178
|
+
"result": {
|
|
179
|
+
"content": [
|
|
180
|
+
{
|
|
181
|
+
"type": "text",
|
|
182
|
+
"text": f"Tool returned non-serializable result: {serialize_err}",
|
|
183
|
+
}
|
|
184
|
+
],
|
|
185
|
+
"isError": True,
|
|
186
|
+
},
|
|
187
|
+
}
|
|
143
188
|
|
|
144
189
|
return {
|
|
145
190
|
"jsonrpc": "2.0",
|
|
@@ -169,18 +214,43 @@ class MCPServer:
|
|
|
169
214
|
},
|
|
170
215
|
}
|
|
171
216
|
except Exception as e:
|
|
217
|
+
# Unexpected tool execution error - return as tool error for LLM recovery
|
|
172
218
|
return {
|
|
173
219
|
"jsonrpc": "2.0",
|
|
174
220
|
"id": msg_id,
|
|
175
|
-
"
|
|
176
|
-
"
|
|
177
|
-
|
|
221
|
+
"result": {
|
|
222
|
+
"content": [
|
|
223
|
+
{
|
|
224
|
+
"type": "text",
|
|
225
|
+
"text": f"Tool execution error: {e}",
|
|
226
|
+
}
|
|
227
|
+
],
|
|
228
|
+
"isError": True,
|
|
178
229
|
},
|
|
179
230
|
}
|
|
180
231
|
|
|
232
|
+
if method == "notifications/initialized":
|
|
233
|
+
# Client signals it's ready for normal operations
|
|
234
|
+
# No response needed for notifications
|
|
235
|
+
return None
|
|
236
|
+
|
|
181
237
|
if method == "notifications/cancelled":
|
|
238
|
+
# Client cancelled a request
|
|
182
239
|
return None
|
|
183
240
|
|
|
241
|
+
# Unknown method handling per JSON-RPC 2.0:
|
|
242
|
+
# - If it has an id, it's a request and MUST return error
|
|
243
|
+
# - If no id, it's a notification and can be silently ignored
|
|
244
|
+
if msg_id is not None:
|
|
245
|
+
return {
|
|
246
|
+
"jsonrpc": "2.0",
|
|
247
|
+
"id": msg_id,
|
|
248
|
+
"error": {
|
|
249
|
+
"code": -32601,
|
|
250
|
+
"message": f"Method not found: {method}",
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
# Notification we don't handle - ignore silently per JSON-RPC spec
|
|
184
254
|
return None
|
|
185
255
|
|
|
186
256
|
|