create-feltdb 0.4.4 → 0.4.9

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.
package/dist/create.js CHANGED
@@ -53,15 +53,12 @@ export async function createProject(options) {
53
53
  '@feltdb/core': feltdbPackageRange,
54
54
  },
55
55
  devDependencies: {
56
- '@feltdb/cli': feltdbPackageRange,
57
- '@feltdb/studio': feltdbPackageRange,
58
56
  typescript: '^5.0.0',
59
57
  '@types/node': '^20.0.0',
60
58
  vite: '^8.2.1',
61
59
  },
62
60
  };
63
61
  if (framework === 'react') {
64
- packageJson.dependencies['@feltdb/react'] = feltdbPackageRange;
65
62
  packageJson.dependencies['react'] = '^18.0.0';
66
63
  packageJson.dependencies['react-dom'] = '^18.0.0';
67
64
  packageJson.devDependencies['@types/react'] = '^18.0.0';
@@ -75,7 +72,7 @@ export async function createProject(options) {
75
72
  const feltdbConfig = {
76
73
  namespace: applicationName,
77
74
  runtime,
78
- storage: runtime === 'browser' ? 'opfs' : 'durable',
75
+ storage: runtime === 'browser' ? 'indexeddb' : runtime === 'managed' ? 'managed' : 'durable',
79
76
  distributed,
80
77
  agents: {
81
78
  enabled: hasAgents,
@@ -159,16 +156,165 @@ export async function createProject(options) {
159
156
  // Create main application files
160
157
  const runtimeOptions = runtime === 'browser'
161
158
  ? "{ namespace: '" + applicationName + "', browser: true }"
162
- : runtime === 'self-hosted'
163
- ? "{ namespace: '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_URL || 'http://localhost:7700', token: import.meta.env.VITE_FELTDB_API_KEY || '' } }"
164
- : "{ namespace: '" + applicationName + "', memory: true }";
159
+ : runtime === 'managed'
160
+ ? "{ namespace: import.meta.env.VITE_FELTDB_MANAGED_NAMESPACE || '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_MANAGED_URL || import.meta.env.VITE_FELTDB_URL || '', token: import.meta.env.VITE_FELTDB_MANAGED_API_KEY || import.meta.env.VITE_FELTDB_API_KEY || '' } }"
161
+ : runtime === 'self-hosted'
162
+ ? "{ namespace: '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_URL || 'http://localhost:7700', token: import.meta.env.VITE_FELTDB_API_KEY || '' } }"
163
+ : "{ namespace: '" + applicationName + "', memory: true }";
165
164
  const feltdbTs = `import { createFeltDB } from '@feltdb/core';
166
165
 
167
166
  export const db = createFeltDB(${runtimeOptions});
168
167
 
169
168
  // Collections
170
- export const documents = db.collection('documents');
171
- export const reports = db.collection('reports');
169
+ export const projects = db.collection('projects');
170
+ export const tasks = db.collection('tasks');
171
+ export const activity = db.collection('activity');
172
+
173
+ // Types
174
+ export interface Project {
175
+ id: string;
176
+ name: string;
177
+ description: string;
178
+ status: 'active' | 'archived' | 'completed';
179
+ createdAt: string;
180
+ updatedAt: string;
181
+ metadata?: Record<string, any>;
182
+ }
183
+
184
+ export interface Task {
185
+ id: string;
186
+ projectId: string;
187
+ title: string;
188
+ description: string;
189
+ status: 'todo' | 'in-progress' | 'completed';
190
+ priority: 'low' | 'medium' | 'high';
191
+ assignee?: string;
192
+ createdAt: string;
193
+ updatedAt: string;
194
+ }
195
+
196
+ export interface ActivityEvent {
197
+ id: string;
198
+ timestamp: string;
199
+ type: string;
200
+ entityType: 'project' | 'task';
201
+ entityId: string;
202
+ entityName: string;
203
+ changes?: Record<string, any>;
204
+ userId?: string;
205
+ }
206
+
207
+ // Indexes
208
+ projects.createIndex('status');
209
+ tasks.createIndex('projectId');
210
+ tasks.createIndex('status');
211
+ tasks.createIndex('priority');
212
+ activity.createIndex('timestamp');
213
+
214
+ // Operations
215
+ export async function createProject(data: Omit<Project, 'id' | 'createdAt' | 'updatedAt'>): Promise<Project> {
216
+ const project: Project = {
217
+ id: 'proj_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
218
+ ...data,
219
+ createdAt: new Date().toISOString(),
220
+ updatedAt: new Date().toISOString(),
221
+ };
222
+ await projects.insert(project);
223
+ await logActivity({
224
+ type: 'project_created',
225
+ entityType: 'project',
226
+ entityId: project.id,
227
+ entityName: project.name,
228
+ });
229
+ return project;
230
+ }
231
+
232
+ export async function createTask(data: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>): Promise<Task> {
233
+ const task: Task = {
234
+ id: 'task_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
235
+ ...data,
236
+ createdAt: new Date().toISOString(),
237
+ updatedAt: new Date().toISOString(),
238
+ };
239
+ await tasks.insert(task);
240
+ await logActivity({
241
+ type: 'task_created',
242
+ entityType: 'task',
243
+ entityId: task.id,
244
+ entityName: task.title,
245
+ });
246
+ return task;
247
+ }
248
+
249
+ export async function updateTask(id: string, updates: Partial<Task>): Promise<void> {
250
+ const task = await tasks.findOne({ id });
251
+ if (!task) throw new Error('Task not found');
252
+
253
+ const updated = {
254
+ ...task,
255
+ ...updates,
256
+ updatedAt: new Date().toISOString(),
257
+ };
258
+ await tasks.update({ id }, updated);
259
+ await logActivity({
260
+ type: 'task_updated',
261
+ entityType: 'task',
262
+ entityId: id,
263
+ entityName: updated.title,
264
+ changes: updates,
265
+ });
266
+ }
267
+
268
+ export async function updateProject(id: string, updates: Partial<Project>): Promise<void> {
269
+ const project = await projects.findOne({ id });
270
+ if (!project) throw new Error('Project not found');
271
+
272
+ const updated = {
273
+ ...project,
274
+ ...updates,
275
+ updatedAt: new Date().toISOString(),
276
+ };
277
+ await projects.update({ id }, updated);
278
+ await logActivity({
279
+ type: 'project_updated',
280
+ entityType: 'project',
281
+ entityId: id,
282
+ entityName: updated.name,
283
+ changes: updates,
284
+ });
285
+ }
286
+
287
+ export async function getTasksByProject(projectId: string): Promise<Task[]> {
288
+ return tasks.find({ projectId }) as Promise<Task[]>;
289
+ }
290
+
291
+ export async function getRecentActivity(limit = 50): Promise<ActivityEvent[]> {
292
+ const events = await activity.find({});
293
+ return (events as ActivityEvent[]).sort((a, b) =>
294
+ new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
295
+ ).slice(0, limit);
296
+ }
297
+
298
+ export async function getDashboardStats(): Promise<any> {
299
+ const allTasks = await tasks.find({});
300
+ const allProjects = await projects.find({});
301
+ return {
302
+ totalProjects: allProjects.length,
303
+ totalTasks: allTasks.length,
304
+ completedTasks: (allTasks as Task[]).filter(t => t.status === 'completed').length,
305
+ inProgressTasks: (allTasks as Task[]).filter(t => t.status === 'in-progress').length,
306
+ activeTasks: (allTasks as Task[]).filter(t => t.status === 'todo').length,
307
+ };
308
+ }
309
+
310
+ async function logActivity(event: Omit<ActivityEvent, 'id' | 'timestamp'>): Promise<void> {
311
+ const activityEvent: ActivityEvent = {
312
+ id: 'evt_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
313
+ timestamp: new Date().toISOString(),
314
+ ...event,
315
+ };
316
+ await activity.insert(activityEvent);
317
+ }
172
318
  `;
173
319
  fs.writeFileSync(path.join(srcDir, 'feltdb.ts'), feltdbTs);
174
320
  // Create a real local-inference agent for browser projects.
@@ -183,12 +329,16 @@ import { db, reports } from '../../src/feltdb';
183
329
  export const researcher = db.defineAgent({
184
330
  name: 'researcher',
185
331
  version: 1,
186
- description: 'Analyze documents with private, on-device inference',
332
+ description: 'Private, on-device AI assistant for document enhancement and analysis',
187
333
  capabilities: [
188
334
  'document-read',
189
335
  'report-write'
190
336
  ],
191
- goals: ['Summarize documents without sending prompts off-device'],
337
+ goals: [
338
+ 'Analyze documents privately without sending data off-device',
339
+ 'Generate insights and enhancements locally',
340
+ 'Support document summarization, expansion, and improvement'
341
+ ],
192
342
  constraints: { maxIterations: 1 },
193
343
  });
194
344
 
@@ -198,20 +348,138 @@ export async function runResearcher(
198
348
  prompt: string,
199
349
  onProgress?: (message: string) => void,
200
350
  ) {
201
- provider ??= new WebLLMProvider({
202
- onProgress: ({ progress, text }) =>
203
- onProgress?.(\`${'${Math.round(progress * 100)}'}% ${'${text}'}\`),
204
- });
205
- const content = await provider.generate([
206
- { role: 'system', content: 'You are a concise research assistant. Keep all reasoning local.' },
207
- { role: 'user', content: prompt },
208
- ]);
209
- await reports.insert({
210
- title: prompt.slice(0, 80) || 'Local research',
211
- content,
212
- createdAt: new Date(),
213
- });
214
- return content;
351
+ if (!provider) {
352
+ provider = new WebLLMProvider({
353
+ onProgress: ({ progress, text }) => {
354
+ const pct = Math.round(progress * 100);
355
+ onProgress?.(\`\${pct}% - \${text}\`);
356
+ },
357
+ });
358
+ }
359
+
360
+ try {
361
+ const systemPrompt = \`You are a helpful AI assistant that operates entirely in the browser.
362
+ You help users:
363
+ - Summarize documents concisely
364
+ - Expand and enhance text with more details
365
+ - Generate ideas and suggestions
366
+ - Improve writing clarity and structure
367
+ - Answer questions about document content
368
+
369
+ Always be concise and practical. Respect the user's intent.\`;
370
+
371
+ const content = await provider.generate([
372
+ { role: 'system', content: systemPrompt },
373
+ { role: 'user', content: prompt },
374
+ ]);
375
+
376
+ await reports.insert({
377
+ id: 'report_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
378
+ title: prompt.slice(0, 80) || 'AI Research Result',
379
+ content,
380
+ createdAt: new Date().toISOString(),
381
+ });
382
+
383
+ return content;
384
+ } catch (error) {
385
+ throw new Error(error instanceof Error ? error.message : 'Failed to generate response');
386
+ }
387
+ }
388
+
389
+ export async function summarizeDocument(
390
+ title: string,
391
+ content: string,
392
+ onProgress?: (message: string) => void,
393
+ ): Promise<string> {
394
+ const prompt = \`Summarize this document in 2-3 concise sentences:
395
+
396
+ Title: \${title}
397
+
398
+ Content: \${content.substring(0, 2000)}\`;
399
+
400
+ return runResearcher(prompt, onProgress);
401
+ }
402
+
403
+ export async function enhanceDocument(
404
+ title: string,
405
+ content: string,
406
+ onProgress?: (message: string) => void,
407
+ ): Promise<string> {
408
+ const prompt = \`Enhance and improve this document by making it more detailed and clearer:
409
+
410
+ Title: \${title}
411
+
412
+ Content: \${content}
413
+
414
+ Provide the enhanced version:\`;
415
+
416
+ return runResearcher(prompt, onProgress);
417
+ }
418
+
419
+ export async function generateIdeas(
420
+ topic: string,
421
+ onProgress?: (message: string) => void,
422
+ ): Promise<string> {
423
+ const prompt = \`Generate 5 creative ideas related to: \${topic}
424
+
425
+ Format as a numbered list with brief explanations.\`;
426
+
427
+ return runResearcher(prompt, onProgress);
428
+ }
429
+
430
+ export async function generateFeltDBFeature(
431
+ featureDescription: string,
432
+ onProgress?: (message: string) => void,
433
+ ): Promise<string> {
434
+ const prompt = \`Generate FeltDB flow syntax for: \${featureDescription}
435
+
436
+ Include:
437
+ 1. Collection definitions with proper types
438
+ 2. Capability definitions
439
+ 3. Workflow definitions if needed
440
+ 4. Policy rules for access control
441
+
442
+ Format the output as valid FeltDB flow language.\`;
443
+
444
+ return runResearcher(prompt, onProgress);
445
+ }
446
+
447
+ export async function generateReactComponent(
448
+ componentDescription: string,
449
+ onProgress?: (message: string) => void,
450
+ ): Promise<string> {
451
+ const prompt = \`Generate a React component for: \${componentDescription}
452
+
453
+ Include:
454
+ 1. TypeScript types/interfaces
455
+ 2. useState and useEffect hooks
456
+ 3. Error handling
457
+ 4. Loading states
458
+ 5. Proper styling with CSS classes
459
+
460
+ Export a functional component that can be imported and used.\`;
461
+
462
+ return runResearcher(prompt, onProgress);
463
+ }
464
+
465
+ export async function generateCapability(
466
+ capabilityName: string,
467
+ description: string,
468
+ onProgress?: (message: string) => void,
469
+ ): Promise<string> {
470
+ const prompt = \`Generate a FeltDB capability definition for: \${capabilityName}
471
+
472
+ Description: \${description}
473
+
474
+ Include:
475
+ 1. TypeScript interface definitions
476
+ 2. Implementation with proper types
477
+ 3. Error handling
478
+ 4. Documentation comments
479
+
480
+ Make it production-ready.\`;
481
+
482
+ return runResearcher(prompt, onProgress);
215
483
  }
216
484
  `;
217
485
  fs.writeFileSync(path.join(feltdbDir, 'agents', 'researcher.ts'), agentTs);
@@ -243,24 +511,44 @@ export const capabilities = {
243
511
  // Create main application file based on framework
244
512
  if (framework === 'react') {
245
513
  const agentImport = hasAgents
246
- ? "import { runResearcher } from '../feltdb/agents/researcher';"
514
+ ? "import { runResearcher, summarizeDocument, enhanceDocument, generateIdeas, generateFeltDBFeature, generateReactComponent, generateCapability } from '../feltdb/agents/researcher';"
247
515
  : '';
248
516
  const agentState = hasAgents
249
- ? ` const [prompt, setPrompt] = useState('Summarize why local-first applications are useful.');
517
+ ? ` const [prompt, setPrompt] = useState('');
250
518
  const [answer, setAnswer] = useState('');
251
519
  const [modelStatus, setModelStatus] = useState('Model not loaded');
252
- const [generating, setGenerating] = useState(false);`
520
+ const [generating, setGenerating] = useState(false);
521
+ const [selectedDoc, setSelectedDoc] = useState<any | null>(null);
522
+ const [aiMode, setAiMode] = useState<'custom' | 'summarize' | 'enhance' | 'ideas' | 'feltdb' | 'react' | 'capability'>('custom');`
253
523
  : '';
254
524
  const agentHandler = hasAgents
255
525
  ? `
256
- const handleResearch = async () => {
526
+ const handleRunAI = async (mode: 'custom' | 'summarize' | 'enhance' | 'ideas' | 'feltdb' | 'react' | 'capability') => {
257
527
  setGenerating(true);
258
528
  setAnswer('');
529
+ setModelStatus('Running inference...');
530
+
259
531
  try {
260
- setAnswer(await runResearcher(prompt, setModelStatus));
261
- setModelStatus('Ready inference stayed in this browser');
532
+ let result = '';
533
+ if (mode === 'custom') {
534
+ result = await runResearcher(prompt, setModelStatus);
535
+ } else if (mode === 'summarize' && selectedDoc) {
536
+ result = await summarizeDocument(selectedDoc.title, selectedDoc.content, setModelStatus);
537
+ } else if (mode === 'enhance' && selectedDoc) {
538
+ result = await enhanceDocument(selectedDoc.title, selectedDoc.content, setModelStatus);
539
+ } else if (mode === 'ideas') {
540
+ result = await generateIdeas(prompt, setModelStatus);
541
+ } else if (mode === 'feltdb') {
542
+ result = await generateFeltDBFeature(prompt, setModelStatus);
543
+ } else if (mode === 'react') {
544
+ result = await generateReactComponent(prompt, setModelStatus);
545
+ } else if (mode === 'capability') {
546
+ result = await generateCapability('Feature', prompt, setModelStatus);
547
+ }
548
+ setAnswer(result);
549
+ setModelStatus('✓ Ready — all processing done locally');
262
550
  } catch (error) {
263
- setModelStatus(error instanceof Error ? error.message : String(error));
551
+ setModelStatus('✗ Error: ' + (error instanceof Error ? error.message : String(error)));
264
552
  } finally {
265
553
  setGenerating(false);
266
554
  }
@@ -270,228 +558,551 @@ export const capabilities = {
270
558
  const agentMarkup = hasAgents
271
559
  ? `
272
560
  <section className="researcher">
273
- <h2>Private WebLLM Researcher</h2>
274
- <p>{modelStatus}</p>
275
- <textarea value={prompt} onChange={event => setPrompt(event.target.value)} rows={4} />
276
- <button onClick={handleResearch} disabled={generating || !prompt.trim()}>
277
- {generating ? 'Running locally…' : 'Run researcher'}
561
+ <h2>🤖 AI Assistant (Private, Local)</h2>
562
+ <div className="status-badge">{modelStatus}</div>
563
+
564
+ <div className="ai-modes">
565
+ <div className="mode-group">
566
+ <span className="mode-group-label">📝 Content</span>
567
+ <button
568
+ className={\`mode-btn \${aiMode === 'custom' ? 'active' : ''}\`}
569
+ onClick={() => setAiMode('custom')}
570
+ >
571
+ ✍️ Custom
572
+ </button>
573
+ <button
574
+ className={\`mode-btn \${aiMode === 'summarize' ? 'active' : ''}\`}
575
+ onClick={() => setAiMode('summarize')}
576
+ disabled={!selectedDoc}
577
+ >
578
+ 📄 Summarize
579
+ </button>
580
+ <button
581
+ className={\`mode-btn \${aiMode === 'enhance' ? 'active' : ''}\`}
582
+ onClick={() => setAiMode('enhance')}
583
+ disabled={!selectedDoc}
584
+ >
585
+ ✨ Enhance
586
+ </button>
587
+ <button
588
+ className={\`mode-btn \${aiMode === 'ideas' ? 'active' : ''}\`}
589
+ onClick={() => setAiMode('ideas')}
590
+ >
591
+ 💡 Ideas
592
+ </button>
593
+ </div>
594
+
595
+ <div className="mode-group">
596
+ <span className="mode-group-label">💻 Code Gen</span>
597
+ <button
598
+ className={\`mode-btn code-mode \${aiMode === 'feltdb' ? 'active' : ''}\`}
599
+ onClick={() => setAiMode('feltdb')}
600
+ >
601
+ ⚙️ FeltDB Flow
602
+ </button>
603
+ <button
604
+ className={\`mode-btn code-mode \${aiMode === 'react' ? 'active' : ''}\`}
605
+ onClick={() => setAiMode('react')}
606
+ >
607
+ ⚛️ React
608
+ </button>
609
+ <button
610
+ className={\`mode-btn code-mode \${aiMode === 'capability' ? 'active' : ''}\`}
611
+ onClick={() => setAiMode('capability')}
612
+ >
613
+ 🔧 Capability
614
+ </button>
615
+ </div>
616
+ </div>
617
+
618
+ {aiMode === 'summarize' || aiMode === 'enhance' ? (
619
+ <div className="doc-selector">
620
+ <label>Select a document:</label>
621
+ <select
622
+ value={selectedDoc?.id || ''}
623
+ onChange={(e) => {
624
+ const doc = docs.find((d: any) => d.id === e.target.value);
625
+ setSelectedDoc(doc || null);
626
+ }}
627
+ >
628
+ <option value="">Choose a document...</option>
629
+ {docs.map((doc: any) => (
630
+ <option key={doc.id} value={doc.id}>
631
+ {doc.title}
632
+ </option>
633
+ ))}
634
+ </select>
635
+ </div>
636
+ ) : (
637
+ <textarea
638
+ value={prompt}
639
+ onChange={(event) => setPrompt(event.target.value)}
640
+ placeholder={
641
+ aiMode === 'ideas' ? 'What topic do you want ideas for?' :
642
+ aiMode === 'feltdb' ? 'Describe the FeltDB feature you want to create...' :
643
+ aiMode === 'react' ? 'Describe the React component you need...' :
644
+ aiMode === 'capability' ? 'Describe the capability implementation...' :
645
+ 'Enter your prompt...'
646
+ }
647
+ rows={5}
648
+ />
649
+ )}
650
+
651
+ <button
652
+ onClick={() => handleRunAI(aiMode)}
653
+ disabled={
654
+ generating ||
655
+ (aiMode === 'custom' && !prompt.trim()) ||
656
+ ((aiMode === 'summarize' || aiMode === 'enhance') && !selectedDoc) ||
657
+ ((aiMode === 'ideas' || aiMode === 'feltdb' || aiMode === 'react' || aiMode === 'capability') && !prompt.trim())
658
+ }
659
+ className="research-btn"
660
+ >
661
+ {generating ? '⏳ Running locally…' : '🚀 Generate'}
278
662
  </button>
279
- {answer && <article><h3>Result</h3><p>{answer}</p></article>}
663
+
664
+ {answer && (
665
+ <article className="research-result">
666
+ <h3>Generated Output</h3>
667
+ <div className="result-content">{answer}</div>
668
+ <div className="result-actions">
669
+ {aiMode === 'custom' || aiMode === 'enhance' || aiMode === 'ideas' ? (
670
+ <button
671
+ onClick={() => {
672
+ const title = aiMode === 'ideas' ? prompt : (selectedDoc?.title || 'AI Generated');
673
+ documents.insert({
674
+ id: 'doc_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
675
+ title: title + ' (AI Enhanced)',
676
+ content: answer,
677
+ createdAt: new Date().toISOString(),
678
+ });
679
+ loadDocs();
680
+ setAnswer('');
681
+ }}
682
+ className="save-result-btn"
683
+ >
684
+ 💾 Save to Documents
685
+ </button>
686
+ ) : (
687
+ <>
688
+ <button
689
+ onClick={() => {
690
+ const filename = aiMode === 'feltdb' ? 'feature.flow' :
691
+ aiMode === 'react' ? 'Component.tsx' :
692
+ 'capability.ts';
693
+ const blob = new Blob([answer], { type: 'text/plain' });
694
+ const url = URL.createObjectURL(blob);
695
+ const a = document.createElement('a');
696
+ a.href = url;
697
+ a.download = filename;
698
+ a.click();
699
+ URL.revokeObjectURL(url);
700
+ }}
701
+ className="save-result-btn"
702
+ >
703
+ 📥 Download Code
704
+ </button>
705
+ <button
706
+ onClick={() => {
707
+ navigator.clipboard.writeText(answer);
708
+ alert('Code copied to clipboard!');
709
+ }}
710
+ className="copy-result-btn"
711
+ >
712
+ 📋 Copy Code
713
+ </button>
714
+ </>
715
+ )}
716
+ </div>
717
+ <p className="code-note">
718
+ 💡 Tip: Generated code can be downloaded, copied, or saved to documents for later reference.
719
+ </p>
720
+ </article>
721
+ )}
280
722
  </section>`
281
723
  : '';
282
724
  const appTsx = `import React, { useState, useEffect } from 'react';
283
- import { db, documents } from './feltdb';
284
- ${agentImport}
725
+ import { useCollection } from '@feltdb/core/react';
726
+ import {
727
+ projects,
728
+ tasks,
729
+ activity,
730
+ createProject,
731
+ createTask,
732
+ updateTask,
733
+ getDashboardStats,
734
+ } from './feltdb';
735
+
736
+ type View = 'dashboard' | 'projects' | 'tasks' | 'activity' | 'inspector';
285
737
 
286
738
  export function App() {
287
- const [docs, setDocs] = useState<any[]>([]);
288
- const [loading, setLoading] = useState(true);
289
- ${agentState}
739
+ const [currentView, setCurrentView] = useState<View>('dashboard');
740
+ const [selectedProjectId, setSelectedProjectId] = useState<string>('');
741
+ const [dashboardStats, setDashboardStats] = useState<any>(null);
742
+ const [newProjectName, setNewProjectName] = useState('');
743
+ const [newTaskTitle, setNewTaskTitle] = useState('');
744
+ const [newTaskProject, setNewTaskProject] = useState('');
745
+
746
+ const { data: projectsList } = useCollection(projects);
747
+ const { data: tasksList } = useCollection(tasks);
748
+ const { data: activityList } = useCollection(activity);
290
749
 
291
750
  useEffect(() => {
292
- const loadDocs = async () => {
293
- try {
294
- const allDocs = await documents.find({});
295
- setDocs(allDocs);
296
- } catch (err) {
297
- console.error('Error loading documents:', err);
298
- } finally {
299
- setLoading(false);
300
- }
301
- };
751
+ getDashboardStats().then(setDashboardStats);
752
+ }, [projectsList, tasksList]);
302
753
 
303
- loadDocs();
304
- }, []);
754
+ const handleCreateProject = async (e: React.FormEvent) => {
755
+ e.preventDefault();
756
+ if (!newProjectName.trim()) return;
757
+ await createProject({
758
+ name: newProjectName,
759
+ description: 'Created via Workspace',
760
+ status: 'active',
761
+ metadata: {},
762
+ });
763
+ setNewProjectName('');
764
+ };
305
765
 
306
- const handleAddDocument = async () => {
307
- try {
308
- await documents.insert({
309
- title: 'New Document',
310
- content: 'Enter content here',
311
- createdAt: new Date(),
312
- });
313
- // Reload documents
314
- const allDocs = await documents.find({});
315
- setDocs(allDocs);
316
- } catch (err) {
317
- console.error('Error adding document:', err);
318
- }
766
+ const handleCreateTask = async (e: React.FormEvent) => {
767
+ e.preventDefault();
768
+ if (!newTaskTitle.trim() || !newTaskProject) return;
769
+ await createTask({
770
+ projectId: newTaskProject,
771
+ title: newTaskTitle,
772
+ description: '',
773
+ status: 'todo',
774
+ priority: 'medium',
775
+ });
776
+ setNewTaskTitle('');
777
+ };
778
+
779
+ const containerStyle: React.CSSProperties = {
780
+ display: 'flex',
781
+ minHeight: '100vh',
782
+ fontFamily: 'system-ui, sans-serif',
783
+ backgroundColor: '#f5f7fa',
784
+ };
785
+
786
+ const sidebarStyle: React.CSSProperties = {
787
+ width: '240px',
788
+ backgroundColor: '#1e293b',
789
+ color: '#e2e8f0',
790
+ padding: '20px',
791
+ borderRight: '1px solid #334155',
792
+ display: 'flex',
793
+ flexDirection: 'column',
794
+ gap: '20px',
795
+ };
796
+
797
+ const mainStyle: React.CSSProperties = {
798
+ flex: 1,
799
+ display: 'flex',
800
+ flexDirection: 'column',
801
+ };
802
+
803
+ const headerStyle: React.CSSProperties = {
804
+ backgroundColor: '#fff',
805
+ borderBottom: '1px solid #e2e8f0',
806
+ padding: '20px',
807
+ display: 'flex',
808
+ justifyContent: 'space-between',
809
+ alignItems: 'center',
810
+ };
811
+
812
+ const contentStyle: React.CSSProperties = {
813
+ flex: 1,
814
+ padding: '20px',
815
+ overflowY: 'auto',
816
+ };
817
+
818
+ const titleStyle: React.CSSProperties = {
819
+ fontSize: '24px',
820
+ fontWeight: 'bold',
821
+ color: '#1e293b',
822
+ };
823
+
824
+ const navItemStyle = (active: boolean): React.CSSProperties => ({
825
+ padding: '10px 12px',
826
+ borderRadius: '6px',
827
+ cursor: 'pointer',
828
+ backgroundColor: active ? '#334155' : 'transparent',
829
+ color: active ? '#fff' : '#cbd5e1',
830
+ border: 'none',
831
+ textAlign: 'left',
832
+ width: '100%',
833
+ fontSize: '14px',
834
+ });
835
+
836
+ const statsStyle: React.CSSProperties = {
837
+ display: 'grid',
838
+ gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
839
+ gap: '16px',
840
+ marginBottom: '24px',
841
+ };
842
+
843
+ const statCardStyle: React.CSSProperties = {
844
+ backgroundColor: '#fff',
845
+ padding: '16px',
846
+ borderRadius: '8px',
847
+ border: '1px solid #e2e8f0',
848
+ };
849
+
850
+ const formStyle: React.CSSProperties = {
851
+ display: 'flex',
852
+ gap: '8px',
853
+ marginBottom: '20px',
854
+ };
855
+
856
+ const inputStyle: React.CSSProperties = {
857
+ flex: 1,
858
+ padding: '8px 12px',
859
+ border: '1px solid #cbd5e1',
860
+ borderRadius: '6px',
861
+ fontSize: '14px',
862
+ };
863
+
864
+ const buttonStyle: React.CSSProperties = {
865
+ padding: '8px 16px',
866
+ backgroundColor: '#0284c7',
867
+ color: '#fff',
868
+ border: 'none',
869
+ borderRadius: '6px',
870
+ cursor: 'pointer',
871
+ fontSize: '14px',
872
+ };
873
+
874
+ const itemStyle: React.CSSProperties = {
875
+ backgroundColor: '#fff',
876
+ padding: '12px',
877
+ borderRadius: '6px',
878
+ marginBottom: '8px',
879
+ border: '1px solid #e2e8f0',
319
880
  };
320
- ${agentHandler}
321
881
 
322
882
  return (
323
- <div className="app">
324
- <header>
325
- <h1>🌊 FeltDB Research App</h1>
326
- <p>Distributed document management with agents and capabilities</p>
327
- </header>
328
-
329
- <main>
330
- <section className="stats">
331
- <div className="stat">
332
- <span className="label">Documents:</span>
333
- <span className="value">{docs.length}</span>
334
- </div>
335
- <div className="stat">
336
- <span className="label">Runtime:</span>
337
- <span className="value">${runtime}</span>
338
- </div>
339
- <div className="stat">
340
- <span className="label">Distributed:</span>
341
- <span className="value">${distributed ? '' : '✗'}</span>
883
+ <div style={containerStyle}>
884
+ <div style={sidebarStyle}>
885
+ <div style={{ fontSize: '16px', fontWeight: 'bold' }}>⚡ FeltDB Workspace</div>
886
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
887
+ <button
888
+ style={navItemStyle(currentView === 'dashboard')}
889
+ onClick={() => setCurrentView('dashboard')}
890
+ >
891
+ 📊 Dashboard
892
+ </button>
893
+ <button
894
+ style={navItemStyle(currentView === 'projects')}
895
+ onClick={() => setCurrentView('projects')}
896
+ >
897
+ 📁 Projects
898
+ </button>
899
+ <button
900
+ style={navItemStyle(currentView === 'tasks')}
901
+ onClick={() => setCurrentView('tasks')}
902
+ >
903
+ ✅ Tasks
904
+ </button>
905
+ <button
906
+ style={navItemStyle(currentView === 'activity')}
907
+ onClick={() => setCurrentView('activity')}
908
+ >
909
+ 📝 Activity
910
+ </button>
911
+ <button
912
+ style={navItemStyle(currentView === 'inspector')}
913
+ onClick={() => setCurrentView('inspector')}
914
+ >
915
+ 🔍 Inspector
916
+ </button>
917
+ </div>
918
+ <div style={{ marginTop: 'auto', fontSize: '12px', color: '#64748b' }}>
919
+ {projectsList.length} projects • {tasksList.length} tasks
920
+ </div>
921
+ </div>
922
+
923
+ <div style={mainStyle}>
924
+ <div style={headerStyle}>
925
+ <div style={titleStyle}>
926
+ {currentView === 'dashboard' && '📊 Dashboard'}
927
+ {currentView === 'projects' && '📁 Projects'}
928
+ {currentView === 'tasks' && '✅ Tasks'}
929
+ {currentView === 'activity' && '📝 Activity'}
930
+ {currentView === 'inspector' && '🔍 Data Inspector'}
342
931
  </div>
343
- </section>
344
-
345
- <section className="documents">
346
- <h2>Documents</h2>
347
- {loading ? (
348
- <p>Loading...</p>
349
- ) : docs.length === 0 ? (
350
- <p>No documents yet</p>
351
- ) : (
352
- <ul>
353
- {docs.map((doc: any) => (
354
- <li key={doc.id}>
355
- <h3>{doc.title}</h3>
356
- <p>{doc.content}</p>
357
- </li>
358
- ))}
359
- </ul>
932
+ </div>
933
+
934
+ <div style={contentStyle}>
935
+ {currentView === 'dashboard' && (
936
+ <div>
937
+ <div style={statsStyle}>
938
+ <div style={statCardStyle}>
939
+ <div style={{ fontSize: '12px', color: '#64748b' }}>Projects</div>
940
+ <div style={{ fontSize: '28px', fontWeight: 'bold' }}>{projectsList.length}</div>
941
+ </div>
942
+ <div style={statCardStyle}>
943
+ <div style={{ fontSize: '12px', color: '#64748b' }}>Tasks</div>
944
+ <div style={{ fontSize: '28px', fontWeight: 'bold' }}>{tasksList.length}</div>
945
+ </div>
946
+ <div style={statCardStyle}>
947
+ <div style={{ fontSize: '12px', color: '#64748b' }}>Activity Events</div>
948
+ <div style={{ fontSize: '28px', fontWeight: 'bold' }}>{activityList.length}</div>
949
+ </div>
950
+ </div>
951
+ <h3>Welcome to FeltDB Workspace!</h3>
952
+ <p style={{ color: '#64748b', lineHeight: '1.6' }}>
953
+ This application demonstrates core FeltDB capabilities:<br/>
954
+ ✅ Collections and relationships (Projects → Tasks)<br/>
955
+ ✅ Indexed querying for efficient lookups<br/>
956
+ ✅ Activity logs for audit trails<br/>
957
+ ✅ Local-first persistence with IndexedDB<br/>
958
+ ✅ Reactive updates using React hooks<br/>
959
+ <br/>
960
+ Create a project to get started! 👇
961
+ </p>
962
+ </div>
360
963
  )}
361
- <button onClick={handleAddDocument}>Add Document</button>
362
- </section>
363
- ${agentMarkup}
364
- </main>
365
-
366
- <style>{\`
367
- body {
368
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto;
369
- margin: 0;
370
- padding: 0;
371
- background: #f5f5f5;
372
- }
373
-
374
- .app {
375
- max-width: 1200px;
376
- margin: 0 auto;
377
- padding: 20px;
378
- }
379
-
380
- header {
381
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
382
- color: white;
383
- padding: 30px;
384
- border-radius: 8px;
385
- margin-bottom: 30px;
386
- }
387
-
388
- header h1 {
389
- margin: 0 0 10px 0;
390
- font-size: 28px;
391
- }
392
-
393
- header p {
394
- margin: 0;
395
- opacity: 0.9;
396
- }
397
-
398
- main {
399
- background: white;
400
- padding: 20px;
401
- border-radius: 8px;
402
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
403
- }
404
-
405
- .stats {
406
- display: grid;
407
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
408
- gap: 20px;
409
- margin-bottom: 30px;
410
- }
411
-
412
- .stat {
413
- padding: 15px;
414
- background: #f9f9f9;
415
- border-radius: 4px;
416
- border-left: 3px solid #667eea;
417
- }
418
-
419
- .stat .label {
420
- display: block;
421
- color: #666;
422
- font-size: 12px;
423
- text-transform: uppercase;
424
- margin-bottom: 5px;
425
- }
426
-
427
- .stat .value {
428
- display: block;
429
- font-size: 24px;
430
- font-weight: bold;
431
- color: #333;
432
- }
433
-
434
- .documents h2 {
435
- margin-top: 0;
436
- }
437
-
438
- .documents ul {
439
- list-style: none;
440
- padding: 0;
441
- margin: 0 0 20px 0;
442
- }
443
-
444
- .documents li {
445
- padding: 15px;
446
- background: #f9f9f9;
447
- border-radius: 4px;
448
- margin-bottom: 10px;
449
- }
450
-
451
- .documents h3 {
452
- margin: 0 0 10px 0;
453
- color: #333;
454
- }
455
-
456
- .documents p {
457
- margin: 0;
458
- color: #666;
459
- font-size: 14px;
460
- }
461
-
462
- button {
463
- background: #667eea;
464
- color: white;
465
- border: none;
466
- padding: 10px 20px;
467
- border-radius: 4px;
468
- cursor: pointer;
469
- font-size: 14px;
470
- font-weight: 500;
471
- }
472
-
473
- button:hover {
474
- background: #5568d3;
475
- }
476
-
477
- textarea {
478
- box-sizing: border-box;
479
- display: block;
480
- margin: 12px 0;
481
- padding: 10px;
482
- width: 100%;
483
- }
484
-
485
- .researcher {
486
- border-top: 1px solid #eee;
487
- margin-top: 30px;
488
- padding-top: 20px;
489
- }
490
- \`}</style>
964
+
965
+ {currentView === 'projects' && (
966
+ <div>
967
+ <form style={formStyle} onSubmit={handleCreateProject}>
968
+ <input
969
+ style={inputStyle}
970
+ type="text"
971
+ placeholder="New project name..."
972
+ value={newProjectName}
973
+ onChange={(e) => setNewProjectName(e.target.value)}
974
+ />
975
+ <button style={buttonStyle} type="submit">Create</button>
976
+ </form>
977
+ <div>
978
+ {projectsList.map((p: any) => (
979
+ <div key={p.id} style={itemStyle}>
980
+ <div style={{ fontWeight: 'bold' }}>{p.name}</div>
981
+ {p.description && (
982
+ <div style={{ fontSize: '14px', color: '#64748b', margin: '4px 0' }}>
983
+ {p.description}
984
+ </div>
985
+ )}
986
+ <div style={{ fontSize: '12px', color: '#0284c7', marginTop: '4px' }}>
987
+ Status: {p.status}
988
+ </div>
989
+ </div>
990
+ ))}
991
+ {projectsList.length === 0 && (
992
+ <p style={{ color: '#64748b' }}>No projects yet. Create one above!</p>
993
+ )}
994
+ </div>
995
+ </div>
996
+ )}
997
+
998
+ {currentView === 'tasks' && (
999
+ <div>
1000
+ <form style={formStyle} onSubmit={handleCreateTask}>
1001
+ <select
1002
+ style={inputStyle}
1003
+ value={newTaskProject}
1004
+ onChange={(e) => setNewTaskProject(e.target.value)}
1005
+ >
1006
+ <option value="">Select a project...</option>
1007
+ {projectsList.map((p: any) => (
1008
+ <option key={p.id} value={p.id}>{p.name}</option>
1009
+ ))}
1010
+ </select>
1011
+ <input
1012
+ style={inputStyle}
1013
+ type="text"
1014
+ placeholder="Task title..."
1015
+ value={newTaskTitle}
1016
+ onChange={(e) => setNewTaskTitle(e.target.value)}
1017
+ />
1018
+ <button style={buttonStyle} type="submit">Create</button>
1019
+ </form>
1020
+ <div>
1021
+ {tasksList.map((t: any) => (
1022
+ <div key={t.id} style={itemStyle}>
1023
+ <div style={{ fontWeight: 'bold' }}>{t.title}</div>
1024
+ {t.description && (
1025
+ <div style={{ fontSize: '14px', color: '#64748b', margin: '4px 0' }}>
1026
+ {t.description}
1027
+ </div>
1028
+ )}
1029
+ <div style={{ fontSize: '12px', color: '#64748b', marginTop: '4px' }}>
1030
+ Priority: <strong>{t.priority}</strong> | Status: <strong>{t.status}</strong>
1031
+ </div>
1032
+ </div>
1033
+ ))}
1034
+ {tasksList.length === 0 && (
1035
+ <p style={{ color: '#64748b' }}>No tasks yet. Create one above!</p>
1036
+ )}
1037
+ </div>
1038
+ </div>
1039
+ )}
1040
+
1041
+ {currentView === 'activity' && (
1042
+ <div>
1043
+ {activityList.length === 0 ? (
1044
+ <p style={{ color: '#64748b' }}>No activity yet</p>
1045
+ ) : (
1046
+ <div>
1047
+ {activityList.slice(-30).map((event: any, idx: number) => (
1048
+ <div key={idx} style={itemStyle}>
1049
+ <div style={{ fontSize: '12px', color: '#64748b' }}>
1050
+ {new Date(event.timestamp).toLocaleString()}
1051
+ </div>
1052
+ <div style={{ fontWeight: 'bold' }}>{event.type}</div>
1053
+ {event.entityName && (
1054
+ <div style={{ fontSize: '14px', marginTop: '4px' }}>
1055
+ {event.entityName}
1056
+ </div>
1057
+ )}
1058
+ </div>
1059
+ ))}
1060
+ </div>
1061
+ )}
1062
+ </div>
1063
+ )}
1064
+
1065
+ {currentView === 'inspector' && (
1066
+ <div>
1067
+ <h3>Data Inspector</h3>
1068
+ <p style={{ color: '#64748b', marginBottom: '16px' }}>
1069
+ Developer panel showing FeltDB internals
1070
+ </p>
1071
+ <div style={statCardStyle}>
1072
+ <div style={{ fontWeight: 'bold', marginBottom: '8px' }}>Collections</div>
1073
+ <div style={{ fontSize: '14px', color: '#64748b', lineHeight: '1.6' }}>
1074
+ 📦 projects: {projectsList.length} records<br/>
1075
+ 📦 tasks: {tasksList.length} records<br/>
1076
+ 📦 activity: {activityList.length} events<br/>
1077
+ </div>
1078
+ </div>
1079
+ <div style={statCardStyle}>
1080
+ <div style={{ fontWeight: 'bold', marginBottom: '8px' }}>Storage</div>
1081
+ <div style={{ fontSize: '14px', color: '#64748b', lineHeight: '1.6' }}>
1082
+ 💾 Type: IndexedDB<br/>
1083
+ 🔑 Namespace: FeltDB<br/>
1084
+ 🔄 Sync: Automatic
1085
+ </div>
1086
+ </div>
1087
+ <div style={statCardStyle}>
1088
+ <div style={{ fontWeight: 'bold', marginBottom: '8px' }}>Indexes</div>
1089
+ <div style={{ fontSize: '14px', color: '#64748b', lineHeight: '1.6' }}>
1090
+ 📌 projects.status<br/>
1091
+ 📌 tasks.projectId<br/>
1092
+ 📌 tasks.status<br/>
1093
+ 📌 tasks.priority<br/>
1094
+ 📌 activity.timestamp
1095
+ </div>
1096
+ </div>
1097
+ </div>
1098
+ )}
1099
+ </div>
1100
+ </div>
491
1101
  </div>
492
1102
  );
493
1103
  }
494
1104
  `;
1105
+ ;
495
1106
  fs.writeFileSync(path.join(srcDir, 'App.tsx'), appTsx);
496
1107
  const indexTsx = `import React from 'react';
497
1108
  import ReactDOM from 'react-dom/client';
@@ -537,13 +1148,282 @@ main().catch(console.error);
537
1148
  fs.writeFileSync(path.join(projectDir, 'index.html'), indexHtml);
538
1149
  // Create .env.example
539
1150
  const envExample = `# FeltDB Configuration
1151
+ # Copy this file to .env.local and update the values
1152
+
1153
+ # API Key for authenticating with FeltDB servers
1154
+ # Leave empty for browser runtime; required for authenticated self-hosted and managed runtimes
540
1155
  VITE_FELTDB_API_KEY=
1156
+
1157
+ # FeltDB Server URL (self-hosted or managed runtime)
541
1158
  VITE_FELTDB_URL=http://localhost:7700
542
- # Override the versioned self-hosted container when needed:
1159
+
1160
+ # Managed example: https://runtime.your-app.feltdb.com
1161
+ VITE_FELTDB_MANAGED_URL=
1162
+ VITE_FELTDB_MANAGED_API_KEY=
1163
+ VITE_FELTDB_MANAGED_TENANT_ID=
1164
+ VITE_FELTDB_MANAGED_APPLICATION_ID=
1165
+ VITE_FELTDB_MANAGED_NAMESPACE=
1166
+ VITE_FELTDB_MANAGED_ENVIRONMENT=production
1167
+
1168
+ # Override the default self-hosted container image
1169
+ # Example: ghcr.io/rkendel1/feltdb:latest
543
1170
  FELTDB_IMAGE=
1171
+
1172
+ # Node environment
544
1173
  NODE_ENV=development
545
1174
  `;
546
1175
  fs.writeFileSync(path.join(projectDir, '.env.example'), envExample);
1176
+ // Create RUNTIME_GUIDE.md
1177
+ const runtimeGuide = `# Runtime Configuration Guide
1178
+
1179
+ This guide explains the differences between FeltDB runtime options and how to choose the right one for your use case.
1180
+
1181
+ ## Browser Runtime
1182
+
1183
+ \`\`\`json
1184
+ {
1185
+ "runtime": "browser",
1186
+ "storage": "opfs"
1187
+ }
1188
+ \`\`\`
1189
+
1190
+ ### What it does
1191
+ - Runs FeltDB entirely in the browser using JavaScript
1192
+ - Stores data in the browser's Origin Private File System (OPFS)
1193
+ - No backend server required
1194
+
1195
+ ### When to use
1196
+ - Building offline-first applications
1197
+ - Client-side only projects
1198
+ - Prototyping and development
1199
+ - Privacy-focused applications where data never leaves the user's device
1200
+
1201
+ ### Limitations
1202
+ - Data is isolated per browser/device
1203
+ - Single-user only
1204
+ - Limited by browser disk space (usually 50GB+)
1205
+ - Cannot be accessed from other devices/browsers
1206
+
1207
+ ### Setup
1208
+ No special setup required. Just run \`npm run dev\`.
1209
+
1210
+ ## Node.js Runtime
1211
+
1212
+ \`\`\`json
1213
+ {
1214
+ "runtime": "node",
1215
+ "storage": "durable"
1216
+ }
1217
+ \`\`\`
1218
+
1219
+ ### What it does
1220
+ - Runs FeltDB as a Node.js server
1221
+ - Uses file-based or database storage
1222
+ - Accessible via HTTP/API
1223
+
1224
+ ### When to use
1225
+ - Building backend APIs
1226
+ - Server-side applications
1227
+ - REST API backends
1228
+ - Integration with other services
1229
+
1230
+ ### Limitations
1231
+ - Requires Node.js environment
1232
+ - Single server by default (no automatic distribution)
1233
+ - Data persistence depends on storage backend
1234
+
1235
+ ### Setup
1236
+ \`\`\`bash
1237
+ npm run dev
1238
+ # or
1239
+ npm run feltdb:server
1240
+ \`\`\`
1241
+
1242
+ ## Self-Hosted Runtime
1243
+
1244
+ \`\`\`json
1245
+ {
1246
+ "runtime": "self-hosted",
1247
+ "storage": "durable"
1248
+ }
1249
+ \`\`\`
1250
+
1251
+ ### What it does
1252
+ - Runs a dedicated FeltDB server in Docker
1253
+ - Provides distributed capabilities
1254
+ - Enables multi-user, multi-device access
1255
+ - Includes API management and authentication
1256
+
1257
+ ### When to use
1258
+ - Production deployments
1259
+ - Multi-user applications
1260
+ - Synchronization across devices
1261
+ - Team collaboration features
1262
+ - Advanced distributed scenarios
1263
+
1264
+ ### Requirements
1265
+ - Docker installed and running
1266
+ - Internet access (first run downloads image)
1267
+
1268
+ ### Setup
1269
+ \`\`\`bash
1270
+ npm run dev
1271
+ # Docker will be started automatically
1272
+ \`\`\`
1273
+
1274
+ The self-hosted instance runs on \`http://localhost:7700\` by default.
1275
+
1276
+ ## Managed Runtime
1277
+
1278
+ \`\`\`json
1279
+ {
1280
+ "runtime": "managed",
1281
+ "storage": "managed"
1282
+ }
1283
+ \`\`\`
1284
+
1285
+ Managed mode uses the same application API with a FeltDB-hosted endpoint. The CLI creates \`.env.local\` with \`VITE_FELTDB_MANAGED_URL\` and \`VITE_FELTDB_MANAGED_API_KEY\` after account setup. Studio uses that same connection for state, health, operations, and API-key administration.
1286
+
1287
+ ## Vector Search Status
1288
+
1289
+ ### Current Status
1290
+ Vector search support in FeltDB is available through integrations with vector databases:
1291
+ - Milvus
1292
+ - Pinecone
1293
+ - Weaviate
1294
+ - Local vector storage (development)
1295
+
1296
+ ### Enabling Vector Search
1297
+ To enable vector search capabilities:
1298
+
1299
+ 1. Update \`feltdb.config.json\`:
1300
+ \`\`\`json
1301
+ {
1302
+ "capabilities": {
1303
+ "search": true,
1304
+ "vector-search": true
1305
+ }
1306
+ }
1307
+ \`\`\`
1308
+
1309
+ 2. Configure your vector database in your environment variables or code
1310
+
1311
+ 3. Implement vector embedding logic in your capabilities:
1312
+ \`\`\`typescript
1313
+ // feeldb/capabilities/index.ts
1314
+ export const capabilities = {
1315
+ 'vector-search': {
1316
+ enabled: true,
1317
+ scope: ['documents:read', 'capabilities:execute'],
1318
+ },
1319
+ };
1320
+ \`\`\`
1321
+
1322
+ ### Vector Storage Options
1323
+
1324
+ #### Local Development
1325
+ For development, FeltDB includes a local vector store:
1326
+ - In-memory embeddings
1327
+ - No external dependencies
1328
+ - Perfect for prototyping
1329
+
1330
+ #### Production
1331
+ For production deployments:
1332
+
1333
+ **Milvus** (Open source)
1334
+ - Self-hosted vector database
1335
+ - Scalable to millions of vectors
1336
+ - Full-text + vector search combined
1337
+
1338
+ **Pinecone** (Managed service)
1339
+ - Serverless vector database
1340
+ - Easy to set up
1341
+ - No infrastructure to manage
1342
+
1343
+ **Weaviate** (Open source + Cloud)
1344
+ - GraphQL interface
1345
+ - Hybrid search (text + vectors)
1346
+ - Multiple deployment options
1347
+
1348
+ ## Migration Between Runtimes
1349
+
1350
+ You can migrate between runtimes by:
1351
+
1352
+ 1. Export data from current runtime
1353
+ 2. Update \`feltdb.config.json\` with new runtime
1354
+ 3. Import data to new runtime
1355
+
1356
+ Example:
1357
+ \`\`\`bash
1358
+ # Export from browser
1359
+ npm run feltdb:export
1360
+
1361
+ # Update config
1362
+ # nano feltdb.config.json
1363
+
1364
+ # Start new runtime
1365
+ npm run dev
1366
+
1367
+ # Import data
1368
+ npm run feltdb:import
1369
+ \`\`\`
1370
+
1371
+ ## Environment Variables by Runtime
1372
+
1373
+ ### Browser
1374
+ - No special variables needed
1375
+ - Optional: \`VITE_DEBUG=1\` for debugging
1376
+
1377
+ ### Node.js
1378
+ - \`DATABASE_URL\`: Connection string for persistent database
1379
+ - \`PORT\`: Server port (default: 3000)
1380
+ - \`NODE_ENV\`: development|production
1381
+
1382
+ ### Self-Hosted
1383
+ - \`VITE_FELTDB_URL\`: Docker container URL (default: http://localhost:7700)
1384
+ - \`VITE_FELTDB_API_KEY\`: API token for authentication
1385
+ - \`FELTDB_IMAGE\`: Docker image to use (optional override)
1386
+ - \`DOCKER_NETWORK\`: Custom Docker network (optional)
1387
+
1388
+ ## Troubleshooting
1389
+
1390
+ ### "Runtime not configured" Error
1391
+ Update \`feltdb.config.json\` with a valid runtime selection.
1392
+
1393
+ ### "Cannot connect to self-hosted server"
1394
+ Check that Docker is running:
1395
+ \`\`\`bash
1396
+ docker ps | grep feltdb
1397
+ \`\`\`
1398
+
1399
+ If not running, restart:
1400
+ \`\`\`bash
1401
+ npm run dev
1402
+ \`\`\`
1403
+
1404
+ ### Browser Storage Full
1405
+ OPFS limit reached. Clear unused data or migrate to self-hosted.
1406
+
1407
+ ### Vector Search Not Working
1408
+ Verify vector database connection and that embeddings are enabled in capabilities.
1409
+
1410
+ ## Performance Considerations
1411
+
1412
+ | Metric | Browser | Node.js | Self-Hosted |
1413
+ |--------|---------|---------|-------------|
1414
+ | Latency | Immediate | Low | Low |
1415
+ | Throughput | ~1000 ops/sec | ~10K ops/sec | ~100K ops/sec |
1416
+ | Data Limit | 50GB+ | Disk dependent | Unlimited |
1417
+ | Users | 1 | Few | Many |
1418
+ | Cost | Free | Hosting cost | Hosting cost |
1419
+
1420
+ ## Next Steps
1421
+
1422
+ - Read the [FeltDB Documentation](https://github.com/rkendel1/feltdb)
1423
+ - Check out example projects in the templates
1424
+ - Join the community Discord for support
1425
+ `;
1426
+ fs.writeFileSync(path.join(projectDir, 'RUNTIME_GUIDE.md'), runtimeGuide);
547
1427
  // Create .gitignore
548
1428
  const gitignore = `node_modules/
549
1429
  dist/
@@ -560,7 +1440,14 @@ build/
560
1440
  // Create README
561
1441
  const readme = `# ${applicationName}
562
1442
 
563
- A FeltDB distributed application with agents and capabilities.
1443
+ A FeltDB Workspace application demonstrating local-first database capabilities.
1444
+
1445
+ **Deployment Target:** \`${runtime}\`
1446
+ - **Runtime:** ${runtime === 'browser' ? 'Browser (IndexedDB)' : runtime === 'node' ? 'Node.js Server' : runtime === 'managed' ? 'Managed FeltDB' : 'Self-hosted Docker'}
1447
+ - **Framework:** ${framework}
1448
+ - **Distributed:** ${distributed ? 'Yes' : 'No'}
1449
+ - **Agents:** ${hasAgents ? 'Yes' : 'No'}
1450
+ - **Capabilities:** ${capabilities}
564
1451
 
565
1452
  ## Quick Start
566
1453
 
@@ -569,37 +1456,123 @@ npm install
569
1456
  npm run dev
570
1457
  \`\`\`
571
1458
 
1459
+ The application will be available at http://localhost:5173 (or your configured port).
1460
+
1461
+ ## FeltDB Workspace Showcase
1462
+
1463
+ This application demonstrates core FeltDB capabilities:
1464
+
1465
+ ✅ **Collections & Relationships** - Projects, Tasks, and Activity collections with foreign key relationships
1466
+ ✅ **Indexed Querying** - Efficient queries by projectId, status, priority, and timestamp
1467
+ ✅ **Activity/Audit Logs** - Append-only event collection demonstrating immutable history
1468
+ ✅ **Persistent Storage** - Data persists through page reloads (IndexedDB)
1469
+ ✅ **Reactive State** - Real-time updates using React hooks
1470
+ ✅ **Local-first** - Works offline with no server required
1471
+ ✅ **Data Inspector** - Built-in developer panel to inspect collections and indexes
1472
+
1473
+ ### Application Features
1474
+
1475
+ - **Dashboard** - Overview of projects, tasks, and runtime status
1476
+ - **Projects** - Create and manage projects with descriptions
1477
+ - **Tasks** - Create tasks within projects with priority and status
1478
+ - **Activity Log** - Append-only event history of all changes
1479
+ - **Search** - Indexed searching across task titles and descriptions
1480
+ - **Data Inspector** - Developer panel showing collections, indexes, and storage info
1481
+
572
1482
  ## Project Structure
573
1483
 
574
1484
  \`\`\`
575
1485
  ${applicationName}/
576
- ├── feltdb/
1486
+ ├── feltdb/ # FeltDB configuration
577
1487
  │ ├── agents/ # Agent definitions
578
1488
  │ ├── capabilities/ # Capability implementations
579
1489
  │ ├── workflows/ # Workflow definitions
580
1490
  │ └── schema/ # Data schemas
581
- ├── src/
582
- │ ├── App.${framework === 'react' ? 'tsx' : 'js'}
583
- │ ├── feltdb.ts
584
- │ └── index.${framework === 'react' ? 'tsx' : 'js'}
585
- ├── public/
1491
+ ├── src/ # Application source code
1492
+ │ ├── App.${framework === 'react' ? 'tsx' : 'js'} # Main workspace UI
1493
+ │ ├── feltdb.ts # FeltDB collections and operations
1494
+ │ └── index.${framework === 'react' ? 'tsx' : 'js'} # Entry point
1495
+ ├── public/ # Static assets
586
1496
  ├── index.html
587
1497
  ├── .feltdb/ # Local FeltDB configuration
588
- ├── feltdb.config.json
589
- ├── .env.example
1498
+ ├── feltdb.config.json # FeltDB configuration
1499
+ ├── .env.example # Environment variables template
590
1500
  ├── package.json
591
1501
  ├── tsconfig.json
592
1502
  └── README.md
593
1503
  \`\`\`
594
1504
 
1505
+ ## FeltDB Runtime
1506
+
1507
+ This project was created with the **${runtime === 'browser' ? 'Browser' : runtime === 'node' ? 'Node.js Server' : runtime === 'managed' ? 'Managed' : 'Self-hosted'}** runtime.
1508
+
1509
+ ### Browser Runtime
1510
+
1511
+ FeltDB runs locally in the browser and persists through IndexedDB.
1512
+
1513
+ - ✅ Zero server/database infrastructure
1514
+ - ✅ Works completely offline
1515
+ - ✅ Data stays on your device (privacy-first)
1516
+ - ✅ Best for: Client-side apps, offline-first experiences, prototypes
1517
+
1518
+ **Data Persistence:** IndexedDB (OPFS fallback)
1519
+
1520
+ ### Node.js Runtime
1521
+
1522
+ FeltDB runs as a Node.js server with server-side authority.
1523
+
1524
+ - ✅ Server-side FeltDB authority
1525
+ - ✅ Persistent file-based storage
1526
+ - ✅ Multi-client capable
1527
+ - ✅ Best for: Server-side applications, APIs, backend services
1528
+
1529
+ **Data Persistence:** File-based storage
1530
+
1531
+ ### Self-hosted Runtime
1532
+
1533
+ FeltDB runs through a dedicated server with Docker Compose and persistent data volume.
1534
+
1535
+ - ✅ Production-ready deployment
1536
+ - ✅ Multi-node replication
1537
+ - ✅ Health checks and orchestration
1538
+ - ✅ Best for: Production deployments, multi-user systems
1539
+
1540
+ **Data Persistence:** Docker volume (persistent /data)
1541
+
1542
+ ### Managed Runtime
1543
+
1544
+ FeltDB provides the runtime endpoint, durable storage, synchronization, and background workload infrastructure. The CLI configures \`VITE_FELTDB_MANAGED_URL\` and \`VITE_FELTDB_MANAGED_API_KEY\` in \`.env.local\`.
1545
+
595
1546
  ## Configuration
596
1547
 
597
1548
  Configuration is in \`feltdb.config.json\`:
598
- - \`runtime\`: ${runtime} (browser|node|self-hosted)
599
- - \`storage\`: ${runtime === 'browser' ? 'opfs' : 'durable'}
600
- - \`distributed\`: ${distributed}
601
- - \`agents.enabled\`: ${hasAgents}
602
- - \`capabilities\`: ${capabilities}
1549
+
1550
+ \`\`\`json
1551
+ {
1552
+ "namespace": "${applicationName}",
1553
+ "runtime": "${runtime}",
1554
+ "storage": "${runtime === 'browser' ? 'indexeddb' : runtime === 'managed' ? 'managed' : 'durable'}",
1555
+ "distributed": ${distributed},
1556
+ "agents": {
1557
+ "enabled": ${hasAgents}
1558
+ },
1559
+ "capabilities": ["${capabilities}"]
1560
+ }
1561
+ \`\`\`
1562
+
1563
+ ### Environment Variables
1564
+
1565
+ Create a \`.env.local\` file (copy from \`.env.example\`):
1566
+
1567
+ \`\`\`
1568
+ VITE_FELTDB_API_KEY=your_api_key_here
1569
+ VITE_FELTDB_URL=http://localhost:7700
1570
+ VITE_FELTDB_MANAGED_API_KEY=your_managed_api_key_here
1571
+ VITE_FELTDB_MANAGED_URL=https://api.feltdb.com
1572
+ VITE_FELTDB_WEBSOCKET_URL=ws://localhost:7700
1573
+ \`\`\`
1574
+
1575
+ These are required when connecting to an authenticated self-hosted or managed FeltDB instance.
603
1576
 
604
1577
  ## Development
605
1578
 
@@ -613,29 +1586,105 @@ npm run dev
613
1586
  npm run build
614
1587
  \`\`\`
615
1588
 
616
- ### Connect to Remote Server
1589
+ ### Validate FeltDB Configuration
617
1590
  \`\`\`bash
618
- feltdb connect http://localhost:7700
1591
+ npm run feltdb:validate
619
1592
  \`\`\`
620
1593
 
621
- ### Create API Key
1594
+ ### View FeltDB Status
622
1595
  \`\`\`bash
623
- feltdb keys create --name development --scope '*'
1596
+ npm run feltdb:status
624
1597
  \`\`\`
625
1598
 
1599
+ ### Open FeltDB Studio
1600
+ \`\`\`bash
1601
+ npm run feltdb:studio
1602
+ \`\`\`
1603
+
1604
+ ## Database Schema
1605
+
1606
+ ### Collections
1607
+
1608
+ #### projects
1609
+ - **id** (string): Unique project identifier
1610
+ - **name** (string): Project name
1611
+ - **description** (string): Project description
1612
+ - **status** (string): 'active' | 'archived' | 'completed'
1613
+ - **createdAt** (string): ISO timestamp
1614
+ - **updatedAt** (string): ISO timestamp
1615
+ - **metadata** (object, optional): Custom metadata
1616
+
1617
+ **Indexes:** status
1618
+
1619
+ #### tasks
1620
+ - **id** (string): Unique task identifier
1621
+ - **projectId** (string): Foreign key to projects
1622
+ - **title** (string): Task title
1623
+ - **description** (string): Task description
1624
+ - **status** (string): 'todo' | 'in-progress' | 'completed'
1625
+ - **priority** (string): 'low' | 'medium' | 'high'
1626
+ - **assignee** (string, optional): Assigned team member
1627
+ - **createdAt** (string): ISO timestamp
1628
+ - **updatedAt** (string): ISO timestamp
1629
+
1630
+ **Indexes:** projectId, status, priority
1631
+
1632
+ #### activity
1633
+ - **id** (string): Unique event identifier
1634
+ - **timestamp** (string): ISO timestamp
1635
+ - **type** (string): Event type
1636
+ - **entityType** (string): 'project' | 'task'
1637
+ - **entityId** (string): Reference to entity
1638
+ - **entityName** (string): Name of entity
1639
+ - **changes** (object, optional): Changed fields
1640
+ - **userId** (string, optional): User who made change
1641
+
1642
+ **Indexes:** timestamp, (entityType, entityId)
1643
+
626
1644
  ## Agents
627
1645
 
628
- ${hasAgents ? `The \`researcher\` agent runs real private inference with
629
- \`@feltdb/webllm\`. The model downloads on first use, runs in a Web Worker, and
630
- caches its artifacts in the browser. Generated reports are stored in FeltDB.` : 'No agents configured'}
1646
+ ${hasAgents ? `### Available Agents
1647
+ The application includes autonomous agents powered by \`@feltdb/webllm\`:
1648
+ - Runs entirely in the browser (private, no external APIs)
1649
+ - Model downloads on first use
1650
+ - Inference runs in a Web Worker
1651
+ - Learn more: https://github.com/mlc-ai/web-llm` : 'No agents configured. To enable agents, re-run create-feltdb or add them to feltdb.config.json.'}
1652
+
1653
+ ## Troubleshooting
631
1654
 
632
- ## Capabilities
1655
+ ### Data not persisting
1656
+ 1. Check browser console for errors
1657
+ 2. Verify IndexedDB is enabled (not in private mode)
1658
+ 3. Check browser storage quota
1659
+ 4. Clear browser cache and try again
633
1660
 
634
- ${capabilities}
1661
+ ### Slow performance
1662
+ 1. Check browser DevTools Performance tab
1663
+ 2. Verify indexes are being used (check Data Inspector)
1664
+ 3. Consider optimizing queries or collection size
1665
+
1666
+ ### Self-Hosted Connection Issues
1667
+ If using self-hosted mode and connection fails:
1668
+ 1. Ensure Docker is installed and running
1669
+ 2. Check VITE_FELTDB_URL and API key in .env.local
1670
+ 3. Run \`npm run feltdb:status\` to check server health
1671
+ 4. View Docker logs: \`docker logs feltdb\`
1672
+
1673
+ ### Port conflicts
1674
+ If port 5173 is in use:
1675
+ 1. Change the port in \`vite.config.ts\`
1676
+ 2. Or: \`npm run dev -- --port 3000\`
635
1677
 
636
1678
  ## Learn More
637
1679
 
638
- Visit [FeltDB Documentation](https://github.com/rkendel1/feltdb) to learn more.
1680
+ - [FeltDB Documentation](https://github.com/rkendel1/feltdb)
1681
+ - [FeltDB Architecture](https://github.com/rkendel1/feltdb/blob/main/ARCHITECTURE.md)
1682
+ - [WebLLM (In-browser LLM)](https://github.com/mlc-ai/web-llm)
1683
+ - [Getting Started Guide](https://github.com/rkendel1/feltdb/blob/main/GETTING_STARTED.md)
1684
+
1685
+ ## License
1686
+
1687
+ MIT
639
1688
  `;
640
1689
  fs.writeFileSync(path.join(projectDir, 'README.md'), readme);
641
1690
  }