create-feltdb 0.4.0 → 0.4.2

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/README.md CHANGED
@@ -2,29 +2,33 @@
2
2
 
3
3
  Interactive CLI tool to scaffold new FeltDB applications.
4
4
 
5
- ## Installation
6
-
7
- ```bash
8
- npm install -g create-feltdb
9
- ```
10
-
11
5
  ## Usage
12
6
 
13
- Create a new project:
7
+ One command scaffolds, installs, and starts the application, Studio, and local
8
+ WebLLM agent (no global installation required):
14
9
 
15
10
  ```bash
16
- npx create-feltdb my-app
11
+ npx --yes create-feltdb@latest my-app
17
12
  ```
18
13
 
19
- Or use the installed command:
14
+ Use the interactive menu, or accept its defaults non-interactively:
20
15
 
21
16
  ```bash
22
- create-feltdb my-app
17
+ npx --yes create-feltdb@latest my-app --yes
23
18
  ```
24
19
 
25
20
  ### Options
26
21
 
27
- - `--yes` or `-y` - Skip interactive prompts and use defaults
22
+ - `--runtime <browser|node|self-hosted>`
23
+ - `--framework <react|vanilla>`
24
+ - `--no-distributed`
25
+ - `--no-agents`
26
+ - `--capabilities <search|vector|search,vector>`
27
+ - `--no-install` — generate without installing dependencies
28
+ - `--no-start` — do not start services after generation
29
+ - `--yes` or `-y` — skip the interactive menu and use defaults
30
+ - `--help` or `-h`
31
+ - `--version`
28
32
 
29
33
  ## What Gets Generated
30
34
 
@@ -47,16 +51,16 @@ my-app/
47
51
  └── README.md
48
52
  ```
49
53
 
50
- ## Quick Start
51
-
52
- After running `create-feltdb`:
54
+ ## Run It Again
53
55
 
54
56
  ```bash
55
57
  cd my-app
56
- npm install
57
58
  npm run dev
58
59
  ```
59
60
 
61
+ This starts the app and Studio together. Self-hosted projects also start the
62
+ version-matched FeltDB server container and therefore require Docker.
63
+
60
64
  ## Default Configuration
61
65
 
62
66
  The generated project uses:
@@ -64,6 +68,8 @@ The generated project uses:
64
68
  - **Storage**: OPFS (with IndexedDB fallback)
65
69
  - **Distributed**: Enabled by default
66
70
  - **Capabilities**: Search enabled, Vector disabled
71
+ - **AI**: Private WebLLM agent enabled; model loads lazily in the browser
72
+ - **Studio**: Installed and served alongside the application
67
73
 
68
74
  ## License
69
75
 
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Application identity management
3
+ *
4
+ * Handles:
5
+ * - FELTDB_APP_NAME: Human-readable application name
6
+ * - FELTDB_APP_ID: UUID-based stable identifier
7
+ * - FELTDB_VERSION: Semantic versioning
8
+ * - Version matching between components
9
+ */
10
+ import crypto from 'crypto';
11
+ /**
12
+ * Generate a stable UUID from application name
13
+ * Ensures same name always produces same ID
14
+ */
15
+ export function generateAppId(appName) {
16
+ const hash = crypto
17
+ .createHash('sha256')
18
+ .update(appName)
19
+ .digest('hex');
20
+ // Convert to UUID v5-like format
21
+ const uuid = [
22
+ hash.substring(0, 8),
23
+ hash.substring(8, 12),
24
+ '5' + hash.substring(13, 16),
25
+ ((parseInt(hash.substring(16, 18), 16) & 0x3f) | 0x80).toString(16).padStart(2, '0') +
26
+ hash.substring(18, 20),
27
+ hash.substring(20, 32),
28
+ ].join('-');
29
+ return uuid;
30
+ }
31
+ /**
32
+ * Sanitize application name for use in identifiers
33
+ */
34
+ export function sanitizeAppName(name) {
35
+ return (name
36
+ // Replace spaces with hyphens
37
+ .replace(/\s+/g, '-')
38
+ // Remove non-alphanumeric except hyphens
39
+ .replace(/[^a-z0-9-]/gi, '')
40
+ // Remove leading/trailing hyphens
41
+ .replace(/^-+|-+$/g, '')
42
+ // Limit to 50 chars
43
+ .substring(0, 50)
44
+ .toLowerCase());
45
+ }
46
+ /**
47
+ * Get current FeltDB version from package.json
48
+ */
49
+ export function getCurrentFeltDBVersion() {
50
+ // This would be replaced at build time with the actual version
51
+ return '0.4.2';
52
+ }
53
+ /**
54
+ * Create application identity manifest
55
+ */
56
+ export function createApplicationIdentity(appName, feltdbVersion) {
57
+ const version = feltdbVersion || getCurrentFeltDBVersion();
58
+ const sanitized = sanitizeAppName(appName);
59
+ const appId = generateAppId(sanitized);
60
+ return {
61
+ name: sanitized,
62
+ id: appId,
63
+ version,
64
+ createdAt: new Date().toISOString(),
65
+ components: {
66
+ app: version,
67
+ feltdb: version,
68
+ studio: version,
69
+ },
70
+ };
71
+ }
72
+ /**
73
+ * Generate version manifest for Docker image metadata
74
+ */
75
+ export function generateVersionLabels(identity) {
76
+ return {
77
+ 'app.feltdb/app-name': identity.name,
78
+ 'app.feltdb/app-id': identity.id,
79
+ 'app.feltdb/version': identity.version,
80
+ 'app.feltdb/created': identity.createdAt,
81
+ 'app.feltdb/component.app': identity.components.app,
82
+ 'app.feltdb/component.feltdb': identity.components.feltdb,
83
+ 'app.feltdb/component.studio': identity.components.studio,
84
+ };
85
+ }
86
+ /**
87
+ * Validate version compatibility
88
+ */
89
+ export function validateVersionCompatibility(appVersion, feltdbVersion, studioVersion) {
90
+ const warnings = [];
91
+ // All components should be same major.minor version for safety
92
+ const extractMajorMinor = (v) => v.split('.').slice(0, 2).join('.');
93
+ const appMajorMinor = extractMajorMinor(appVersion);
94
+ const feltdbMajorMinor = extractMajorMinor(feltdbVersion);
95
+ const studioMajorMinor = extractMajorMinor(studioVersion);
96
+ if (appMajorMinor !== feltdbMajorMinor) {
97
+ warnings.push(`App version ${appVersion} differs from FeltDB ${feltdbVersion}`);
98
+ }
99
+ if (feltdbMajorMinor !== studioMajorMinor) {
100
+ warnings.push(`FeltDB version ${feltdbVersion} differs from Studio ${studioVersion}`);
101
+ }
102
+ return {
103
+ compatible: warnings.length === 0,
104
+ warnings,
105
+ };
106
+ }
107
+ /**
108
+ * Generate .feltdb/identity.json manifest
109
+ */
110
+ export function generateIdentityManifest(identity) {
111
+ return JSON.stringify(identity, null, 2);
112
+ }
113
+ /**
114
+ * Generate environment file with version information
115
+ */
116
+ export function generateVersionEnv(identity) {
117
+ return `# Application Identity
118
+ # Generated at: ${identity.createdAt}
119
+
120
+ FELTDB_APP_NAME=${identity.name}
121
+ FELTDB_APP_ID=${identity.id}
122
+ FELTDB_VERSION=${identity.version}
123
+
124
+ # Component Versions
125
+ FELTDB_APP_VERSION=${identity.components.app}
126
+ FELTDB_CORE_VERSION=${identity.components.feltdb}
127
+ FELTDB_STUDIO_VERSION=${identity.components.studio}
128
+
129
+ # Build Info
130
+ FELTDB_BUILT_AT=${new Date().toISOString()}
131
+ `;
132
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * CLI lifecycle scripts for FeltDB applications
3
+ *
4
+ * Generates scripts for common operations:
5
+ * - feltdb:up - Start services
6
+ * - feltdb:down - Stop services
7
+ * - feltdb:logs - View logs
8
+ * - feltdb:status - Check health
9
+ * - feltdb:reset - Reset data
10
+ * - feltdb:ps - List running services
11
+ */
12
+ export function generateUpScript() {
13
+ return `#!/bin/bash
14
+
15
+ # Start FeltDB application stack
16
+ # Usage: npm run feltdb:up
17
+
18
+ set -e
19
+
20
+ echo "🚀 Starting FeltDB application..."
21
+
22
+ # Load environment
23
+ if [ -f .env.local ]; then
24
+ export \$(cat .env.local | grep -v '^#' | xargs)
25
+ fi
26
+
27
+ # Build and start containers
28
+ docker-compose up -d
29
+
30
+ echo "⏳ Waiting for services to be healthy..."
31
+
32
+ # Wait for FeltDB
33
+ TIMEOUT=60
34
+ ELAPSED=0
35
+ while [ \$ELAPSED -lt \$TIMEOUT ]; do
36
+ if curl -s -f http://localhost:7700/health > /dev/null 2>&1; then
37
+ STATE=\$(curl -s http://localhost:7700/health | grep -o '"state":"[^"]*' | cut -d'"' -f4)
38
+ if [ "\$STATE" = "ready" ] || [ "\$STATE" = "recovering" ]; then
39
+ echo "✅ FeltDB is \$STATE"
40
+ break
41
+ fi
42
+ fi
43
+ sleep 2
44
+ ELAPSED=\$((ELAPSED + 2))
45
+ done
46
+
47
+ # Wait for application
48
+ ELAPSED=0
49
+ while [ \$ELAPSED -lt \$TIMEOUT ]; do
50
+ if curl -s -f http://localhost:3000/health > /dev/null 2>&1; then
51
+ echo "✅ Application is ready"
52
+ break
53
+ fi
54
+ sleep 2
55
+ ELAPSED=\$((ELAPSED + 2))
56
+ done
57
+
58
+ echo ""
59
+ echo "✨ FeltDB stack is running!"
60
+ echo ""
61
+ echo "Services:"
62
+ echo " FeltDB: http://localhost:7700"
63
+ echo " App: http://localhost:3000"
64
+ echo " Studio: http://localhost:8000"
65
+ echo ""
66
+ echo "Next: npm run feltdb:logs"
67
+ `;
68
+ }
69
+ export function generateDownScript() {
70
+ return `#!/bin/bash
71
+
72
+ # Stop FeltDB application stack
73
+ # Usage: npm run feltdb:down
74
+
75
+ echo "🛑 Stopping FeltDB application..."
76
+
77
+ docker-compose down
78
+
79
+ echo "✅ Services stopped"
80
+ `;
81
+ }
82
+ export function generateLogsScript() {
83
+ return `#!/bin/bash
84
+
85
+ # View logs from FeltDB stack
86
+ # Usage: npm run feltdb:logs [service]
87
+ # npm run feltdb:logs feltdb
88
+ # npm run feltdb:logs app
89
+ # npm run feltdb:logs studio
90
+
91
+ SERVICE=\$1
92
+
93
+ if [ -z "\$SERVICE" ]; then
94
+ echo "📋 Logs from all services (Ctrl+C to exit):"
95
+ docker-compose logs -f
96
+ else
97
+ echo "📋 Logs from \$SERVICE (Ctrl+C to exit):"
98
+ docker-compose logs -f \$SERVICE
99
+ fi
100
+ `;
101
+ }
102
+ export function generateStatusScript() {
103
+ return `#!/bin/bash
104
+
105
+ # Check health and status of FeltDB stack
106
+ # Usage: npm run feltdb:status
107
+
108
+ echo "🔍 FeltDB Application Status"
109
+ echo ""
110
+
111
+ # Docker Compose status
112
+ echo "Containers:"
113
+ docker-compose ps
114
+
115
+ echo ""
116
+
117
+ # FeltDB health
118
+ echo "FeltDB Health:"
119
+ if RESPONSE=\$(curl -s -m 5 http://localhost:7700/health 2>/dev/null); then
120
+ STATE=\$(echo \$RESPONSE | grep -o '"state":"[^"]*' | cut -d'"' -f4)
121
+ VERSION=\$(echo \$RESPONSE | grep -o '"version":"[^"]*' | cut -d'"' -f4)
122
+ echo " State: \$STATE"
123
+ echo " Version: \$VERSION"
124
+ else
125
+ echo " ❌ No response"
126
+ fi
127
+
128
+ # Application health
129
+ echo ""
130
+ echo "Application Health:"
131
+ if RESPONSE=\$(curl -s -m 5 http://localhost:3000/health 2>/dev/null); then
132
+ echo " ✅ Responding"
133
+ else
134
+ echo " ❌ No response"
135
+ fi
136
+
137
+ echo ""
138
+ echo "Log tail: npm run feltdb:logs"
139
+ `;
140
+ }
141
+ export function generateResetScript() {
142
+ return `#!/bin/bash
143
+
144
+ # Reset FeltDB data (WARNING: destructive)
145
+ # Usage: npm run feltdb:reset
146
+
147
+ echo "⚠️ This will delete all FeltDB data!"
148
+ read -p "Are you sure? (type 'yes' to confirm): " CONFIRM
149
+
150
+ if [ "\$CONFIRM" != "yes" ]; then
151
+ echo "Cancelled."
152
+ exit 1
153
+ fi
154
+
155
+ echo "🧹 Resetting FeltDB data..."
156
+
157
+ # Stop containers
158
+ docker-compose down
159
+
160
+ # Remove volumes
161
+ docker volume rm \$(docker volume ls -q | grep feltdb_data) 2>/dev/null || true
162
+
163
+ # Restart
164
+ echo "🚀 Restarting services..."
165
+ docker-compose up -d
166
+
167
+ echo "✅ Data reset complete"
168
+ `;
169
+ }
170
+ export function generatePsScript() {
171
+ return `#!/bin/bash
172
+
173
+ # List running FeltDB services
174
+ # Usage: npm run feltdb:ps
175
+
176
+ docker-compose ps
177
+ `;
178
+ }
179
+ export function generatePackageJsonScripts(runtime) {
180
+ const scripts = {
181
+ 'feltdb:up': 'bash scripts/feltdb-up.sh',
182
+ 'feltdb:down': 'bash scripts/feltdb-down.sh',
183
+ 'feltdb:logs': 'bash scripts/feltdb-logs.sh',
184
+ 'feltdb:status': 'bash scripts/feltdb-status.sh',
185
+ 'feltdb:reset': 'bash scripts/feltdb-reset.sh',
186
+ 'feltdb:ps': 'bash scripts/feltdb-ps.sh',
187
+ };
188
+ if (runtime === 'browser') {
189
+ // Browser doesn't need container scripts
190
+ return {
191
+ dev: 'vite',
192
+ build: 'tsc && vite build',
193
+ };
194
+ }
195
+ if (runtime === 'node') {
196
+ return {
197
+ dev: 'tsx src/index.ts',
198
+ build: 'tsc',
199
+ start: 'node dist/index.js',
200
+ ...scripts,
201
+ };
202
+ }
203
+ if (runtime === 'self-hosted') {
204
+ return {
205
+ dev: 'docker-compose up -d --build',
206
+ build: 'docker-compose build',
207
+ ...scripts,
208
+ };
209
+ }
210
+ return scripts;
211
+ }
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@
7
7
  import path from 'path';
8
8
  import { fileURLToPath } from 'url';
9
9
  import readline from 'readline';
10
+ import { spawn } from 'child_process';
10
11
  import { createProject } from './create.js';
11
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
13
  function parseArgs(args) {
@@ -38,6 +39,15 @@ function parseArgs(args) {
38
39
  }
39
40
  return options;
40
41
  }
42
+ function run(command, args, cwd) {
43
+ return new Promise((resolve, reject) => {
44
+ const child = spawn(command, args, { cwd, stdio: 'inherit' });
45
+ child.once('error', reject);
46
+ child.once('exit', code => code === 0
47
+ ? resolve()
48
+ : reject(new Error(`${command} exited with status ${code ?? 'unknown'}`)));
49
+ });
50
+ }
41
51
  async function select(message, choices, initialValue) {
42
52
  let selected = Math.max(0, choices.findIndex(choice => choice.value === initialValue));
43
53
  const input = process.stdin;
@@ -114,11 +124,11 @@ async function promptForOptions(defaults) {
114
124
  async function main() {
115
125
  const args = process.argv.slice(2);
116
126
  if (args.includes('--help') || args.includes('-h')) {
117
- console.log(`create-feltdb 0.4.0\n\nUsage: create-feltdb [project-name] [options]\n\nOptions:\n --runtime <browser|node|self-hosted>\n --framework <react|vanilla>\n --no-distributed\n --no-agents\n --capabilities <list>\n -y, --yes\n -h, --help\n --version`);
127
+ console.log(`create-feltdb 0.4.2\n\nUsage: create-feltdb [project-name] [options]\n\nOptions:\n --runtime <browser|node|self-hosted>\n --framework <react|vanilla>\n --no-distributed\n --no-agents\n --capabilities <list>\n --no-install\n --no-start\n -y, --yes\n -h, --help\n --version`);
118
128
  return;
119
129
  }
120
130
  if (args.includes('--version')) {
121
- console.log('0.4.0');
131
+ console.log('0.4.2');
122
132
  return;
123
133
  }
124
134
  // Find project name (first non-flag argument)
@@ -135,6 +145,8 @@ async function main() {
135
145
  }
136
146
  }
137
147
  const shouldAutoYes = args.includes('--yes') || args.includes('-y');
148
+ const shouldInstall = !args.includes('--no-install');
149
+ const shouldStart = !args.includes('--no-start');
138
150
  let options = parseArgs(args);
139
151
  console.log('\n✨ Creating FeltDB Application\n');
140
152
  if (!shouldAutoYes) {
@@ -158,16 +170,28 @@ async function main() {
158
170
  capabilities: options.capabilities,
159
171
  });
160
172
  console.log('\n✅ FeltDB application created successfully!\n');
161
- console.log(`Next steps:`);
162
- console.log(` cd ${projectName}`);
163
- console.log(` npm install`);
164
- if (options.runtime === 'self-hosted') {
165
- console.log(` feltdb server --data ./data`);
173
+ const projectDir = path.resolve(process.cwd(), projectName);
174
+ const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
175
+ if (shouldInstall) {
176
+ console.log('📦 Installing application, Studio, and local AI dependencies...\n');
177
+ await run(npm, ['install'], projectDir);
178
+ }
179
+ if (shouldStart) {
180
+ if (!shouldInstall) {
181
+ console.log(`Start after installing dependencies:\n cd ${projectName}\n npm install\n npm run dev\n`);
182
+ }
183
+ else {
184
+ console.log('\n🚀 Starting the application and FeltDB Studio...\n');
185
+ await run(npm, ['run', 'dev'], projectDir);
186
+ }
166
187
  }
167
188
  else {
168
- console.log(` npm run dev`);
189
+ console.log('Project is ready. Start everything with:');
190
+ console.log(` cd ${projectName}`);
191
+ if (!shouldInstall)
192
+ console.log(' npm install');
193
+ console.log(' npm run dev');
169
194
  }
170
- console.log('');
171
195
  }
172
196
  catch (error) {
173
197
  console.error('❌ Failed to create project:', error);
package/dist/create.js CHANGED
@@ -54,6 +54,7 @@ export async function createProject(options) {
54
54
  },
55
55
  devDependencies: {
56
56
  '@feltdb/cli': feltdbPackageRange,
57
+ '@feltdb/studio': feltdbPackageRange,
57
58
  typescript: '^5.0.0',
58
59
  '@types/node': '^20.0.0',
59
60
  vite: '^8.2.1',
@@ -66,6 +67,9 @@ export async function createProject(options) {
66
67
  packageJson.devDependencies['@types/react'] = '^18.0.0';
67
68
  packageJson.devDependencies['@types/react-dom'] = '^18.0.0';
68
69
  }
70
+ if (hasAgents) {
71
+ packageJson.dependencies['@feltdb/webllm'] = feltdbPackageRange;
72
+ }
69
73
  fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify(packageJson, null, 2));
70
74
  // Create feltdb.config.json
71
75
  const feltdbConfig = {
@@ -153,52 +157,62 @@ export async function createProject(options) {
153
157
  }
154
158
  fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
155
159
  // Create main application files
160
+ const runtimeOptions = runtime === 'browser'
161
+ ? "{ 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 }";
156
165
  const feltdbTs = `import { createFeltDB } from '@feltdb/core';
157
166
 
158
- export const db = createFeltDB({
159
- namespace: '${applicationName}',
160
- memory: true, // Development-only; configure \`server\` for durable customer data.
161
- });
167
+ export const db = createFeltDB(${runtimeOptions});
162
168
 
163
169
  // Collections
164
170
  export const documents = db.collection('documents');
165
171
  export const reports = db.collection('reports');
166
172
  `;
167
173
  fs.writeFileSync(path.join(srcDir, 'feltdb.ts'), feltdbTs);
168
- // Create a sample agent if enabled
174
+ // Create a real local-inference agent for browser projects.
169
175
  if (hasAgents) {
170
- const agentTs = `import { db } from '../feltdb';
176
+ const agentTs = `import { WebLLMProvider } from '@feltdb/webllm';
177
+ import { db, reports } from '../../src/feltdb';
171
178
 
172
179
  /**
173
- * Researcher Agent
174
- *
175
- * This agent demonstrates distributed agent execution
176
- * across peers in the FeltDB fabric.
180
+ * Researcher metadata is registered in FeltDB; inference runs privately in
181
+ * the browser through WebLLM and its worker.
177
182
  */
178
183
  export const researcher = db.defineAgent({
179
184
  name: 'researcher',
180
- description: 'Search and analyze documents across the fabric',
185
+ version: 1,
186
+ description: 'Analyze documents with private, on-device inference',
181
187
  capabilities: [
182
188
  'document-read',
183
- 'vector-search',
184
189
  'report-write'
185
190
  ],
186
- async run(options: any) {
187
- console.log('🤖 Researcher agent starting...');
188
- console.log('Goal:', options.goal);
189
- console.log('Inputs:', options.inputs);
190
-
191
- // The agent will execute capabilities across peers
192
- // Results are automatically acquired from remote locations
193
-
194
- return {
195
- status: 'complete',
196
- results: {
197
- message: 'Research completed successfully'
198
- }
199
- };
200
- }
191
+ goals: ['Summarize documents without sending prompts off-device'],
192
+ constraints: { maxIterations: 1 },
201
193
  });
194
+
195
+ let provider: WebLLMProvider | undefined;
196
+
197
+ export async function runResearcher(
198
+ prompt: string,
199
+ onProgress?: (message: string) => void,
200
+ ) {
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;
215
+ }
202
216
  `;
203
217
  fs.writeFileSync(path.join(feltdbDir, 'agents', 'researcher.ts'), agentTs);
204
218
  }
@@ -228,12 +242,51 @@ export const capabilities = {
228
242
  fs.writeFileSync(path.join(feltdbDir, 'capabilities', 'index.ts'), capabilitiesTs);
229
243
  // Create main application file based on framework
230
244
  if (framework === 'react') {
245
+ const agentImport = hasAgents
246
+ ? "import { runResearcher } from '../feltdb/agents/researcher';"
247
+ : '';
248
+ const agentState = hasAgents
249
+ ? ` const [prompt, setPrompt] = useState('Summarize why local-first applications are useful.');
250
+ const [answer, setAnswer] = useState('');
251
+ const [modelStatus, setModelStatus] = useState('Model not loaded');
252
+ const [generating, setGenerating] = useState(false);`
253
+ : '';
254
+ const agentHandler = hasAgents
255
+ ? `
256
+ const handleResearch = async () => {
257
+ setGenerating(true);
258
+ setAnswer('');
259
+ try {
260
+ setAnswer(await runResearcher(prompt, setModelStatus));
261
+ setModelStatus('Ready — inference stayed in this browser');
262
+ } catch (error) {
263
+ setModelStatus(error instanceof Error ? error.message : String(error));
264
+ } finally {
265
+ setGenerating(false);
266
+ }
267
+ };
268
+ `
269
+ : '';
270
+ const agentMarkup = hasAgents
271
+ ? `
272
+ <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'}
278
+ </button>
279
+ {answer && <article><h3>Result</h3><p>{answer}</p></article>}
280
+ </section>`
281
+ : '';
231
282
  const appTsx = `import React, { useState, useEffect } from 'react';
232
283
  import { db, documents } from './feltdb';
284
+ ${agentImport}
233
285
 
234
286
  export function App() {
235
287
  const [docs, setDocs] = useState<any[]>([]);
236
288
  const [loading, setLoading] = useState(true);
289
+ ${agentState}
237
290
 
238
291
  useEffect(() => {
239
292
  const loadDocs = async () => {
@@ -264,6 +317,7 @@ export function App() {
264
317
  console.error('Error adding document:', err);
265
318
  }
266
319
  };
320
+ ${agentHandler}
267
321
 
268
322
  return (
269
323
  <div className="app">
@@ -306,6 +360,7 @@ export function App() {
306
360
  )}
307
361
  <button onClick={handleAddDocument}>Add Document</button>
308
362
  </section>
363
+ ${agentMarkup}
309
364
  </main>
310
365
 
311
366
  <style>{\`
@@ -418,6 +473,20 @@ export function App() {
418
473
  button:hover {
419
474
  background: #5568d3;
420
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
+ }
421
490
  \`}</style>
422
491
  </div>
423
492
  );
@@ -468,8 +537,10 @@ main().catch(console.error);
468
537
  fs.writeFileSync(path.join(projectDir, 'index.html'), indexHtml);
469
538
  // Create .env.example
470
539
  const envExample = `# FeltDB Configuration
471
- FELTDB_API_KEY=
472
- FELTDB_URL=http://localhost:7700
540
+ VITE_FELTDB_API_KEY=
541
+ VITE_FELTDB_URL=http://localhost:7700
542
+ # Override the versioned self-hosted container when needed:
543
+ FELTDB_IMAGE=
473
544
  NODE_ENV=development
474
545
  `;
475
546
  fs.writeFileSync(path.join(projectDir, '.env.example'), envExample);
@@ -554,12 +625,9 @@ feltdb keys create --name development --scope '*'
554
625
 
555
626
  ## Agents
556
627
 
557
- ${hasAgents ? `The \`researcher\` agent demonstrates distributed execution:
558
- - Resolves document references
559
- - Discovers capabilities across peers
560
- - Executes on compatible peers
561
- - Creates workflows
562
- - Publishes results` : 'No agents configured'}
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'}
563
631
 
564
632
  ## Capabilities
565
633
 
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Docker Compose generator for self-hosted FeltDB deployments
3
+ *
4
+ * Generates:
5
+ * - docker-compose.yml with three services (FeltDB, App, Studio)
6
+ * - Dockerfile for application container
7
+ * - .dockerignore for optimized builds
8
+ * - Environment configuration
9
+ */
10
+ export function generateDockerCompose(config) {
11
+ return `version: '3.9'
12
+
13
+ services:
14
+ feltdb:
15
+ image: rkendel1/feltdb:latest
16
+ container_name: \${COMPOSE_PROJECT_NAME}-feltdb
17
+ environment:
18
+ FELTDB_APP_NAME: \${FELTDB_APP_NAME:-${config.appName}}
19
+ FELTDB_APP_ID: \${FELTDB_APP_ID:-${config.appId}}
20
+ FELTDB_VERSION: \${FELTDB_VERSION:-${config.version}}
21
+ FELTDB_PEERS: \${FELTDB_PEERS:-}
22
+ FELTDB_REPLICATION_PORT: ${config.replicationPort}
23
+ NODE_ENV: \${NODE_ENV:-production}
24
+ ports:
25
+ - "\${FELTDB_API_PORT:-${config.port}}:7700"
26
+ - "\${FELTDB_REPLICATION_PORT:-${config.replicationPort}}:${config.replicationPort}"
27
+ volumes:
28
+ - feltdb_data:/data
29
+ - ./feltdb.flow:/app/feltdb.flow:ro
30
+ healthcheck:
31
+ test: ["CMD", "curl", "-f", "http://localhost:7700/health"]
32
+ interval: 10s
33
+ timeout: 5s
34
+ retries: 3
35
+ start_period: 30s
36
+ restart: unless-stopped
37
+ networks:
38
+ - feltdb_network
39
+ labels:
40
+ app.feltdb/component: "database"
41
+ app.feltdb/version: "${config.version}"
42
+
43
+ app:
44
+ build:
45
+ context: .
46
+ dockerfile: Dockerfile
47
+ args:
48
+ NODE_ENV: production
49
+ container_name: \${COMPOSE_PROJECT_NAME}-app
50
+ environment:
51
+ FELTDB_API_URL: http://feltdb:7700
52
+ FELTDB_APP_NAME: \${FELTDB_APP_NAME:-${config.appName}}
53
+ NODE_ENV: \${NODE_ENV:-production}
54
+ ports:
55
+ - "\${APP_PORT:-3000}:3000"
56
+ depends_on:
57
+ feltdb:
58
+ condition: service_healthy
59
+ volumes:
60
+ - ./src:/app/src
61
+ - app_node_modules:/app/node_modules
62
+ healthcheck:
63
+ test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
64
+ interval: 10s
65
+ timeout: 5s
66
+ retries: 3
67
+ start_period: 30s
68
+ restart: unless-stopped
69
+ networks:
70
+ - feltdb_network
71
+ labels:
72
+ app.feltdb/component: "application"
73
+ app.feltdb/version: "${config.version}"
74
+
75
+ studio:
76
+ image: rkendel1/feltdb-studio:latest
77
+ container_name: \${COMPOSE_PROJECT_NAME}-studio
78
+ environment:
79
+ FELTDB_API_URL: http://feltdb:7700
80
+ APP_URL: http://app:3000
81
+ ports:
82
+ - "\${STUDIO_PORT:-${config.studioPort}}:8000"
83
+ depends_on:
84
+ - feltdb
85
+ - app
86
+ networks:
87
+ - feltdb_network
88
+ labels:
89
+ app.feltdb/component: "studio"
90
+
91
+ networks:
92
+ feltdb_network:
93
+ driver: bridge
94
+ labels:
95
+ app.feltdb/network: "internal"
96
+
97
+ volumes:
98
+ feltdb_data:
99
+ driver: local
100
+ labels:
101
+ app.feltdb/data: "durable-operations-log"
102
+ app_node_modules:
103
+ driver: local
104
+ `;
105
+ }
106
+ export function generateDockerfile(config, framework) {
107
+ const buildCommand = framework === 'react' ? 'npm run build' : 'npm run build';
108
+ return `# FeltDB Application Container
109
+ # Multi-stage build for optimized production image
110
+
111
+ FROM node:18-alpine AS builder
112
+
113
+ WORKDIR /app
114
+
115
+ # Install dependencies
116
+ COPY package.json package-lock.json ./
117
+ RUN npm ci --only=production && npm cache clean --force
118
+
119
+ # Copy source
120
+ COPY . .
121
+
122
+ # Build application
123
+ RUN ${buildCommand}
124
+
125
+ # Final stage
126
+ FROM node:18-alpine
127
+
128
+ WORKDIR /app
129
+
130
+ # Install health check utility
131
+ RUN apk add --no-cache curl
132
+
133
+ # Copy built application
134
+ COPY --from=builder /app/node_modules ./node_modules
135
+ COPY --from=builder /app/dist ./dist
136
+ COPY --from=builder /app/package.json ./package.json
137
+ COPY --from=builder /app/feltdb.flow ./feltdb.flow
138
+
139
+ # Create data directory for Node runtime
140
+ RUN mkdir -p /data && chmod 755 /data
141
+
142
+ # Non-root user for security
143
+ RUN addgroup -g 1001 -S nodejs
144
+ RUN adduser -S nodejs -u 1001
145
+ USER nodejs
146
+
147
+ # Health check
148
+ HEALTHCHECK --interval=10s --timeout=5s --retries=3 --start-period=30s \\
149
+ CMD curl -f http://localhost:3000/health || exit 1
150
+
151
+ # Startup
152
+ ENV NODE_ENV=production
153
+ EXPOSE 3000
154
+
155
+ CMD ["node", "dist/index.js"]
156
+ `;
157
+ }
158
+ export function generateDockerIgnore() {
159
+ return `node_modules
160
+ npm-debug.log
161
+ dist
162
+ build
163
+ .git
164
+ .gitignore
165
+ .env
166
+ .env.local
167
+ .env.*.local
168
+ .DS_Store
169
+ *.log
170
+ .feltdb/keys.json
171
+ .feltdb/connection.json
172
+ .nextc
173
+ .next
174
+ coverage
175
+ .nyc_output
176
+ .cache
177
+ .turbo
178
+ `;
179
+ }
180
+ export function generateDotEnvLocal(config) {
181
+ return `# Docker Compose Environment
182
+ # Generated by create-feltdb for \${config.appName}
183
+
184
+ COMPOSE_PROJECT_NAME=${config.appName.toLowerCase().replace(/[^a-z0-9]/g, '-')}
185
+
186
+ # FeltDB Configuration
187
+ FELTDB_APP_NAME=${config.appName}
188
+ FELTDB_APP_ID=${config.appId}
189
+ FELTDB_VERSION=${config.version}
190
+ FELTDB_API_PORT=${config.port}
191
+ FELTDB_REPLICATION_PORT=${config.replicationPort}
192
+ FELTDB_PEERS=
193
+
194
+ # Application Configuration
195
+ APP_PORT=3000
196
+ FELTDB_API_URL=http://feltdb:7700
197
+
198
+ # Studio Configuration
199
+ STUDIO_PORT=${config.studioPort}
200
+
201
+ # Runtime
202
+ NODE_ENV=production
203
+ `;
204
+ }
205
+ export function generateHealthCheckScript() {
206
+ return `#!/bin/bash
207
+
208
+ # Multi-service health check orchestration
209
+ # Ensures all services are ready before marking deployment as healthy
210
+
211
+ set -e
212
+
213
+ TIMEOUT=60
214
+ ELAPSED=0
215
+ INTERVAL=2
216
+
217
+ echo "[Health Check] Starting orchestration check (timeout: \${TIMEOUT}s)"
218
+
219
+ # Wait for FeltDB
220
+ echo "[Health Check] Checking FeltDB service..."
221
+ while [ \$ELAPSED -lt \$TIMEOUT ]; do
222
+ if curl -s -f http://feltdb:7700/health > /dev/null 2>&1; then
223
+ STATE=\$(curl -s http://feltdb:7700/health | grep -o '"state":"[^"]*' | cut -d'"' -f4)
224
+ if [ "\$STATE" = "ready" ] || [ "\$STATE" = "recovering" ]; then
225
+ echo "[Health Check] FeltDB is \$STATE"
226
+ break
227
+ fi
228
+ fi
229
+ sleep \$INTERVAL
230
+ ELAPSED=\$((ELAPSED + INTERVAL))
231
+ done
232
+
233
+ if [ \$ELAPSED -ge \$TIMEOUT ]; then
234
+ echo "[Health Check] FAILED: FeltDB did not respond within timeout"
235
+ exit 1
236
+ fi
237
+
238
+ # Wait for application
239
+ echo "[Health Check] Checking application service..."
240
+ ELAPSED=0
241
+ while [ \$ELAPSED -lt \$TIMEOUT ]; do
242
+ if curl -s -f http://app:3000/health > /dev/null 2>&1; then
243
+ echo "[Health Check] Application is ready"
244
+ break
245
+ fi
246
+ sleep \$INTERVAL
247
+ ELAPSED=\$((ELAPSED + INTERVAL))
248
+ done
249
+
250
+ if [ \$ELAPSED -ge \$TIMEOUT ]; then
251
+ echo "[Health Check] FAILED: Application did not respond within timeout"
252
+ exit 1
253
+ fi
254
+
255
+ echo "[Health Check] All services healthy - deployment ready"
256
+ exit 0
257
+ `;
258
+ }
@@ -1,4 +1,4 @@
1
1
  // One release train keeps generated applications installable. The repository
2
2
  // validation script checks these values against every workspace manifest.
3
- export const FELTDB_PACKAGE_VERSION = '0.4.0';
3
+ export const FELTDB_PACKAGE_VERSION = '0.4.2';
4
4
  export const feltdbPackageRange = `^${FELTDB_PACKAGE_VERSION}`;
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Runtime-specific templates and configuration
3
+ *
4
+ * Generates runtime-appropriate file structure and configuration
5
+ * for Browser, Node, and Self-hosted deployment modes.
6
+ */
7
+ export function generateBrowserRuntime(config) {
8
+ return {
9
+ feltdbInit: `import { createFeltDB } from '@feltdb/core';
10
+
11
+ /**
12
+ * Browser Runtime
13
+ *
14
+ * Local-first FeltDB with durable IndexedDB storage.
15
+ * No server required. Full offline capability.
16
+ *
17
+ * AppID: ${config.appId}
18
+ */
19
+ export const db = createFeltDB({
20
+ namespace: '${config.appName}',
21
+ runtime: 'browser',
22
+ storage: {
23
+ type: 'indexeddb',
24
+ durable: true,
25
+ },
26
+ ${config.distributed ? `
27
+ replication: {
28
+ enabled: false, // Browser cannot initiate replication
29
+ },` : ''}
30
+ health: {
31
+ onStateChange: (state: 'starting' | 'recovering' | 'ready' | 'failed') => {
32
+ console.log(\`[FeltDB Health] Browser state changed: \${state}\`);
33
+ window.dispatchEvent(new CustomEvent('feltdb:health', { detail: { state } }));
34
+ },
35
+ },
36
+ });
37
+
38
+ // Collections
39
+ export const documents = db.collection('documents');
40
+ export const reports = db.collection('reports');
41
+ `,
42
+ envExample: `# Browser Runtime
43
+ # No environment configuration needed.
44
+ # Data persists locally in IndexedDB.
45
+ NODE_ENV=development
46
+ `,
47
+ };
48
+ }
49
+ export function generateNodeRuntime(config) {
50
+ return {
51
+ feltdbInit: `import { createFeltDB } from '@feltdb/core';
52
+ import path from 'path';
53
+ import { fileURLToPath } from 'url';
54
+ import fs from 'fs';
55
+
56
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
57
+
58
+ /**
59
+ * Node Runtime
60
+ *
61
+ * Server-side FeltDB with durable file-based storage.
62
+ * Can participate in distributed replication.
63
+ * Persistent operation log to /data directory.
64
+ *
65
+ * AppID: ${config.appId}
66
+ */
67
+
68
+ const dataDir = process.env.FELTDB_DATA_DIR || path.join(__dirname, '..', 'data');
69
+ if (!fs.existsSync(dataDir)) {
70
+ fs.mkdirSync(dataDir, { recursive: true });
71
+ }
72
+
73
+ export const db = createFeltDB({
74
+ namespace: '${config.appName}',
75
+ runtime: 'node',
76
+ storage: {
77
+ type: 'file',
78
+ path: dataDir,
79
+ durable: true,
80
+ fsync: true,
81
+ },
82
+ ${config.distributed ? `
83
+ replication: {
84
+ enabled: true,
85
+ port: parseInt(process.env.FELTDB_REPLICATION_PORT || '9000'),
86
+ },` : ''}
87
+ health: {
88
+ onStateChange: (state: 'starting' | 'recovering' | 'ready' | 'failed') => {
89
+ console.log(\`[FeltDB Health] Node state changed: \${state}\`);
90
+ // Emit to monitoring system
91
+ process.emit('feltdb:health', { state, timestamp: Date.now() });
92
+ },
93
+ },
94
+ });
95
+
96
+ // Collections
97
+ export const documents = db.collection('documents');
98
+ export const reports = db.collection('reports');
99
+
100
+ // Startup hook
101
+ export async function startup() {
102
+ console.log(\`[FeltDB] Starting Node runtime\`);
103
+ console.log(\`[FeltDB] App: ${config.appName} (${config.appId})\`);
104
+ console.log(\`[FeltDB] Version: ${config.version}\`);
105
+ console.log(\`[FeltDB] Data directory: \${dataDir}\`);
106
+ ${config.distributed ? `console.log(\`[FeltDB] Replication enabled on port \${process.env.FELTDB_REPLICATION_PORT || 9000}\`);` : ''}
107
+ await db.ready();
108
+ console.log(\`[FeltDB] Ready\`);
109
+ }
110
+ `,
111
+ envExample: `# Node Runtime
112
+ FELTDB_DATA_DIR=./data
113
+ FELTDB_REPLICATION_PORT=9000
114
+ NODE_ENV=development
115
+ `,
116
+ startupScript: `#!/usr/bin/env node
117
+
118
+ /**
119
+ * Node runtime startup orchestration
120
+ */
121
+
122
+ import { db, startup } from './feltdb.js';
123
+
124
+ async function main() {
125
+ try {
126
+ await startup();
127
+
128
+ // Start HTTP server for API
129
+ const { createServer } = await import('./server.js');
130
+ const server = createServer(db);
131
+ const port = process.env.FELTDB_API_PORT || 7700;
132
+ server.listen(port, () => {
133
+ console.log(\`[HTTP] API server listening on port \${port}\`);
134
+ });
135
+ } catch (err) {
136
+ console.error('[FeltDB] Startup failed:', err);
137
+ process.exit(1);
138
+ }
139
+ }
140
+
141
+ main();
142
+ `,
143
+ };
144
+ }
145
+ export function generateSelfHostedRuntime(config) {
146
+ return {
147
+ feltdbInit: `import { createFeltDB } from '@feltdb/core';
148
+ import path from 'path';
149
+ import { fileURLToPath } from 'url';
150
+ import fs from 'fs';
151
+
152
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
153
+
154
+ /**
155
+ * Self-hosted Runtime
156
+ *
157
+ * Containerized FeltDB with Docker Compose orchestration.
158
+ * Durable file-based storage with persistent volumes.
159
+ * Full replication topology with multi-node support.
160
+ *
161
+ * AppID: ${config.appId}
162
+ * Version: ${config.version}
163
+ */
164
+
165
+ const dataDir = process.env.FELTDB_DATA_DIR || '/data';
166
+ if (!fs.existsSync(dataDir)) {
167
+ fs.mkdirSync(dataDir, { recursive: true });
168
+ }
169
+
170
+ export const db = createFeltDB({
171
+ namespace: '${config.appName}',
172
+ runtime: 'self-hosted',
173
+ storage: {
174
+ type: 'file',
175
+ path: dataDir,
176
+ durable: true,
177
+ fsync: true,
178
+ },
179
+ replication: {
180
+ enabled: true,
181
+ port: 9000,
182
+ peers: (process.env.FELTDB_PEERS || '').split(',').filter(Boolean),
183
+ },
184
+ health: {
185
+ onStateChange: (state: 'starting' | 'recovering' | 'ready' | 'failed') => {
186
+ console.log(\`[\${new Date().toISOString()}] [FeltDB] \${state.toUpperCase()}\`);
187
+ // Structured logging for container orchestration
188
+ process.emit('feltdb:health', { state, timestamp: Date.now() });
189
+ },
190
+ },
191
+ });
192
+
193
+ // Collections
194
+ export const documents = db.collection('documents');
195
+ export const reports = db.collection('reports');
196
+
197
+ // Startup with health check loop
198
+ export async function startup() {
199
+ console.log(\`[\${new Date().toISOString()}] [FeltDB] Container starting\`);
200
+ console.log(\`[FeltDB] App: ${config.appName} (${config.appId})\`);
201
+ console.log(\`[FeltDB] Version: ${config.version}\`);
202
+ console.log(\`[FeltDB] Runtime: self-hosted\`);
203
+ console.log(\`[FeltDB] Data directory: \${dataDir}\`);
204
+
205
+ await db.ready();
206
+ console.log(\`[\${new Date().toISOString()}] [FeltDB] READY\`);
207
+ }
208
+ `,
209
+ envExample: `# Self-hosted Docker Runtime
210
+ FELTDB_DATA_DIR=/data
211
+ FELTDB_APP_NAME=${config.appName}
212
+ FELTDB_APP_ID=${config.appId}
213
+ FELTDB_VERSION=${config.version}
214
+ FELTDB_REPLICATION_PORT=9000
215
+ FELTDB_PEERS=
216
+ NODE_ENV=production
217
+ `,
218
+ healthCheck: `#!/bin/sh
219
+
220
+ # Health check for Docker container
221
+ # Verifies FeltDB is responding and not in RECOVERING state
222
+
223
+ HEALTH_URL="http://localhost:7700/health"
224
+ TIMEOUT=5
225
+
226
+ # Query health endpoint
227
+ RESPONSE=\$(curl -s -m \${TIMEOUT} \${HEALTH_URL} 2>/dev/null)
228
+
229
+ if [ -z "\$RESPONSE" ]; then
230
+ echo "UNHEALTHY: No response from health endpoint"
231
+ exit 1
232
+ fi
233
+
234
+ STATE=\$(echo \$RESPONSE | grep -o '"state":"[^"]*' | cut -d'"' -f4)
235
+
236
+ case "\$STATE" in
237
+ ready)
238
+ echo "HEALTHY: State is ready"
239
+ exit 0
240
+ ;;
241
+ recovering)
242
+ echo "STARTING: Recovering from disk"
243
+ exit 0
244
+ ;;
245
+ starting)
246
+ echo "STARTING: Initializing"
247
+ exit 0
248
+ ;;
249
+ failed)
250
+ echo "UNHEALTHY: State is failed"
251
+ exit 1
252
+ ;;
253
+ *)
254
+ echo "UNKNOWN: State is \$STATE"
255
+ exit 2
256
+ ;;
257
+ esac
258
+ `,
259
+ };
260
+ }
261
+ export function getTemplate(config) {
262
+ switch (config.runtime) {
263
+ case 'browser':
264
+ return generateBrowserRuntime(config);
265
+ case 'node':
266
+ return generateNodeRuntime(config);
267
+ case 'self-hosted':
268
+ return generateSelfHostedRuntime(config);
269
+ default:
270
+ throw new Error(`Unknown runtime: ${config.runtime}`);
271
+ }
272
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-feltdb",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Create a new FeltDB application with one command",
5
5
  "license": "MIT",
6
6
  "bin": {