reflexive 0.1.0

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.
@@ -0,0 +1,571 @@
1
+ /**
2
+ * Demo: AI-Powered Application Features
3
+ *
4
+ * This demo shows how to use Reflexive to add AI-powered features to your app:
5
+ * 1. Dynamic AI endpoints (e.g., /poem/:topic generates poems on any topic)
6
+ * 2. AI-powered data operations (filtering, suggestions)
7
+ * 3. Breakpoints for debugging with AI assistance
8
+ *
9
+ * Run with: node demo-ai-features.js
10
+ * Then visit: http://localhost:8080
11
+ * Dashboard: http://localhost:3099/reflexive
12
+ */
13
+
14
+ import http from 'http';
15
+ import { makeReflexive } from './src/reflexive.js';
16
+ import { tool } from '@anthropic-ai/claude-agent-sdk';
17
+ import { z } from 'zod';
18
+
19
+ // Sample data for the dropdown demo
20
+ const people = [
21
+ { id: 1, name: 'Alice Chen', role: 'Engineer', department: 'Platform', skills: ['Go', 'Kubernetes', 'AWS'] },
22
+ { id: 2, name: 'Bob Smith', role: 'Designer', department: 'Product', skills: ['Figma', 'CSS', 'User Research'] },
23
+ { id: 3, name: 'Carol Davis', role: 'Engineer', department: 'Frontend', skills: ['React', 'TypeScript', 'GraphQL'] },
24
+ { id: 4, name: 'Dan Wilson', role: 'Manager', department: 'Platform', skills: ['Leadership', 'Agile', 'Strategy'] },
25
+ { id: 5, name: 'Eva Martinez', role: 'Data Scientist', department: 'Analytics', skills: ['Python', 'ML', 'SQL'] },
26
+ { id: 6, name: 'Frank Johnson', role: 'Engineer', department: 'Backend', skills: ['Java', 'Spring', 'PostgreSQL'] },
27
+ { id: 7, name: 'Grace Lee', role: 'DevOps', department: 'Platform', skills: ['Terraform', 'Docker', 'CI/CD'] },
28
+ { id: 8, name: 'Henry Brown', role: 'Engineer', department: 'Mobile', skills: ['Swift', 'Kotlin', 'React Native'] },
29
+ ];
30
+
31
+ // Initialize Reflexive with custom tools
32
+ const reflexive = makeReflexive({
33
+ port: 3099,
34
+ title: 'AI Features Demo',
35
+ systemPrompt: `You are an AI assistant embedded in a demo application that showcases AI-powered features.
36
+
37
+ The app has:
38
+ 1. A /poem/:topic endpoint - when called, generate a short, creative poem about the topic
39
+ 2. A people directory with filtering - help users find people by natural language queries
40
+ 3. Breakpoint debugging - the app has breakpoints you can inspect and control
41
+
42
+ When generating poems, be creative and keep them to 4-8 lines.
43
+ When filtering people, interpret natural language like "engineers who know React" or "people in Platform team".
44
+
45
+ Available people data: ${JSON.stringify(people, null, 2)}`,
46
+
47
+ // Custom tools for AI-powered features
48
+ tools: [
49
+ tool(
50
+ 'generate_poem',
51
+ 'Generate a creative poem about a given topic. Called by the /poem/:topic endpoint.',
52
+ {
53
+ topic: z.string().describe('The topic to write a poem about')
54
+ },
55
+ async ({ topic }) => {
56
+ // The agent will use this tool and return a poem
57
+ return {
58
+ content: [{
59
+ type: 'text',
60
+ text: `Please generate a short, creative poem (4-8 lines) about: ${topic}`
61
+ }]
62
+ };
63
+ }
64
+ ),
65
+
66
+ tool(
67
+ 'filter_people',
68
+ 'Filter the people list based on natural language criteria. Examples: "engineers", "people who know Python", "Platform team members"',
69
+ {
70
+ query: z.string().describe('Natural language query to filter people')
71
+ },
72
+ async ({ query }) => {
73
+ // The agent interprets the query and returns matching people
74
+ return {
75
+ content: [{
76
+ type: 'text',
77
+ text: `Filter the people list based on this query: "${query}"\n\nPeople data:\n${JSON.stringify(people, null, 2)}\n\nReturn JSON array of matching people IDs.`
78
+ }]
79
+ };
80
+ }
81
+ ),
82
+
83
+ tool(
84
+ 'get_people_list',
85
+ 'Get the full list of people in the directory',
86
+ {},
87
+ async () => ({
88
+ content: [{
89
+ type: 'text',
90
+ text: JSON.stringify(people, null, 2)
91
+ }]
92
+ })
93
+ )
94
+ ]
95
+ });
96
+
97
+ // Track state
98
+ reflexive.setState('totalPeople', people.length);
99
+ reflexive.setState('poemsGenerated', 0);
100
+ reflexive.setState('filtersApplied', 0);
101
+
102
+ let poemsGenerated = 0;
103
+ let filtersApplied = 0;
104
+
105
+ // Generate the web UI
106
+ function getIndexHTML() {
107
+ return `<!DOCTYPE html>
108
+ <html lang="en">
109
+ <head>
110
+ <meta charset="UTF-8">
111
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
112
+ <title>AI Features Demo</title>
113
+ <link rel="icon" type="image/jpeg" href="/favicon.ico">
114
+ <style>
115
+ * { box-sizing: border-box; margin: 0; padding: 0; }
116
+ body {
117
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
118
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
119
+ color: #e0e0e0;
120
+ min-height: 100vh;
121
+ padding: 40px 20px;
122
+ }
123
+ .container { max-width: 800px; margin: 0 auto; }
124
+ .header { display: flex; align-items: center; gap: 20px; margin-bottom: 40px; }
125
+ .logo { width: 80px; height: 80px; border-radius: 12px; object-fit: cover; box-shadow: 0 4px 20px rgba(0,0,0,0.3); }
126
+ .header-content { flex: 1; }
127
+ h1 { font-size: 2rem; margin-bottom: 8px; color: #fff; }
128
+ .subtitle { color: #888; }
129
+ .card {
130
+ background: rgba(255,255,255,0.05);
131
+ border: 1px solid rgba(255,255,255,0.1);
132
+ border-radius: 12px;
133
+ padding: 24px;
134
+ margin-bottom: 24px;
135
+ }
136
+ h2 { font-size: 1.2rem; margin-bottom: 16px; color: #fff; display: flex; align-items: center; gap: 8px; }
137
+ .emoji { font-size: 1.4rem; }
138
+ input, select {
139
+ width: 100%;
140
+ padding: 12px 16px;
141
+ background: rgba(255,255,255,0.1);
142
+ border: 1px solid rgba(255,255,255,0.2);
143
+ border-radius: 8px;
144
+ color: #fff;
145
+ font-size: 1rem;
146
+ margin-bottom: 12px;
147
+ }
148
+ input::placeholder { color: #666; }
149
+ button {
150
+ padding: 12px 24px;
151
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
152
+ border: none;
153
+ border-radius: 8px;
154
+ color: #fff;
155
+ font-size: 1rem;
156
+ cursor: pointer;
157
+ transition: transform 0.2s, box-shadow 0.2s;
158
+ }
159
+ button:hover { transform: translateY(-2px); box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4); }
160
+ button:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
161
+ .result {
162
+ margin-top: 16px;
163
+ padding: 16px;
164
+ background: rgba(0,0,0,0.3);
165
+ border-radius: 8px;
166
+ white-space: pre-wrap;
167
+ font-family: 'SF Mono', Monaco, monospace;
168
+ font-size: 0.9rem;
169
+ min-height: 60px;
170
+ }
171
+ .result.poem { font-style: italic; line-height: 1.8; }
172
+ .people-list { display: grid; gap: 12px; margin-top: 16px; }
173
+ .person {
174
+ display: flex;
175
+ justify-content: space-between;
176
+ align-items: center;
177
+ padding: 12px 16px;
178
+ background: rgba(255,255,255,0.05);
179
+ border-radius: 8px;
180
+ }
181
+ .person-name { font-weight: 600; color: #fff; }
182
+ .person-role { color: #888; font-size: 0.85rem; }
183
+ .person-skills { display: flex; gap: 6px; flex-wrap: wrap; }
184
+ .skill {
185
+ padding: 2px 8px;
186
+ background: rgba(102, 126, 234, 0.3);
187
+ border-radius: 4px;
188
+ font-size: 0.75rem;
189
+ }
190
+ .loading { opacity: 0.6; animation: pulse 1.5s infinite; }
191
+ @keyframes pulse { 0%, 100% { opacity: 0.6; } 50% { opacity: 1; } }
192
+ .dashboard-link {
193
+ display: inline-block;
194
+ margin-top: 20px;
195
+ color: #667eea;
196
+ text-decoration: none;
197
+ }
198
+ .dashboard-link:hover { text-decoration: underline; }
199
+ .note { font-size: 0.85rem; color: #666; margin-top: 8px; }
200
+ </style>
201
+ </head>
202
+ <body>
203
+ <div class="container">
204
+ <div class="header">
205
+ <img src="/logo" alt="Reflexive Logo" class="logo" />
206
+ <div class="header-content">
207
+ <h1>AI Features Demo</h1>
208
+ <p class="subtitle">Demonstrating AI-powered endpoints and data operations with Reflexive</p>
209
+ </div>
210
+ </div>
211
+
212
+ <div class="card">
213
+ <h2><span class="emoji">📝</span> AI Poem Generator</h2>
214
+ <p style="margin-bottom: 16px; color: #888;">Enter any topic and get a unique AI-generated poem</p>
215
+ <input type="text" id="poem-topic" placeholder="Enter a topic (e.g., 'coffee', 'autumn', 'coding')..." />
216
+ <button onclick="generatePoem()">Generate Poem</button>
217
+ <div class="result poem" id="poem-result">Your poem will appear here...</div>
218
+ <p class="note">Try: "the joy of debugging", "a rainy Monday", "machine learning"</p>
219
+ </div>
220
+
221
+ <div class="card">
222
+ <h2><span class="emoji">👥</span> AI-Powered People Filter</h2>
223
+ <p style="margin-bottom: 16px; color: #888;">Use natural language to find people in the directory</p>
224
+ <input type="text" id="filter-query" placeholder="e.g., 'engineers who know React' or 'Platform team'" />
225
+ <button onclick="filterPeople()">Find People</button>
226
+ <div class="people-list" id="people-list">
227
+ ${people.map(p => `
228
+ <div class="person" data-id="${p.id}">
229
+ <div>
230
+ <div class="person-name">${p.name}</div>
231
+ <div class="person-role">${p.role} · ${p.department}</div>
232
+ </div>
233
+ <div class="person-skills">
234
+ ${p.skills.map(s => `<span class="skill">${s}</span>`).join('')}
235
+ </div>
236
+ </div>
237
+ `).join('')}
238
+ </div>
239
+ <p class="note">Try: "designers", "people who know AWS", "Backend team engineers"</p>
240
+ </div>
241
+
242
+ <div class="card">
243
+ <h2><span class="emoji">🔧</span> Breakpoint Demo</h2>
244
+ <p style="margin-bottom: 16px; color: #888;">Trigger a breakpoint and control execution from the Reflexive dashboard</p>
245
+ <button onclick="triggerBreakpoint()">Trigger Breakpoint</button>
246
+ <div class="result" id="breakpoint-result">Click to trigger a breakpoint. Watch the dashboard!</div>
247
+ <p class="note">Open the <a href="http://localhost:3099/reflexive" target="_blank" style="color: #667eea;">Reflexive dashboard</a> to see and resume the breakpoint.</p>
248
+ </div>
249
+
250
+ <div class="card" style="background: linear-gradient(135deg, rgba(52, 211, 153, 0.1) 0%, rgba(16, 185, 129, 0.1) 100%); border-color: rgba(52, 211, 153, 0.3);">
251
+ <h2><span class="emoji">⚡</span> Quick Add Person (AI Injected!)</h2>
252
+ <p style="margin-bottom: 16px; color: #888;">Click to instantly add a randomly generated person to the directory</p>
253
+ <button onclick="quickAddPerson()" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%);">
254
+ ⚡ Add Random Person
255
+ </button>
256
+ <div class="result" id="quick-add-result">Click the button to add someone new!</div>
257
+ <p class="note" style="color: #10b981;">✨ This feature calls reflexive.chat() just like the poem generator!</p>
258
+ </div>
259
+
260
+ <a href="http://localhost:3099/reflexive" target="_blank" class="dashboard-link">Open Reflexive Dashboard →</a>
261
+ </div>
262
+
263
+ <script>
264
+ async function generatePoem() {
265
+ const topic = document.getElementById('poem-topic').value.trim();
266
+ if (!topic) return alert('Please enter a topic');
267
+
268
+ const result = document.getElementById('poem-result');
269
+ result.textContent = 'Generating poem...';
270
+ result.classList.add('loading');
271
+
272
+ try {
273
+ const res = await fetch('/poem/' + encodeURIComponent(topic));
274
+ const data = await res.json();
275
+ result.textContent = data.poem || data.error || 'No poem generated';
276
+ } catch (e) {
277
+ result.textContent = 'Error: ' + e.message;
278
+ }
279
+ result.classList.remove('loading');
280
+ }
281
+
282
+ async function filterPeople() {
283
+ const query = document.getElementById('filter-query').value.trim();
284
+ if (!query) return alert('Please enter a filter query');
285
+
286
+ const list = document.getElementById('people-list');
287
+ list.classList.add('loading');
288
+
289
+ try {
290
+ const res = await fetch('/filter?q=' + encodeURIComponent(query));
291
+ const data = await res.json();
292
+
293
+ // Highlight matching people
294
+ document.querySelectorAll('.person').forEach(el => {
295
+ el.style.opacity = data.matchingIds?.includes(parseInt(el.dataset.id)) ? '1' : '0.3';
296
+ });
297
+ } catch (e) {
298
+ alert('Error: ' + e.message);
299
+ }
300
+ list.classList.remove('loading');
301
+ }
302
+
303
+ async function triggerBreakpoint() {
304
+ const result = document.getElementById('breakpoint-result');
305
+ result.textContent = 'Triggering breakpoint... Check the dashboard!';
306
+ result.classList.add('loading');
307
+
308
+ try {
309
+ const res = await fetch('/debug/breakpoint');
310
+ const data = await res.json();
311
+ result.textContent = data.message || 'Breakpoint completed';
312
+ } catch (e) {
313
+ result.textContent = 'Error: ' + e.message;
314
+ }
315
+ result.classList.remove('loading');
316
+ }
317
+
318
+ async function quickAddPerson() {
319
+ const result = document.getElementById('quick-add-result');
320
+ result.textContent = 'Asking AI to generate a person...';
321
+ result.classList.add('loading');
322
+
323
+ try {
324
+ const res = await fetch('/api/quick-add', { method: 'POST' });
325
+ const data = await res.json();
326
+
327
+ if (data.error) {
328
+ result.textContent = 'Error: ' + data.error;
329
+ } else {
330
+ result.textContent = '✨ Added: ' + data.name + ' (' + data.role + ' in ' + data.department + ')';
331
+
332
+ // Refresh the people list after a short delay
333
+ setTimeout(() => location.reload(), 1500);
334
+ }
335
+ } catch (e) {
336
+ result.textContent = 'Error: ' + e.message;
337
+ }
338
+ result.classList.remove('loading');
339
+ }
340
+
341
+ </script>
342
+ </body>
343
+ </html>`;
344
+ }
345
+
346
+ // HTTP Server
347
+ const PORT = 8080;
348
+
349
+ const server = http.createServer(async (req, res) => {
350
+ const url = new URL(req.url, `http://${req.headers.host}`);
351
+
352
+ console.log(`${req.method} ${url.pathname}`);
353
+
354
+ // CORS
355
+ res.setHeader('Access-Control-Allow-Origin', '*');
356
+
357
+ // Serve logo
358
+ if (url.pathname === '/logo' || url.pathname === '/logo1.jpg') {
359
+ try {
360
+ const fs = await import('fs/promises');
361
+ const logoPath = new URL('./logo1.jpg', import.meta.url);
362
+ const logoData = await fs.readFile(logoPath);
363
+ res.setHeader('Content-Type', 'image/jpeg');
364
+ res.setHeader('Cache-Control', 'public, max-age=86400');
365
+ res.end(logoData);
366
+ } catch (e) {
367
+ res.writeHead(404);
368
+ res.end('Logo not found');
369
+ }
370
+ return;
371
+ }
372
+
373
+ // Serve secondary logo
374
+ if (url.pathname === '/logo2.jpg') {
375
+ try {
376
+ const fs = await import('fs/promises');
377
+ const logoPath = new URL('./logo2.jpg', import.meta.url);
378
+ const logoData = await fs.readFile(logoPath);
379
+ res.setHeader('Content-Type', 'image/jpeg');
380
+ res.setHeader('Cache-Control', 'public, max-age=86400');
381
+ res.end(logoData);
382
+ } catch (e) {
383
+ res.writeHead(404);
384
+ res.end('Logo not found');
385
+ }
386
+ return;
387
+ }
388
+
389
+ // Serve favicon (using logo2)
390
+ if (url.pathname === '/favicon.ico') {
391
+ try {
392
+ const fs = await import('fs/promises');
393
+ const logoPath = new URL('./logo2.jpg', import.meta.url);
394
+ const logoData = await fs.readFile(logoPath);
395
+ res.setHeader('Content-Type', 'image/jpeg');
396
+ res.setHeader('Cache-Control', 'public, max-age=86400');
397
+ res.end(logoData);
398
+ } catch (e) {
399
+ res.writeHead(404);
400
+ res.end('Favicon not found');
401
+ }
402
+ return;
403
+ }
404
+
405
+ // Serve index page
406
+ if (url.pathname === '/' || url.pathname === '/index.html') {
407
+ res.setHeader('Content-Type', 'text/html');
408
+ res.end(getIndexHTML());
409
+ return;
410
+ }
411
+
412
+ // AI Poem endpoint
413
+ if (url.pathname.startsWith('/poem/')) {
414
+ const topic = decodeURIComponent(url.pathname.slice(6));
415
+ console.log(`Generating poem about: ${topic}`);
416
+
417
+ // Use Reflexive's chat to generate the poem
418
+ try {
419
+ const poem = await reflexive.chat(`Generate a short, creative poem (4-8 lines) about: ${topic}. Return ONLY the poem text, no explanation.`);
420
+ poemsGenerated++;
421
+ reflexive.setState('poemsGenerated', poemsGenerated);
422
+
423
+ res.setHeader('Content-Type', 'application/json');
424
+ res.end(JSON.stringify({ topic, poem }));
425
+ } catch (e) {
426
+ res.setHeader('Content-Type', 'application/json');
427
+ res.end(JSON.stringify({ error: e.message }));
428
+ }
429
+ return;
430
+ }
431
+
432
+ // AI Filter endpoint
433
+ if (url.pathname === '/filter') {
434
+ const query = url.searchParams.get('q');
435
+ console.log(`Filtering people: ${query}`);
436
+
437
+ try {
438
+ const response = await reflexive.chat(
439
+ `Given this people data:\n${JSON.stringify(people, null, 2)}\n\n` +
440
+ `Filter for: "${query}"\n\n` +
441
+ `Return ONLY a JSON object like {"matchingIds": [1,2,3]} with the IDs of matching people. No explanation.`
442
+ );
443
+
444
+ // Parse the response to extract IDs
445
+ const match = response.match(/\{[\s\S]*"matchingIds"[\s\S]*\}/);
446
+ let matchingIds = [];
447
+ if (match) {
448
+ try {
449
+ const parsed = JSON.parse(match[0]);
450
+ matchingIds = parsed.matchingIds || [];
451
+ } catch (e) {
452
+ // Try to extract numbers from response
453
+ matchingIds = (response.match(/\d+/g) || []).map(Number);
454
+ }
455
+ }
456
+
457
+ filtersApplied++;
458
+ reflexive.setState('filtersApplied', filtersApplied);
459
+
460
+ res.setHeader('Content-Type', 'application/json');
461
+ res.end(JSON.stringify({ query, matchingIds }));
462
+ } catch (e) {
463
+ res.setHeader('Content-Type', 'application/json');
464
+ res.end(JSON.stringify({ error: e.message, matchingIds: [] }));
465
+ }
466
+ return;
467
+ }
468
+
469
+ // Quick Add Person endpoint (uses AI to generate person data)
470
+ if (url.pathname === '/api/quick-add' && req.method === 'POST') {
471
+ console.log('Quick adding person with AI...');
472
+
473
+ try {
474
+ // Use reflexive.chat() to generate a random person - just like the poem endpoint!
475
+ const prompt = `Generate a random person for a company directory. Return ONLY valid JSON in this exact format with no markdown, no explanation:
476
+ {"name": "FirstName LastName", "role": "Job Title", "department": "Department Name", "skills": ["Skill1", "Skill2", "Skill3"]}
477
+
478
+ Use realistic, diverse names. Choose from these roles: Engineer, Designer, Product Manager, Data Scientist, DevOps, QA Engineer.
479
+ Departments: Platform, Frontend, Backend, Mobile, Analytics, Infrastructure.
480
+ Skills: JavaScript, Python, React, Vue, Docker, Kubernetes, AWS, GCP, TypeScript, Go, Rust, SQL, NoSQL, GraphQL, REST, CI/CD`;
481
+
482
+ const aiResponse = await reflexive.chat(prompt);
483
+
484
+ // Parse the JSON response
485
+ const jsonMatch = aiResponse.match(/\{[\s\S]*\}/);
486
+ if (!jsonMatch) {
487
+ throw new Error('Failed to parse AI response');
488
+ }
489
+
490
+ const personData = JSON.parse(jsonMatch[0]);
491
+
492
+ // Add to people array
493
+ const newPerson = {
494
+ id: people.length + 1,
495
+ ...personData
496
+ };
497
+
498
+ people.push(newPerson);
499
+ reflexive.setState('totalPeople', people.length);
500
+ console.log(`✨ AI generated and added: ${newPerson.name} (${newPerson.role})`);
501
+
502
+ res.setHeader('Content-Type', 'application/json');
503
+ res.end(JSON.stringify(newPerson));
504
+ } catch (e) {
505
+ res.setHeader('Content-Type', 'application/json');
506
+ res.end(JSON.stringify({ error: e.message }));
507
+ }
508
+ return;
509
+ }
510
+
511
+ // Breakpoint demo endpoint
512
+ if (url.pathname === '/debug/breakpoint') {
513
+ console.log('Triggering breakpoint demo...');
514
+
515
+ // Use process.reflexive.breakpoint if available (when running with --inject)
516
+ if (process.reflexive && process.reflexive.breakpoint) {
517
+ const context = {
518
+ requestUrl: url.pathname,
519
+ timestamp: new Date().toISOString(),
520
+ peopleCount: people.length,
521
+ stats: { poemsGenerated, filtersApplied }
522
+ };
523
+
524
+ console.log('Hitting breakpoint - check dashboard to resume');
525
+ const result = await process.reflexive.breakpoint('debug-endpoint', context);
526
+
527
+ res.setHeader('Content-Type', 'application/json');
528
+ res.end(JSON.stringify({
529
+ message: 'Breakpoint completed! Execution was paused and resumed.',
530
+ returnValue: result
531
+ }));
532
+ } else {
533
+ res.setHeader('Content-Type', 'application/json');
534
+ res.end(JSON.stringify({
535
+ message: 'Breakpoint feature requires --inject flag. Run: reflexive --inject demo-ai-features.js'
536
+ }));
537
+ }
538
+ return;
539
+ }
540
+
541
+ // Get people
542
+ if (url.pathname === '/api/people') {
543
+ res.setHeader('Content-Type', 'application/json');
544
+ res.end(JSON.stringify(people));
545
+ return;
546
+ }
547
+
548
+ // 404
549
+ res.writeHead(404);
550
+ res.setHeader('Content-Type', 'application/json');
551
+ res.end(JSON.stringify({ error: 'Not found' }));
552
+ });
553
+
554
+ server.listen(PORT, () => {
555
+ console.log(`
556
+ ╔════════════════════════════════════════════════════════════════╗
557
+ ║ AI Features Demo ║
558
+ ╠════════════════════════════════════════════════════════════════╣
559
+ ║ Web App: http://localhost:${PORT} ║
560
+ ║ Dashboard: http://localhost:3099/reflexive ║
561
+ ╠════════════════════════════════════════════════════════════════╣
562
+ ║ Features: ║
563
+ ║ • /poem/:topic - AI-generated poems ║
564
+ ║ • /filter?q=... - Natural language people filter ║
565
+ ║ • /debug/breakpoint - Breakpoint demo (needs --inject) ║
566
+ ╠════════════════════════════════════════════════════════════════╣
567
+ ║ For breakpoints, run with: ║
568
+ ║ node src/reflexive.js --inject demo-ai-features.js ║
569
+ ╚════════════════════════════════════════════════════════════════╝
570
+ `);
571
+ });