@feltdb/core 0.4.5 → 0.4.7

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.
Files changed (38) hide show
  1. package/README.md +9 -0
  2. package/bin/create-feltdb.js +3 -0
  3. package/bin/feltdb.js +3 -0
  4. package/dist/analytics-backend.js +1 -1
  5. package/dist/cli/api-client.js +236 -0
  6. package/dist/cli/cli.js +18 -0
  7. package/dist/cli/commands.js +795 -0
  8. package/dist/cli/config.js +66 -0
  9. package/dist/cli/index.js +399 -0
  10. package/dist/create/application-identity.js +132 -0
  11. package/dist/create/cli-scripts-generator.js +211 -0
  12. package/dist/create/cli.js +251 -0
  13. package/dist/create/create.js +1862 -0
  14. package/dist/create/docker-compose-generator.js +258 -0
  15. package/dist/create/index.js +4 -0
  16. package/dist/create/package-versions.js +4 -0
  17. package/dist/create/runtime-templates.js +272 -0
  18. package/dist/index-backend.js +1 -1
  19. package/dist/react/useFeltDB.d.ts +1 -1
  20. package/dist/react/useFeltDB.js +1 -1
  21. package/dist/studio/KeyManagementPanel-B0s0xAXz.js +298 -0
  22. package/dist/studio/components/KeyManagementPanel.d.ts.map +1 -1
  23. package/dist/studio/components/KeyManagementPanel.js +1 -1
  24. package/dist/studio/components/index.js +2 -2
  25. package/dist/studio/{components-BRYUceo9.js → components-BAycgZhP.js} +1 -1
  26. package/dist/studio/index.js +2 -2
  27. package/dist/studio-app/assets/{feltdb_wasm-BXMn9UxO.js → feltdb_wasm-Bb1Pg6qz.js} +1 -1
  28. package/dist/studio-app/assets/feltdb_wasm_bg-2_wVudcZ.wasm +0 -0
  29. package/dist/studio-app/assets/{index-B-lZjdtI.js → index-DKVLtS37.js} +2 -2
  30. package/dist/studio-app/index.html +1 -1
  31. package/dist/wasm/feltdb_wasm.d.ts +461 -0
  32. package/dist/wasm/feltdb_wasm.js +1690 -0
  33. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  34. package/dist/wasm/feltdb_wasm_bg.wasm.d.ts +84 -0
  35. package/dist/wasm/package.json +16 -0
  36. package/package.json +11 -5
  37. package/dist/studio/KeyManagementPanel-BOvWTRyH.js +0 -246
  38. package/dist/studio-app/assets/feltdb_wasm_bg-bYYbZeRM.wasm +0 -0
@@ -0,0 +1,1862 @@
1
+ /**
2
+ * Project creation logic
3
+ */
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { feltdbPackageRange } from './package-versions.js';
7
+ export async function createProject(options) {
8
+ const { projectName, templatesDir } = options;
9
+ const projectDir = path.resolve(process.cwd(), projectName);
10
+ const applicationName = path.basename(projectDir);
11
+ // Create project directory
12
+ if (!fs.existsSync(projectDir)) {
13
+ fs.mkdirSync(projectDir, { recursive: true });
14
+ }
15
+ // Create feltdb directory structure
16
+ const feltdbDir = path.join(projectDir, 'feltdb');
17
+ const srcDir = path.join(projectDir, 'src');
18
+ const publicDir = path.join(projectDir, 'public');
19
+ const feltdbConfigDir = path.join(projectDir, '.feltdb');
20
+ fs.mkdirSync(path.join(feltdbDir, 'capabilities'), { recursive: true });
21
+ fs.mkdirSync(path.join(feltdbDir, 'workflows'), { recursive: true });
22
+ fs.mkdirSync(path.join(feltdbDir, 'agents'), { recursive: true });
23
+ fs.mkdirSync(path.join(feltdbDir, 'schema'), { recursive: true });
24
+ fs.mkdirSync(srcDir, { recursive: true });
25
+ fs.mkdirSync(publicDir, { recursive: true });
26
+ fs.mkdirSync(feltdbConfigDir, { recursive: true });
27
+ const runtime = options.runtime || 'browser';
28
+ const framework = options.framework || 'react';
29
+ const distributed = options.distributed !== false;
30
+ const hasAgents = options.agents !== false;
31
+ const capabilities = options.capabilities || 'search';
32
+ // Create package.json
33
+ const packageJson = {
34
+ name: applicationName,
35
+ version: '0.1.0',
36
+ description: `A FeltDB application`,
37
+ main: 'src/index.ts',
38
+ type: 'module',
39
+ scripts: {
40
+ dev: 'feltdb dev',
41
+ build: 'feltdb build',
42
+ test: 'node --test',
43
+ feltdb: 'feltdb',
44
+ 'feltdb:server': 'feltdb server',
45
+ 'feltdb:connect': 'feltdb connect http://localhost:7700',
46
+ 'feltdb:status': 'feltdb status',
47
+ 'feltdb:studio': 'feltdb studio',
48
+ 'feltdb:validate': 'feltdb validate',
49
+ 'feltdb:diff': 'feltdb diff',
50
+ 'feltdb:deploy': 'feltdb deploy',
51
+ },
52
+ dependencies: {
53
+ '@feltdb/core': feltdbPackageRange,
54
+ },
55
+ devDependencies: {
56
+ typescript: '^5.0.0',
57
+ '@types/node': '^20.0.0',
58
+ vite: '^8.2.1',
59
+ },
60
+ };
61
+ if (framework === 'react') {
62
+ packageJson.dependencies['react'] = '^18.0.0';
63
+ packageJson.dependencies['react-dom'] = '^18.0.0';
64
+ packageJson.devDependencies['@types/react'] = '^18.0.0';
65
+ packageJson.devDependencies['@types/react-dom'] = '^18.0.0';
66
+ }
67
+ if (hasAgents) {
68
+ packageJson.dependencies['@feltdb/webllm'] = feltdbPackageRange;
69
+ }
70
+ fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify(packageJson, null, 2));
71
+ // Create feltdb.config.json
72
+ const feltdbConfig = {
73
+ namespace: applicationName,
74
+ runtime,
75
+ storage: runtime === 'browser' ? 'opfs' : 'durable',
76
+ distributed,
77
+ agents: {
78
+ enabled: hasAgents,
79
+ },
80
+ capabilities: {},
81
+ };
82
+ if (capabilities.includes('search')) {
83
+ feltdbConfig.capabilities.search = true;
84
+ }
85
+ if (capabilities.includes('vector')) {
86
+ feltdbConfig.capabilities['vector-search'] = true;
87
+ }
88
+ fs.writeFileSync(path.join(projectDir, 'feltdb.config.json'), JSON.stringify(feltdbConfig, null, 2));
89
+ const appName = applicationName.replace(/[^A-Za-z0-9_]/g, '_').replace(/^[^A-Za-z_]/, 'App_');
90
+ const flowSpec = `app ${appName} {
91
+ collection Document {
92
+ title: text
93
+ content: text
94
+ createdAt: datetime
95
+ index search using fulltext(content)
96
+ }
97
+
98
+ collection Report {
99
+ title: text
100
+ content: text
101
+ document: ref Document
102
+ }
103
+
104
+ capability Research {
105
+ read Document
106
+ write Report
107
+ }
108
+
109
+ agent Researcher {
110
+ capability Research
111
+ workflow ResearchDocument
112
+ }
113
+
114
+ workflow ResearchDocument(document: Document) {
115
+ step search {
116
+ input document.content
117
+ }
118
+ step identity {
119
+ input search.output
120
+ }
121
+ }
122
+
123
+ trigger on Document.created {
124
+ workflow ResearchDocument(document)
125
+ }
126
+
127
+ policy Document {
128
+ read: authenticated
129
+ write: authenticated
130
+ }
131
+ }
132
+ `;
133
+ fs.writeFileSync(path.join(projectDir, 'feltdb.flow'), flowSpec);
134
+ // Create tsconfig.json
135
+ const tsconfig = {
136
+ compilerOptions: {
137
+ target: 'ES2020',
138
+ module: 'ESNext',
139
+ lib: ['ES2020', 'DOM'],
140
+ declaration: true,
141
+ outDir: './dist',
142
+ rootDir: './src',
143
+ strict: true,
144
+ esModuleInterop: true,
145
+ skipLibCheck: true,
146
+ forceConsistentCasingInFileNames: true,
147
+ moduleResolution: 'node',
148
+ },
149
+ include: ['src/**/*'],
150
+ exclude: ['node_modules'],
151
+ };
152
+ if (framework === 'react') {
153
+ tsconfig.compilerOptions.jsx = 'react-jsx';
154
+ }
155
+ fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
156
+ // Create main application files
157
+ const runtimeOptions = runtime === 'browser'
158
+ ? "{ namespace: '" + applicationName + "', browser: true }"
159
+ : runtime === 'self-hosted'
160
+ ? "{ namespace: '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_URL || 'http://localhost:7700', token: import.meta.env.VITE_FELTDB_API_KEY || '' } }"
161
+ : "{ namespace: '" + applicationName + "', memory: true }";
162
+ const feltdbTs = `import { createFeltDB } from '@feltdb/core';
163
+
164
+ export const db = createFeltDB(${runtimeOptions});
165
+
166
+ // Collections
167
+ export const documents = db.collection('documents');
168
+ export const reports = db.collection('reports');
169
+ `;
170
+ fs.writeFileSync(path.join(srcDir, 'feltdb.ts'), feltdbTs);
171
+ // Create a real local-inference agent for browser projects.
172
+ if (hasAgents) {
173
+ const agentTs = `import { WebLLMProvider } from '@feltdb/webllm';
174
+ import { db, reports } from '../../src/feltdb';
175
+
176
+ /**
177
+ * Researcher metadata is registered in FeltDB; inference runs privately in
178
+ * the browser through WebLLM and its worker.
179
+ */
180
+ export const researcher = db.defineAgent({
181
+ name: 'researcher',
182
+ version: 1,
183
+ description: 'Private, on-device AI assistant for document enhancement and analysis',
184
+ capabilities: [
185
+ 'document-read',
186
+ 'report-write'
187
+ ],
188
+ goals: [
189
+ 'Analyze documents privately without sending data off-device',
190
+ 'Generate insights and enhancements locally',
191
+ 'Support document summarization, expansion, and improvement'
192
+ ],
193
+ constraints: { maxIterations: 1 },
194
+ });
195
+
196
+ let provider: WebLLMProvider | undefined;
197
+
198
+ export async function runResearcher(
199
+ prompt: string,
200
+ onProgress?: (message: string) => void,
201
+ ) {
202
+ if (!provider) {
203
+ provider = new WebLLMProvider({
204
+ onProgress: ({ progress, text }) => {
205
+ const pct = Math.round(progress * 100);
206
+ onProgress?.(\`\${pct}% - \${text}\`);
207
+ },
208
+ });
209
+ }
210
+
211
+ try {
212
+ const systemPrompt = \`You are a helpful AI assistant that operates entirely in the browser.
213
+ You help users:
214
+ - Summarize documents concisely
215
+ - Expand and enhance text with more details
216
+ - Generate ideas and suggestions
217
+ - Improve writing clarity and structure
218
+ - Answer questions about document content
219
+
220
+ Always be concise and practical. Respect the user's intent.\`;
221
+
222
+ const content = await provider.generate([
223
+ { role: 'system', content: systemPrompt },
224
+ { role: 'user', content: prompt },
225
+ ]);
226
+
227
+ await reports.insert({
228
+ id: 'report_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
229
+ title: prompt.slice(0, 80) || 'AI Research Result',
230
+ content,
231
+ createdAt: new Date().toISOString(),
232
+ });
233
+
234
+ return content;
235
+ } catch (error) {
236
+ throw new Error(error instanceof Error ? error.message : 'Failed to generate response');
237
+ }
238
+ }
239
+
240
+ export async function summarizeDocument(
241
+ title: string,
242
+ content: string,
243
+ onProgress?: (message: string) => void,
244
+ ): Promise<string> {
245
+ const prompt = \`Summarize this document in 2-3 concise sentences:
246
+
247
+ Title: \${title}
248
+
249
+ Content: \${content.substring(0, 2000)}\`;
250
+
251
+ return runResearcher(prompt, onProgress);
252
+ }
253
+
254
+ export async function enhanceDocument(
255
+ title: string,
256
+ content: string,
257
+ onProgress?: (message: string) => void,
258
+ ): Promise<string> {
259
+ const prompt = \`Enhance and improve this document by making it more detailed and clearer:
260
+
261
+ Title: \${title}
262
+
263
+ Content: \${content}
264
+
265
+ Provide the enhanced version:\`;
266
+
267
+ return runResearcher(prompt, onProgress);
268
+ }
269
+
270
+ export async function generateIdeas(
271
+ topic: string,
272
+ onProgress?: (message: string) => void,
273
+ ): Promise<string> {
274
+ const prompt = \`Generate 5 creative ideas related to: \${topic}
275
+
276
+ Format as a numbered list with brief explanations.\`;
277
+
278
+ return runResearcher(prompt, onProgress);
279
+ }
280
+
281
+ export async function generateFeltDBFeature(
282
+ featureDescription: string,
283
+ onProgress?: (message: string) => void,
284
+ ): Promise<string> {
285
+ const prompt = \`Generate FeltDB flow syntax for: \${featureDescription}
286
+
287
+ Include:
288
+ 1. Collection definitions with proper types
289
+ 2. Capability definitions
290
+ 3. Workflow definitions if needed
291
+ 4. Policy rules for access control
292
+
293
+ Format the output as valid FeltDB flow language.\`;
294
+
295
+ return runResearcher(prompt, onProgress);
296
+ }
297
+
298
+ export async function generateReactComponent(
299
+ componentDescription: string,
300
+ onProgress?: (message: string) => void,
301
+ ): Promise<string> {
302
+ const prompt = \`Generate a React component for: \${componentDescription}
303
+
304
+ Include:
305
+ 1. TypeScript types/interfaces
306
+ 2. useState and useEffect hooks
307
+ 3. Error handling
308
+ 4. Loading states
309
+ 5. Proper styling with CSS classes
310
+
311
+ Export a functional component that can be imported and used.\`;
312
+
313
+ return runResearcher(prompt, onProgress);
314
+ }
315
+
316
+ export async function generateCapability(
317
+ capabilityName: string,
318
+ description: string,
319
+ onProgress?: (message: string) => void,
320
+ ): Promise<string> {
321
+ const prompt = \`Generate a FeltDB capability definition for: \${capabilityName}
322
+
323
+ Description: \${description}
324
+
325
+ Include:
326
+ 1. TypeScript interface definitions
327
+ 2. Implementation with proper types
328
+ 3. Error handling
329
+ 4. Documentation comments
330
+
331
+ Make it production-ready.\`;
332
+
333
+ return runResearcher(prompt, onProgress);
334
+ }
335
+ `;
336
+ fs.writeFileSync(path.join(feltdbDir, 'agents', 'researcher.ts'), agentTs);
337
+ }
338
+ // Create capabilities index
339
+ const capabilitiesTs = `/**
340
+ * Capabilities
341
+ *
342
+ * Define capabilities that can be executed by agents
343
+ * across distributed peers
344
+ */
345
+
346
+ export const capabilities = {
347
+ 'document-read': {
348
+ enabled: true,
349
+ scope: ['documents:read'],
350
+ },
351
+ 'vector-search': {
352
+ enabled: ${capabilities.includes('vector')},
353
+ scope: ['documents:read', 'capabilities:execute'],
354
+ },
355
+ 'report-write': {
356
+ enabled: true,
357
+ scope: ['reports:write'],
358
+ },
359
+ };
360
+ `;
361
+ fs.writeFileSync(path.join(feltdbDir, 'capabilities', 'index.ts'), capabilitiesTs);
362
+ // Create main application file based on framework
363
+ if (framework === 'react') {
364
+ const agentImport = hasAgents
365
+ ? "import { runResearcher, summarizeDocument, enhanceDocument, generateIdeas, generateFeltDBFeature, generateReactComponent, generateCapability } from '../feltdb/agents/researcher';"
366
+ : '';
367
+ const agentState = hasAgents
368
+ ? ` const [prompt, setPrompt] = useState('');
369
+ const [answer, setAnswer] = useState('');
370
+ const [modelStatus, setModelStatus] = useState('Model not loaded');
371
+ const [generating, setGenerating] = useState(false);
372
+ const [selectedDoc, setSelectedDoc] = useState<any | null>(null);
373
+ const [aiMode, setAiMode] = useState<'custom' | 'summarize' | 'enhance' | 'ideas' | 'feltdb' | 'react' | 'capability'>('custom');`
374
+ : '';
375
+ const agentHandler = hasAgents
376
+ ? `
377
+ const handleRunAI = async (mode: 'custom' | 'summarize' | 'enhance' | 'ideas' | 'feltdb' | 'react' | 'capability') => {
378
+ setGenerating(true);
379
+ setAnswer('');
380
+ setModelStatus('Running inference...');
381
+
382
+ try {
383
+ let result = '';
384
+ if (mode === 'custom') {
385
+ result = await runResearcher(prompt, setModelStatus);
386
+ } else if (mode === 'summarize' && selectedDoc) {
387
+ result = await summarizeDocument(selectedDoc.title, selectedDoc.content, setModelStatus);
388
+ } else if (mode === 'enhance' && selectedDoc) {
389
+ result = await enhanceDocument(selectedDoc.title, selectedDoc.content, setModelStatus);
390
+ } else if (mode === 'ideas') {
391
+ result = await generateIdeas(prompt, setModelStatus);
392
+ } else if (mode === 'feltdb') {
393
+ result = await generateFeltDBFeature(prompt, setModelStatus);
394
+ } else if (mode === 'react') {
395
+ result = await generateReactComponent(prompt, setModelStatus);
396
+ } else if (mode === 'capability') {
397
+ result = await generateCapability('Feature', prompt, setModelStatus);
398
+ }
399
+ setAnswer(result);
400
+ setModelStatus('✓ Ready — all processing done locally');
401
+ } catch (error) {
402
+ setModelStatus('✗ Error: ' + (error instanceof Error ? error.message : String(error)));
403
+ } finally {
404
+ setGenerating(false);
405
+ }
406
+ };
407
+ `
408
+ : '';
409
+ const agentMarkup = hasAgents
410
+ ? `
411
+ <section className="researcher">
412
+ <h2>🤖 AI Assistant (Private, Local)</h2>
413
+ <div className="status-badge">{modelStatus}</div>
414
+
415
+ <div className="ai-modes">
416
+ <div className="mode-group">
417
+ <span className="mode-group-label">📝 Content</span>
418
+ <button
419
+ className={\`mode-btn \${aiMode === 'custom' ? 'active' : ''}\`}
420
+ onClick={() => setAiMode('custom')}
421
+ >
422
+ ✍️ Custom
423
+ </button>
424
+ <button
425
+ className={\`mode-btn \${aiMode === 'summarize' ? 'active' : ''}\`}
426
+ onClick={() => setAiMode('summarize')}
427
+ disabled={!selectedDoc}
428
+ >
429
+ 📄 Summarize
430
+ </button>
431
+ <button
432
+ className={\`mode-btn \${aiMode === 'enhance' ? 'active' : ''}\`}
433
+ onClick={() => setAiMode('enhance')}
434
+ disabled={!selectedDoc}
435
+ >
436
+ ✨ Enhance
437
+ </button>
438
+ <button
439
+ className={\`mode-btn \${aiMode === 'ideas' ? 'active' : ''}\`}
440
+ onClick={() => setAiMode('ideas')}
441
+ >
442
+ 💡 Ideas
443
+ </button>
444
+ </div>
445
+
446
+ <div className="mode-group">
447
+ <span className="mode-group-label">💻 Code Gen</span>
448
+ <button
449
+ className={\`mode-btn code-mode \${aiMode === 'feltdb' ? 'active' : ''}\`}
450
+ onClick={() => setAiMode('feltdb')}
451
+ >
452
+ ⚙️ FeltDB Flow
453
+ </button>
454
+ <button
455
+ className={\`mode-btn code-mode \${aiMode === 'react' ? 'active' : ''}\`}
456
+ onClick={() => setAiMode('react')}
457
+ >
458
+ ⚛️ React
459
+ </button>
460
+ <button
461
+ className={\`mode-btn code-mode \${aiMode === 'capability' ? 'active' : ''}\`}
462
+ onClick={() => setAiMode('capability')}
463
+ >
464
+ 🔧 Capability
465
+ </button>
466
+ </div>
467
+ </div>
468
+
469
+ {aiMode === 'summarize' || aiMode === 'enhance' ? (
470
+ <div className="doc-selector">
471
+ <label>Select a document:</label>
472
+ <select
473
+ value={selectedDoc?.id || ''}
474
+ onChange={(e) => {
475
+ const doc = docs.find((d: any) => d.id === e.target.value);
476
+ setSelectedDoc(doc || null);
477
+ }}
478
+ >
479
+ <option value="">Choose a document...</option>
480
+ {docs.map((doc: any) => (
481
+ <option key={doc.id} value={doc.id}>
482
+ {doc.title}
483
+ </option>
484
+ ))}
485
+ </select>
486
+ </div>
487
+ ) : (
488
+ <textarea
489
+ value={prompt}
490
+ onChange={(event) => setPrompt(event.target.value)}
491
+ placeholder={
492
+ aiMode === 'ideas' ? 'What topic do you want ideas for?' :
493
+ aiMode === 'feltdb' ? 'Describe the FeltDB feature you want to create...' :
494
+ aiMode === 'react' ? 'Describe the React component you need...' :
495
+ aiMode === 'capability' ? 'Describe the capability implementation...' :
496
+ 'Enter your prompt...'
497
+ }
498
+ rows={5}
499
+ />
500
+ )}
501
+
502
+ <button
503
+ onClick={() => handleRunAI(aiMode)}
504
+ disabled={
505
+ generating ||
506
+ (aiMode === 'custom' && !prompt.trim()) ||
507
+ ((aiMode === 'summarize' || aiMode === 'enhance') && !selectedDoc) ||
508
+ ((aiMode === 'ideas' || aiMode === 'feltdb' || aiMode === 'react' || aiMode === 'capability') && !prompt.trim())
509
+ }
510
+ className="research-btn"
511
+ >
512
+ {generating ? '⏳ Running locally…' : '🚀 Generate'}
513
+ </button>
514
+
515
+ {answer && (
516
+ <article className="research-result">
517
+ <h3>Generated Output</h3>
518
+ <div className="result-content">{answer}</div>
519
+ <div className="result-actions">
520
+ {aiMode === 'custom' || aiMode === 'enhance' || aiMode === 'ideas' ? (
521
+ <button
522
+ onClick={() => {
523
+ const title = aiMode === 'ideas' ? prompt : (selectedDoc?.title || 'AI Generated');
524
+ documents.insert({
525
+ id: 'doc_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
526
+ title: title + ' (AI Enhanced)',
527
+ content: answer,
528
+ createdAt: new Date().toISOString(),
529
+ });
530
+ loadDocs();
531
+ setAnswer('');
532
+ }}
533
+ className="save-result-btn"
534
+ >
535
+ 💾 Save to Documents
536
+ </button>
537
+ ) : (
538
+ <>
539
+ <button
540
+ onClick={() => {
541
+ const filename = aiMode === 'feltdb' ? 'feature.flow' :
542
+ aiMode === 'react' ? 'Component.tsx' :
543
+ 'capability.ts';
544
+ const blob = new Blob([answer], { type: 'text/plain' });
545
+ const url = URL.createObjectURL(blob);
546
+ const a = document.createElement('a');
547
+ a.href = url;
548
+ a.download = filename;
549
+ a.click();
550
+ URL.revokeObjectURL(url);
551
+ }}
552
+ className="save-result-btn"
553
+ >
554
+ 📥 Download Code
555
+ </button>
556
+ <button
557
+ onClick={() => {
558
+ navigator.clipboard.writeText(answer);
559
+ alert('Code copied to clipboard!');
560
+ }}
561
+ className="copy-result-btn"
562
+ >
563
+ 📋 Copy Code
564
+ </button>
565
+ </>
566
+ )}
567
+ </div>
568
+ <p className="code-note">
569
+ 💡 Tip: Generated code can be downloaded, copied, or saved to documents for later reference.
570
+ </p>
571
+ </article>
572
+ )}
573
+ </section>`
574
+ : '';
575
+ const appTsx = `import React, { useState, useEffect } from 'react';
576
+ import { db, documents } from './feltdb';
577
+ ${agentImport}
578
+
579
+ export function App() {
580
+ const [docs, setDocs] = useState<any[]>([]);
581
+ const [loading, setLoading] = useState(true);
582
+ const [error, setError] = useState<string | null>(null);
583
+ const [addingDoc, setAddingDoc] = useState(false);
584
+ const [searchTerm, setSearchTerm] = useState('');
585
+ const [editingId, setEditingId] = useState<string | null>(null);
586
+ const [editTitle, setEditTitle] = useState('');
587
+ const [editContent, setEditContent] = useState('');
588
+ const [deletingId, setDeletingId] = useState<string | null>(null);
589
+ ${agentState}
590
+
591
+ const loadDocs = async () => {
592
+ try {
593
+ setError(null);
594
+ const allDocs = await documents.find({});
595
+ setDocs(allDocs.sort((a: any, b: any) =>
596
+ new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
597
+ ));
598
+ } catch (err) {
599
+ const msg = err instanceof Error ? err.message : 'Failed to load documents';
600
+ console.error('Error loading documents:', err);
601
+ setError(msg);
602
+ } finally {
603
+ setLoading(false);
604
+ }
605
+ };
606
+
607
+ useEffect(() => {
608
+ loadDocs();
609
+ }, []);
610
+
611
+ const filteredDocs = docs.filter((doc: any) =>
612
+ doc.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
613
+ doc.content.toLowerCase().includes(searchTerm.toLowerCase())
614
+ );
615
+
616
+ const handleAddDocument = async () => {
617
+ if (addingDoc) return;
618
+ setAddingDoc(true);
619
+ setError(null);
620
+
621
+ try {
622
+ const newDoc = {
623
+ id: 'doc_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
624
+ title: 'New Document',
625
+ content: 'Enter your content here...',
626
+ createdAt: new Date().toISOString(),
627
+ };
628
+ await documents.insert(newDoc);
629
+ await loadDocs();
630
+ } catch (err) {
631
+ const msg = err instanceof Error ? err.message : 'Failed to add document';
632
+ console.error('Error adding document:', err);
633
+ setError(msg);
634
+ } finally {
635
+ setAddingDoc(false);
636
+ }
637
+ };
638
+
639
+ const handleEditStart = (doc: any) => {
640
+ setEditingId(doc.id);
641
+ setEditTitle(doc.title);
642
+ setEditContent(doc.content);
643
+ };
644
+
645
+ const handleEditSave = async () => {
646
+ if (!editingId) return;
647
+ setError(null);
648
+
649
+ try {
650
+ const doc = docs.find((d: any) => d.id === editingId);
651
+ if (doc) {
652
+ const updatedDoc = {
653
+ ...doc,
654
+ title: editTitle,
655
+ content: editContent,
656
+ updatedAt: new Date().toISOString(),
657
+ };
658
+ await documents.insert(updatedDoc);
659
+ setEditingId(null);
660
+ await loadDocs();
661
+ }
662
+ } catch (err) {
663
+ const msg = err instanceof Error ? err.message : 'Failed to save document';
664
+ console.error('Error saving document:', err);
665
+ setError(msg);
666
+ }
667
+ };
668
+
669
+ const handleDelete = async (docId: string) => {
670
+ if (deletingId) return;
671
+ setDeletingId(docId);
672
+ setError(null);
673
+
674
+ try {
675
+ const doc = docs.find((d: any) => d.id === docId);
676
+ if (doc) {
677
+ await documents.delete(doc);
678
+ await loadDocs();
679
+ }
680
+ } catch (err) {
681
+ const msg = err instanceof Error ? err.message : 'Failed to delete document';
682
+ setError(msg);
683
+ } finally {
684
+ setDeletingId(null);
685
+ }
686
+ };
687
+ ${agentHandler}
688
+
689
+ return (
690
+ <div className="app">
691
+ <header>
692
+ <h1>🌊 FeltDB Research App</h1>
693
+ <p>Distributed document management with agents and capabilities</p>
694
+ </header>
695
+
696
+ <main>
697
+ <section className="stats">
698
+ <div className="stat">
699
+ <span className="label">Documents:</span>
700
+ <span className="value">{docs.length}</span>
701
+ </div>
702
+ <div className="stat">
703
+ <span className="label">Runtime:</span>
704
+ <span className="value">${runtime}</span>
705
+ </div>
706
+ <div className="stat">
707
+ <span className="label">Distributed:</span>
708
+ <span className="value">${distributed ? '✓' : '✗'}</span>
709
+ </div>
710
+ </section>
711
+
712
+ {error && (
713
+ <section className="error-message">
714
+ <strong>⚠ Error:</strong> {error}
715
+ <button onClick={() => window.location.reload()} className="retry-btn">
716
+ Retry
717
+ </button>
718
+ </section>
719
+ )}
720
+
721
+ <section className="documents">
722
+ <div className="docs-header">
723
+ <h2>📚 Documents</h2>
724
+ <div className="docs-controls">
725
+ <input
726
+ type="text"
727
+ placeholder="🔍 Search documents..."
728
+ value={searchTerm}
729
+ onChange={(e) => setSearchTerm(e.target.value)}
730
+ className="search-input"
731
+ />
732
+ <button
733
+ onClick={handleAddDocument}
734
+ disabled={addingDoc || loading}
735
+ className="primary-btn"
736
+ >
737
+ {addingDoc ? '⏳ Adding...' : '➕ New Document'}
738
+ </button>
739
+ </div>
740
+ </div>
741
+
742
+ {loading ? (
743
+ <div className="loading">
744
+ <div className="spinner"></div>
745
+ <p>Loading documents...</p>
746
+ </div>
747
+ ) : docs.length === 0 ? (
748
+ <div className="empty-state">
749
+ <p>📭 No documents yet.</p>
750
+ <p className="hint">Create one to get started!</p>
751
+ </div>
752
+ ) : filteredDocs.length === 0 ? (
753
+ <div className="empty-state">
754
+ <p>🔍 No documents match "{searchTerm}"</p>
755
+ </div>
756
+ ) : (
757
+ <div className="docs-grid">
758
+ {filteredDocs.map((doc: any) => (
759
+ <div key={doc.id} className="doc-card">
760
+ {editingId === doc.id ? (
761
+ <div className="edit-mode">
762
+ <input
763
+ type="text"
764
+ value={editTitle}
765
+ onChange={(e) => setEditTitle(e.target.value)}
766
+ className="edit-title"
767
+ />
768
+ <textarea
769
+ value={editContent}
770
+ onChange={(e) => setEditContent(e.target.value)}
771
+ className="edit-content"
772
+ rows={6}
773
+ />
774
+ <div className="edit-actions">
775
+ <button onClick={handleEditSave} className="save-btn">
776
+ ✓ Save
777
+ </button>
778
+ <button onClick={() => setEditingId(null)} className="cancel-btn">
779
+ ✕ Cancel
780
+ </button>
781
+ </div>
782
+ </div>
783
+ ) : (
784
+ <>
785
+ <h3>{doc.title}</h3>
786
+ <p className="doc-preview">{doc.content.substring(0, 150)}...</p>
787
+ <div className="doc-meta">
788
+ <small>{new Date(doc.createdAt).toLocaleDateString()}</small>
789
+ <span className="doc-size">{doc.content.length} chars</span>
790
+ </div>
791
+ <div className="doc-actions">
792
+ <button
793
+ onClick={() => handleEditStart(doc)}
794
+ className="edit-btn"
795
+ >
796
+ ✎ Edit
797
+ </button>
798
+ <button
799
+ onClick={() => handleDelete(doc.id)}
800
+ disabled={deletingId === doc.id}
801
+ className="delete-btn"
802
+ >
803
+ {deletingId === doc.id ? '⏳ Deleting...' : '🗑 Delete'}
804
+ </button>
805
+ </div>
806
+ </>
807
+ )}
808
+ </div>
809
+ ))}
810
+ </div>
811
+ )}
812
+ </section>
813
+ ${agentMarkup}
814
+ </main>
815
+
816
+ <style>{\`
817
+ * { box-sizing: border-box; }
818
+
819
+ body {
820
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto;
821
+ margin: 0;
822
+ padding: 0;
823
+ background: #f5f7fa;
824
+ color: #333;
825
+ }
826
+
827
+ .app {
828
+ max-width: 1400px;
829
+ margin: 0 auto;
830
+ padding: 20px;
831
+ }
832
+
833
+ header {
834
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
835
+ color: white;
836
+ padding: 40px 30px;
837
+ border-radius: 12px;
838
+ margin-bottom: 30px;
839
+ box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3);
840
+ }
841
+
842
+ header h1 {
843
+ margin: 0 0 10px 0;
844
+ font-size: 32px;
845
+ font-weight: 700;
846
+ }
847
+
848
+ header p {
849
+ margin: 0;
850
+ opacity: 0.95;
851
+ font-size: 16px;
852
+ }
853
+
854
+ main {
855
+ background: white;
856
+ padding: 30px;
857
+ border-radius: 12px;
858
+ box-shadow: 0 2px 12px rgba(0,0,0,0.08);
859
+ }
860
+
861
+ .stats {
862
+ display: grid;
863
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
864
+ gap: 20px;
865
+ margin-bottom: 30px;
866
+ }
867
+
868
+ .stat {
869
+ padding: 24px;
870
+ background: linear-gradient(135deg, #f5f7fa 0%, #f9f9f9 100%);
871
+ border-radius: 8px;
872
+ border-left: 4px solid #667eea;
873
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
874
+ }
875
+
876
+ .stat .label {
877
+ display: block;
878
+ color: #999;
879
+ font-size: 10px;
880
+ text-transform: uppercase;
881
+ letter-spacing: 1px;
882
+ margin-bottom: 8px;
883
+ font-weight: 700;
884
+ }
885
+
886
+ .stat .value {
887
+ display: block;
888
+ font-size: 32px;
889
+ font-weight: 700;
890
+ color: #667eea;
891
+ }
892
+
893
+ .error-message {
894
+ padding: 16px 20px;
895
+ background: #fff5f5;
896
+ border: 1px solid #fca5a5;
897
+ border-left: 4px solid #f56565;
898
+ border-radius: 8px;
899
+ margin-bottom: 20px;
900
+ display: flex;
901
+ justify-content: space-between;
902
+ align-items: center;
903
+ color: #c53030;
904
+ }
905
+
906
+ .error-message strong { font-weight: 600; }
907
+
908
+ .retry-btn {
909
+ background: #f56565;
910
+ padding: 8px 16px;
911
+ font-size: 12px;
912
+ margin-left: 15px;
913
+ border-radius: 4px;
914
+ }
915
+
916
+ .retry-btn:hover { background: #e53e3e; }
917
+
918
+ .docs-header {
919
+ display: flex;
920
+ justify-content: space-between;
921
+ align-items: center;
922
+ margin-bottom: 24px;
923
+ gap: 20px;
924
+ }
925
+
926
+ .docs-header h2 {
927
+ margin: 0;
928
+ color: #1a202c;
929
+ font-size: 26px;
930
+ flex: 1;
931
+ }
932
+
933
+ .docs-controls {
934
+ display: flex;
935
+ gap: 12px;
936
+ flex: 1;
937
+ max-width: 600px;
938
+ }
939
+
940
+ .search-input {
941
+ flex: 1;
942
+ padding: 10px 16px;
943
+ border: 1px solid #e2e8f0;
944
+ border-radius: 6px;
945
+ font-size: 14px;
946
+ transition: all 0.2s ease;
947
+ }
948
+
949
+ .search-input:focus {
950
+ outline: none;
951
+ border-color: #667eea;
952
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
953
+ }
954
+
955
+ .loading {
956
+ text-align: center;
957
+ padding: 60px 20px;
958
+ color: #999;
959
+ }
960
+
961
+ .spinner {
962
+ display: inline-block;
963
+ width: 48px;
964
+ height: 48px;
965
+ border: 4px solid #e2e8f0;
966
+ border-top-color: #667eea;
967
+ border-radius: 50%;
968
+ animation: spin 0.8s linear infinite;
969
+ margin-bottom: 20px;
970
+ }
971
+
972
+ @keyframes spin { to { transform: rotate(360deg); } }
973
+
974
+ .empty-state {
975
+ text-align: center;
976
+ padding: 60px 20px;
977
+ color: #999;
978
+ }
979
+
980
+ .empty-state p { margin: 8px 0; font-size: 16px; }
981
+ .empty-state .hint { font-size: 14px; opacity: 0.8; }
982
+
983
+ .docs-grid {
984
+ display: grid;
985
+ grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
986
+ gap: 20px;
987
+ }
988
+
989
+ .doc-card {
990
+ background: #f9f9f9;
991
+ border: 1px solid #e2e8f0;
992
+ border-radius: 8px;
993
+ padding: 20px;
994
+ transition: all 0.3s ease;
995
+ display: flex;
996
+ flex-direction: column;
997
+ }
998
+
999
+ .doc-card:hover {
1000
+ border-color: #667eea;
1001
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
1002
+ transform: translateY(-2px);
1003
+ }
1004
+
1005
+ .doc-card h3 {
1006
+ margin: 0 0 12px 0;
1007
+ color: #1a202c;
1008
+ font-size: 18px;
1009
+ font-weight: 600;
1010
+ word-break: break-word;
1011
+ }
1012
+
1013
+ .doc-preview {
1014
+ flex: 1;
1015
+ margin: 0 0 12px 0;
1016
+ color: #666;
1017
+ font-size: 14px;
1018
+ line-height: 1.6;
1019
+ display: -webkit-box;
1020
+ -webkit-line-clamp: 3;
1021
+ -webkit-box-orient: vertical;
1022
+ overflow: hidden;
1023
+ }
1024
+
1025
+ .doc-meta {
1026
+ display: flex;
1027
+ justify-content: space-between;
1028
+ align-items: center;
1029
+ padding: 12px 0;
1030
+ border-top: 1px solid #e2e8f0;
1031
+ border-bottom: 1px solid #e2e8f0;
1032
+ margin-bottom: 12px;
1033
+ font-size: 12px;
1034
+ color: #999;
1035
+ }
1036
+
1037
+ .doc-size {
1038
+ background: #f0f4ff;
1039
+ padding: 2px 8px;
1040
+ border-radius: 4px;
1041
+ color: #667eea;
1042
+ font-weight: 600;
1043
+ }
1044
+
1045
+ .doc-actions {
1046
+ display: flex;
1047
+ gap: 8px;
1048
+ }
1049
+
1050
+ .edit-btn, .delete-btn, .save-btn, .cancel-btn {
1051
+ flex: 1;
1052
+ padding: 8px 12px;
1053
+ font-size: 13px;
1054
+ border: 1px solid;
1055
+ border-radius: 6px;
1056
+ cursor: pointer;
1057
+ font-weight: 600;
1058
+ transition: all 0.2s ease;
1059
+ }
1060
+
1061
+ .edit-btn {
1062
+ background: #eef2ff;
1063
+ border-color: #667eea;
1064
+ color: #667eea;
1065
+ }
1066
+
1067
+ .edit-btn:hover { background: #e0e7ff; }
1068
+
1069
+ .delete-btn {
1070
+ background: #fee;
1071
+ border-color: #fca5a5;
1072
+ color: #c53030;
1073
+ }
1074
+
1075
+ .delete-btn:hover:not(:disabled) { background: #fdd; }
1076
+ .delete-btn:disabled { opacity: 0.6; cursor: not-allowed; }
1077
+
1078
+ .edit-mode {
1079
+ display: flex;
1080
+ flex-direction: column;
1081
+ gap: 12px;
1082
+ }
1083
+
1084
+ .edit-title, .edit-content {
1085
+ padding: 10px 12px;
1086
+ border: 1px solid #ddd;
1087
+ border-radius: 6px;
1088
+ font-family: inherit;
1089
+ font-size: 14px;
1090
+ }
1091
+
1092
+ .edit-title {
1093
+ font-size: 16px;
1094
+ font-weight: 600;
1095
+ }
1096
+
1097
+ .edit-title:focus, .edit-content:focus {
1098
+ outline: none;
1099
+ border-color: #667eea;
1100
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
1101
+ }
1102
+
1103
+ .edit-actions {
1104
+ display: flex;
1105
+ gap: 8px;
1106
+ }
1107
+
1108
+ .save-btn {
1109
+ background: #667eea;
1110
+ color: white;
1111
+ border: none;
1112
+ flex: 1;
1113
+ }
1114
+
1115
+ .save-btn:hover { background: #5568d3; }
1116
+
1117
+ .cancel-btn {
1118
+ background: #f0f0f0;
1119
+ color: #666;
1120
+ border: none;
1121
+ flex: 1;
1122
+ }
1123
+
1124
+ .cancel-btn:hover { background: #e0e0e0; }
1125
+
1126
+ .primary-btn {
1127
+ background: #667eea;
1128
+ color: white;
1129
+ border: none;
1130
+ padding: 10px 20px;
1131
+ white-space: nowrap;
1132
+ }
1133
+
1134
+ .primary-btn:hover:not(:disabled) { background: #5568d3; }
1135
+ .primary-btn:disabled { opacity: 0.6; cursor: not-allowed; }
1136
+
1137
+ textarea, input {
1138
+ display: block;
1139
+ width: 100%;
1140
+ margin: 12px 0;
1141
+ padding: 12px;
1142
+ font-family: inherit;
1143
+ font-size: 14px;
1144
+ border: 1px solid #ddd;
1145
+ border-radius: 6px;
1146
+ resize: vertical;
1147
+ }
1148
+
1149
+ textarea:focus, input:focus {
1150
+ outline: none;
1151
+ border-color: #667eea;
1152
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
1153
+ }
1154
+
1155
+ .status-badge {
1156
+ display: inline-block;
1157
+ padding: 8px 12px;
1158
+ background: #f0f4ff;
1159
+ color: #667eea;
1160
+ border-radius: 6px;
1161
+ font-size: 12px;
1162
+ font-weight: 600;
1163
+ margin-bottom: 16px;
1164
+ }
1165
+
1166
+ .researcher {
1167
+ border-top: 2px solid #e2e8f0;
1168
+ margin-top: 40px;
1169
+ padding-top: 30px;
1170
+ }
1171
+
1172
+ .researcher h2 {
1173
+ margin-top: 0;
1174
+ color: #1a202c;
1175
+ font-size: 20px;
1176
+ }
1177
+
1178
+ .research-btn {
1179
+ background: #667eea;
1180
+ color: white;
1181
+ border: none;
1182
+ padding: 12px 24px;
1183
+ margin: 16px 0;
1184
+ border-radius: 6px;
1185
+ cursor: pointer;
1186
+ font-weight: 600;
1187
+ transition: all 0.2s ease;
1188
+ }
1189
+
1190
+ .research-btn:hover:not(:disabled) { background: #5568d3; }
1191
+ .research-btn:disabled { opacity: 0.6; cursor: not-allowed; }
1192
+
1193
+ .research-result {
1194
+ background: #f5f8ff;
1195
+ border: 1px solid #d4e0ff;
1196
+ border-radius: 8px;
1197
+ padding: 16px;
1198
+ margin-top: 20px;
1199
+ }
1200
+
1201
+ .ai-modes {
1202
+ display: flex;
1203
+ gap: 16px;
1204
+ margin-bottom: 16px;
1205
+ flex-wrap: wrap;
1206
+ }
1207
+
1208
+ .mode-group {
1209
+ display: flex;
1210
+ flex-direction: column;
1211
+ gap: 6px;
1212
+ }
1213
+
1214
+ .mode-group-label {
1215
+ font-size: 11px;
1216
+ font-weight: 700;
1217
+ text-transform: uppercase;
1218
+ color: #999;
1219
+ letter-spacing: 0.5px;
1220
+ }
1221
+
1222
+ .mode-group {
1223
+ display: flex;
1224
+ gap: 6px;
1225
+ flex-wrap: wrap;
1226
+ }
1227
+
1228
+ .mode-btn {
1229
+ background: #f0f4ff;
1230
+ color: #667eea;
1231
+ border: 1px solid #d4e0ff;
1232
+ padding: 8px 14px;
1233
+ border-radius: 6px;
1234
+ cursor: pointer;
1235
+ font-size: 12px;
1236
+ font-weight: 600;
1237
+ transition: all 0.2s ease;
1238
+ }
1239
+
1240
+ .mode-btn:hover:not(:disabled) {
1241
+ background: #e0e7ff;
1242
+ border-color: #667eea;
1243
+ transform: translateY(-1px);
1244
+ }
1245
+
1246
+ .mode-btn.active {
1247
+ background: #667eea;
1248
+ color: white;
1249
+ border-color: #667eea;
1250
+ }
1251
+
1252
+ .mode-btn.code-mode {
1253
+ background: #fef5e7;
1254
+ color: #c87832;
1255
+ border-color: #f4d29d;
1256
+ }
1257
+
1258
+ .mode-btn.code-mode:hover:not(:disabled) {
1259
+ background: #fdebd0;
1260
+ border-color: #c87832;
1261
+ }
1262
+
1263
+ .mode-btn.code-mode.active {
1264
+ background: #c87832;
1265
+ color: white;
1266
+ border-color: #c87832;
1267
+ }
1268
+
1269
+ .mode-btn:disabled {
1270
+ opacity: 0.5;
1271
+ cursor: not-allowed;
1272
+ }
1273
+
1274
+ .doc-selector {
1275
+ margin-bottom: 16px;
1276
+ }
1277
+
1278
+ .doc-selector label {
1279
+ display: block;
1280
+ font-size: 13px;
1281
+ font-weight: 600;
1282
+ color: #666;
1283
+ margin-bottom: 8px;
1284
+ text-transform: uppercase;
1285
+ letter-spacing: 0.5px;
1286
+ }
1287
+
1288
+ .doc-selector select {
1289
+ width: 100%;
1290
+ padding: 10px 12px;
1291
+ border: 1px solid #ddd;
1292
+ border-radius: 6px;
1293
+ font-family: inherit;
1294
+ font-size: 14px;
1295
+ background: white;
1296
+ }
1297
+
1298
+ .doc-selector select:focus {
1299
+ outline: none;
1300
+ border-color: #667eea;
1301
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
1302
+ }
1303
+
1304
+ .research-result h3 {
1305
+ margin: 0 0 12px 0;
1306
+ color: #667eea;
1307
+ font-size: 16px;
1308
+ }
1309
+
1310
+ .result-content {
1311
+ color: #555;
1312
+ line-height: 1.6;
1313
+ font-size: 14px;
1314
+ margin-bottom: 16px;
1315
+ max-height: 400px;
1316
+ overflow-y: auto;
1317
+ padding: 12px;
1318
+ background: white;
1319
+ border-radius: 4px;
1320
+ }
1321
+
1322
+ .result-actions {
1323
+ display: flex;
1324
+ gap: 8px;
1325
+ flex-wrap: wrap;
1326
+ }
1327
+
1328
+ .save-result-btn, .copy-result-btn {
1329
+ flex: 1;
1330
+ min-width: 140px;
1331
+ background: #48bb78;
1332
+ color: white;
1333
+ border: none;
1334
+ padding: 10px 16px;
1335
+ border-radius: 6px;
1336
+ cursor: pointer;
1337
+ font-weight: 600;
1338
+ font-size: 13px;
1339
+ transition: all 0.2s ease;
1340
+ }
1341
+
1342
+ .save-result-btn:hover {
1343
+ background: #38a169;
1344
+ transform: translateY(-1px);
1345
+ }
1346
+
1347
+ .copy-result-btn {
1348
+ background: #4299e1;
1349
+ }
1350
+
1351
+ .copy-result-btn:hover {
1352
+ background: #3182ce;
1353
+ transform: translateY(-1px);
1354
+ }
1355
+
1356
+ .code-note {
1357
+ font-size: 12px;
1358
+ color: #999;
1359
+ margin-top: 12px;
1360
+ font-style: italic;
1361
+ border-top: 1px solid #e2e8f0;
1362
+ padding-top: 12px;
1363
+ }
1364
+
1365
+ @media (max-width: 768px) {
1366
+ .docs-header {
1367
+ flex-direction: column;
1368
+ align-items: stretch;
1369
+ }
1370
+ .docs-controls {
1371
+ flex-direction: column;
1372
+ max-width: 100%;
1373
+ }
1374
+ .docs-grid {
1375
+ grid-template-columns: 1fr;
1376
+ }
1377
+ header { padding: 24px 20px; }
1378
+ main { padding: 20px; }
1379
+ }
1380
+ \`}</style>
1381
+ </div>
1382
+ );
1383
+ }
1384
+ `;
1385
+ fs.writeFileSync(path.join(srcDir, 'App.tsx'), appTsx);
1386
+ const indexTsx = `import React from 'react';
1387
+ import ReactDOM from 'react-dom/client';
1388
+ import { App } from './App';
1389
+
1390
+ const root = ReactDOM.createRoot(document.getElementById('root')!);
1391
+ root.render(
1392
+ <React.StrictMode>
1393
+ <App />
1394
+ </React.StrictMode>
1395
+ );
1396
+ `;
1397
+ fs.writeFileSync(path.join(srcDir, 'index.tsx'), indexTsx);
1398
+ }
1399
+ else {
1400
+ // Create vanilla JS app
1401
+ const indexJs = `import { db, documents } from './feltdb';
1402
+
1403
+ async function main() {
1404
+ console.log('🌊 FeltDB Research App');
1405
+ console.log('Runtime: ${runtime}');
1406
+ console.log('Distributed: ${distributed}');
1407
+ }
1408
+
1409
+ main().catch(console.error);
1410
+ `;
1411
+ fs.writeFileSync(path.join(srcDir, 'index.js'), indexJs);
1412
+ }
1413
+ // Create index.html
1414
+ const indexHtml = `<!DOCTYPE html>
1415
+ <html lang="en">
1416
+ <head>
1417
+ <meta charset="UTF-8">
1418
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1419
+ <title>${applicationName}</title>
1420
+ </head>
1421
+ <body>
1422
+ <div id="root"></div>
1423
+ <script type="module" src="/src/index${framework === 'react' ? '.tsx' : '.js'}"></script>
1424
+ </body>
1425
+ </html>
1426
+ `;
1427
+ fs.writeFileSync(path.join(projectDir, 'index.html'), indexHtml);
1428
+ // Create .env.example
1429
+ const envExample = `# FeltDB Configuration
1430
+ # Copy this file to .env.local and update the values
1431
+
1432
+ # API Key for authenticating with FeltDB servers
1433
+ # Leave empty for browser runtime, required for self-hosted
1434
+ VITE_FELTDB_API_KEY=
1435
+
1436
+ # FeltDB Server URL (for self-hosted runtime)
1437
+ VITE_FELTDB_URL=http://localhost:7700
1438
+
1439
+ # Override the default self-hosted container image
1440
+ # Example: ghcr.io/rkendel1/feltdb:latest
1441
+ FELTDB_IMAGE=
1442
+
1443
+ # Node environment
1444
+ NODE_ENV=development
1445
+ `;
1446
+ fs.writeFileSync(path.join(projectDir, '.env.example'), envExample);
1447
+ // Create RUNTIME_GUIDE.md
1448
+ const runtimeGuide = `# Runtime Configuration Guide
1449
+
1450
+ This guide explains the differences between FeltDB runtime options and how to choose the right one for your use case.
1451
+
1452
+ ## Browser Runtime
1453
+
1454
+ \`\`\`json
1455
+ {
1456
+ "runtime": "browser",
1457
+ "storage": "opfs"
1458
+ }
1459
+ \`\`\`
1460
+
1461
+ ### What it does
1462
+ - Runs FeltDB entirely in the browser using JavaScript
1463
+ - Stores data in the browser's Origin Private File System (OPFS)
1464
+ - No backend server required
1465
+
1466
+ ### When to use
1467
+ - Building offline-first applications
1468
+ - Client-side only projects
1469
+ - Prototyping and development
1470
+ - Privacy-focused applications where data never leaves the user's device
1471
+
1472
+ ### Limitations
1473
+ - Data is isolated per browser/device
1474
+ - Single-user only
1475
+ - Limited by browser disk space (usually 50GB+)
1476
+ - Cannot be accessed from other devices/browsers
1477
+
1478
+ ### Setup
1479
+ No special setup required. Just run \`npm run dev\`.
1480
+
1481
+ ## Node.js Runtime
1482
+
1483
+ \`\`\`json
1484
+ {
1485
+ "runtime": "node",
1486
+ "storage": "durable"
1487
+ }
1488
+ \`\`\`
1489
+
1490
+ ### What it does
1491
+ - Runs FeltDB as a Node.js server
1492
+ - Uses file-based or database storage
1493
+ - Accessible via HTTP/API
1494
+
1495
+ ### When to use
1496
+ - Building backend APIs
1497
+ - Server-side applications
1498
+ - REST API backends
1499
+ - Integration with other services
1500
+
1501
+ ### Limitations
1502
+ - Requires Node.js environment
1503
+ - Single server by default (no automatic distribution)
1504
+ - Data persistence depends on storage backend
1505
+
1506
+ ### Setup
1507
+ \`\`\`bash
1508
+ npm run dev
1509
+ # or
1510
+ npm run feltdb:server
1511
+ \`\`\`
1512
+
1513
+ ## Self-Hosted Runtime
1514
+
1515
+ \`\`\`json
1516
+ {
1517
+ "runtime": "self-hosted",
1518
+ "storage": "durable"
1519
+ }
1520
+ \`\`\`
1521
+
1522
+ ### What it does
1523
+ - Runs a dedicated FeltDB server in Docker
1524
+ - Provides distributed capabilities
1525
+ - Enables multi-user, multi-device access
1526
+ - Includes API management and authentication
1527
+
1528
+ ### When to use
1529
+ - Production deployments
1530
+ - Multi-user applications
1531
+ - Synchronization across devices
1532
+ - Team collaboration features
1533
+ - Advanced distributed scenarios
1534
+
1535
+ ### Requirements
1536
+ - Docker installed and running
1537
+ - Internet access (first run downloads image)
1538
+
1539
+ ### Setup
1540
+ \`\`\`bash
1541
+ npm run dev
1542
+ # Docker will be started automatically
1543
+ \`\`\`
1544
+
1545
+ The self-hosted instance runs on \`http://localhost:7700\` by default.
1546
+
1547
+ ## Vector Search Status
1548
+
1549
+ ### Current Status
1550
+ Vector search support in FeltDB is available through integrations with vector databases:
1551
+ - Milvus
1552
+ - Pinecone
1553
+ - Weaviate
1554
+ - Local vector storage (development)
1555
+
1556
+ ### Enabling Vector Search
1557
+ To enable vector search capabilities:
1558
+
1559
+ 1. Update \`feltdb.config.json\`:
1560
+ \`\`\`json
1561
+ {
1562
+ "capabilities": {
1563
+ "search": true,
1564
+ "vector-search": true
1565
+ }
1566
+ }
1567
+ \`\`\`
1568
+
1569
+ 2. Configure your vector database in your environment variables or code
1570
+
1571
+ 3. Implement vector embedding logic in your capabilities:
1572
+ \`\`\`typescript
1573
+ // feeldb/capabilities/index.ts
1574
+ export const capabilities = {
1575
+ 'vector-search': {
1576
+ enabled: true,
1577
+ scope: ['documents:read', 'capabilities:execute'],
1578
+ },
1579
+ };
1580
+ \`\`\`
1581
+
1582
+ ### Vector Storage Options
1583
+
1584
+ #### Local Development
1585
+ For development, FeltDB includes a local vector store:
1586
+ - In-memory embeddings
1587
+ - No external dependencies
1588
+ - Perfect for prototyping
1589
+
1590
+ #### Production
1591
+ For production deployments:
1592
+
1593
+ **Milvus** (Open source)
1594
+ - Self-hosted vector database
1595
+ - Scalable to millions of vectors
1596
+ - Full-text + vector search combined
1597
+
1598
+ **Pinecone** (Managed service)
1599
+ - Serverless vector database
1600
+ - Easy to set up
1601
+ - No infrastructure to manage
1602
+
1603
+ **Weaviate** (Open source + Cloud)
1604
+ - GraphQL interface
1605
+ - Hybrid search (text + vectors)
1606
+ - Multiple deployment options
1607
+
1608
+ ## Migration Between Runtimes
1609
+
1610
+ You can migrate between runtimes by:
1611
+
1612
+ 1. Export data from current runtime
1613
+ 2. Update \`feltdb.config.json\` with new runtime
1614
+ 3. Import data to new runtime
1615
+
1616
+ Example:
1617
+ \`\`\`bash
1618
+ # Export from browser
1619
+ npm run feltdb:export
1620
+
1621
+ # Update config
1622
+ # nano feltdb.config.json
1623
+
1624
+ # Start new runtime
1625
+ npm run dev
1626
+
1627
+ # Import data
1628
+ npm run feltdb:import
1629
+ \`\`\`
1630
+
1631
+ ## Environment Variables by Runtime
1632
+
1633
+ ### Browser
1634
+ - No special variables needed
1635
+ - Optional: \`VITE_DEBUG=1\` for debugging
1636
+
1637
+ ### Node.js
1638
+ - \`DATABASE_URL\`: Connection string for persistent database
1639
+ - \`PORT\`: Server port (default: 3000)
1640
+ - \`NODE_ENV\`: development|production
1641
+
1642
+ ### Self-Hosted
1643
+ - \`VITE_FELTDB_URL\`: Docker container URL (default: http://localhost:7700)
1644
+ - \`VITE_FELTDB_API_KEY\`: API token for authentication
1645
+ - \`FELTDB_IMAGE\`: Docker image to use (optional override)
1646
+ - \`DOCKER_NETWORK\`: Custom Docker network (optional)
1647
+
1648
+ ## Troubleshooting
1649
+
1650
+ ### "Runtime not configured" Error
1651
+ Update \`feltdb.config.json\` with a valid runtime selection.
1652
+
1653
+ ### "Cannot connect to self-hosted server"
1654
+ Check that Docker is running:
1655
+ \`\`\`bash
1656
+ docker ps | grep feltdb
1657
+ \`\`\`
1658
+
1659
+ If not running, restart:
1660
+ \`\`\`bash
1661
+ npm run dev
1662
+ \`\`\`
1663
+
1664
+ ### Browser Storage Full
1665
+ OPFS limit reached. Clear unused data or migrate to self-hosted.
1666
+
1667
+ ### Vector Search Not Working
1668
+ Verify vector database connection and that embeddings are enabled in capabilities.
1669
+
1670
+ ## Performance Considerations
1671
+
1672
+ | Metric | Browser | Node.js | Self-Hosted |
1673
+ |--------|---------|---------|-------------|
1674
+ | Latency | Immediate | Low | Low |
1675
+ | Throughput | ~1000 ops/sec | ~10K ops/sec | ~100K ops/sec |
1676
+ | Data Limit | 50GB+ | Disk dependent | Unlimited |
1677
+ | Users | 1 | Few | Many |
1678
+ | Cost | Free | Hosting cost | Hosting cost |
1679
+
1680
+ ## Next Steps
1681
+
1682
+ - Read the [FeltDB Documentation](https://github.com/rkendel1/feltdb)
1683
+ - Check out example projects in the templates
1684
+ - Join the community Discord for support
1685
+ `;
1686
+ fs.writeFileSync(path.join(projectDir, 'RUNTIME_GUIDE.md'), runtimeGuide);
1687
+ // Create .gitignore
1688
+ const gitignore = `node_modules/
1689
+ dist/
1690
+ build/
1691
+ .env
1692
+ .env.local
1693
+ .env.*.local
1694
+ *.log
1695
+ .DS_Store
1696
+ .feltdb/keys.json
1697
+ .feltdb/connection.json
1698
+ `;
1699
+ fs.writeFileSync(path.join(projectDir, '.gitignore'), gitignore);
1700
+ // Create README
1701
+ const readme = `# ${applicationName}
1702
+
1703
+ A FeltDB distributed application with agents and capabilities.
1704
+ - **Runtime:** ${runtime}
1705
+ - **Framework:** ${framework}
1706
+ - **Distributed:** ${distributed ? 'Yes' : 'No'}
1707
+ - **Agents:** ${hasAgents ? 'Yes' : 'No'}
1708
+ - **Capabilities:** ${capabilities}
1709
+
1710
+ ## Quick Start
1711
+
1712
+ \`\`\`bash
1713
+ npm install
1714
+ npm run dev
1715
+ \`\`\`
1716
+
1717
+ The application will be available at http://localhost:5173.
1718
+
1719
+ ## Project Structure
1720
+
1721
+ \`\`\`
1722
+ ${applicationName}/
1723
+ ├── feltdb/ # FeltDB configuration and logic
1724
+ │ ├── agents/ # Agent definitions
1725
+ │ ├── capabilities/ # Capability implementations
1726
+ │ ├── workflows/ # Workflow definitions
1727
+ │ └── schema/ # Data schemas
1728
+ ├── src/ # Application source code
1729
+ │ ├── App.${framework === 'react' ? 'tsx' : 'js'}
1730
+ │ ├── feltdb.ts # FeltDB client initialization
1731
+ │ └── index.${framework === 'react' ? 'tsx' : 'js'}
1732
+ ├── public/ # Static assets
1733
+ ├── index.html
1734
+ ├── .feltdb/ # Local FeltDB configuration
1735
+ ├── feltdb.config.json # FeltDB configuration
1736
+ ├── .env.example # Environment variables template
1737
+ ├── package.json
1738
+ ├── tsconfig.json
1739
+ └── README.md
1740
+ \`\`\`
1741
+
1742
+ ## Runtime Options
1743
+
1744
+ ### Browser (\`browser\`)
1745
+ - Local-first, client-side only
1746
+ - Uses browser OPFS (Origin Private File System) for storage
1747
+ - No server required
1748
+ - Best for: Offline-first apps, privacy-focused applications
1749
+ - Limitations: Single device scope (data stays local)
1750
+
1751
+ ### Node.js (\`node\`)
1752
+ - Server-side Node.js runtime
1753
+ - In-memory or file-based storage
1754
+ - Suitable for APIs and backend services
1755
+ - Best for: Server-side applications, REST APIs
1756
+ - Limitations: Memory-based by default
1757
+
1758
+ ### Self-Hosted (\`self-hosted\`)
1759
+ - Dedicated FeltDB server instance
1760
+ - Durable storage with distributed capabilities
1761
+ - Requires Docker (image: ghcr.io/rkendel1/feltdb)
1762
+ - Best for: Production deployments, multi-user systems
1763
+ - Setup: \`npm run dev\` starts the Docker container automatically
1764
+
1765
+ ## Configuration
1766
+
1767
+ Configuration is in \`feltdb.config.json\`:
1768
+ - \`runtime\`: ${runtime}
1769
+ - \`storage\`: ${runtime === 'browser' ? 'opfs' : 'durable'}
1770
+ - \`distributed\`: ${distributed}
1771
+ - \`agents.enabled\`: ${hasAgents}
1772
+ - \`capabilities\`: ${capabilities}
1773
+
1774
+ ### Environment Variables
1775
+
1776
+ Create a \`.env.local\` file (copy from \`.env.example\`):
1777
+
1778
+ \`\`\`
1779
+ VITE_FELTDB_API_KEY=your_api_key_here
1780
+ VITE_FELTDB_URL=http://localhost:7700
1781
+ \`\`\`
1782
+
1783
+ These are used when connecting to a self-hosted FeltDB instance.
1784
+
1785
+ ## Development
1786
+
1787
+ ### Start Development Server
1788
+ \`\`\`bash
1789
+ npm run dev
1790
+ \`\`\`
1791
+
1792
+ ### Build for Production
1793
+ \`\`\`bash
1794
+ npm run build
1795
+ \`\`\`
1796
+
1797
+ ### Validate FeltDB Configuration
1798
+ \`\`\`bash
1799
+ npm run feltdb:validate
1800
+ \`\`\`
1801
+
1802
+ ### View FeltDB Status
1803
+ \`\`\`bash
1804
+ npm run feltdb:status
1805
+ \`\`\`
1806
+
1807
+ ## Database Operations
1808
+
1809
+ ### Collections
1810
+
1811
+ The application includes pre-configured collections:
1812
+ - \`documents\`: Stores research documents
1813
+ - \`reports\`: Stores generated reports
1814
+
1815
+ ### Schema
1816
+
1817
+ Review \`feltdb.flow\` for the complete schema and workflow definitions.
1818
+
1819
+ ## Agents
1820
+
1821
+ ${hasAgents ? `### Researcher Agent
1822
+ The \`researcher\` agent runs real private inference using \`@feltdb/webllm\`:
1823
+ - Runs entirely in the browser (no data sent to servers)
1824
+ - Model downloads on first use
1825
+ - Inference runs in a Web Worker
1826
+ - Generated reports are stored in FeltDB
1827
+ - Learn more: https://github.com/mlc-ai/web-llm` : 'No agents configured. Add agents by re-running create-feltdb.'}
1828
+
1829
+ ## Vector Search
1830
+
1831
+ ${capabilities.includes('vector') ? `Vector search is enabled. Configure your vector storage backend in \`feltdb.config.json\`.` : `Vector search is not enabled. To add it, update \`feltdb.config.json\` to include \`"vector-search": true\` in capabilities.`}
1832
+
1833
+ ## Troubleshooting
1834
+
1835
+ ### "Model not loaded" in Researcher
1836
+ If the WebLLM Researcher shows "Model not loaded":
1837
+ 1. Check browser console for errors
1838
+ 2. Ensure sufficient disk space (models are ~2-3GB)
1839
+ 3. Try in a private/incognito window if localStorage is full
1840
+ 4. Clear browser cache and try again
1841
+
1842
+ ### Self-Hosted Connection Issues
1843
+ If using self-hosted mode and connection fails:
1844
+ 1. Ensure Docker is installed and running
1845
+ 2. Check FELTDB_URL and API key in .env.local
1846
+ 3. Run \`npm run feltdb:status\` to check server health
1847
+ 4. View logs: \`docker logs feltdb\`
1848
+
1849
+ ### API Key Errors
1850
+ If API key management fails in Studio:
1851
+ 1. Verify VITE_FELTDB_URL is set correctly
1852
+ 2. Ensure token has proper scopes
1853
+ 3. Check CORS settings on self-hosted server
1854
+
1855
+ ## Learn More
1856
+
1857
+ - [FeltDB Documentation](https://github.com/rkendel1/feltdb)
1858
+ - [WebLLM Documentation](https://github.com/mlc-ai/web-llm)
1859
+ - [Distribution & Capabilities](https://github.com/rkendel1/feltdb/docs/capabilities.md)
1860
+ `;
1861
+ fs.writeFileSync(path.join(projectDir, 'README.md'), readme);
1862
+ }