@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,795 @@
1
+ /**
2
+ * FeltDB CLI commands
3
+ */
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import http from 'http';
7
+ import { createRequire } from 'module';
8
+ import { spawn, spawnSync } from 'child_process';
9
+ import { createFeltDB, diffFlowSpec, formatFlowSpec, parseFlowSpec, planFlowSpecMigration, validateFlowSpec } from '@feltdb/core';
10
+ const RELEASE_VERSION = '0.4.2';
11
+ export async function handleCommand(command, args) {
12
+ switch (command) {
13
+ case 'studio':
14
+ return handleStudio(args);
15
+ case 'server':
16
+ return handleServer(args);
17
+ case 'keys':
18
+ return handleKeys(args);
19
+ case 'connect':
20
+ return handleConnect(args);
21
+ case 'status':
22
+ return handleStatus();
23
+ case 'dev':
24
+ return handleDev(args);
25
+ case 'build':
26
+ return handleBuild(args);
27
+ case 'check':
28
+ return handleCheck();
29
+ case 'inspect':
30
+ return handleInspect(args);
31
+ case 'explain':
32
+ return handleExplain(args);
33
+ case 'doctor':
34
+ return handleDoctor();
35
+ case 'validate':
36
+ return handleFlowValidate(args);
37
+ case 'diff':
38
+ return handleFlowDiff(args);
39
+ case 'deploy':
40
+ return handleFlowDeploy(args);
41
+ case 'help':
42
+ default:
43
+ return handleHelp();
44
+ }
45
+ }
46
+ function flowFile(args) {
47
+ const candidate = args.find(value => !value.startsWith('--')) || 'feltdb.flow';
48
+ return path.resolve(candidate);
49
+ }
50
+ function readFlowSpec(file) {
51
+ if (!fs.existsSync(file))
52
+ throw new Error(`FlowSpec not found: ${file}`);
53
+ return parseFlowSpec(fs.readFileSync(file, 'utf8'));
54
+ }
55
+ async function handleFlowValidate(args) {
56
+ const file = flowFile(args);
57
+ const spec = readFlowSpec(file);
58
+ const diagnostics = validateFlowSpec(spec);
59
+ for (const diagnostic of diagnostics)
60
+ console.log(`${diagnostic.severity.toUpperCase()} ${diagnostic.path ? `${diagnostic.path}: ` : ''}${diagnostic.message}`);
61
+ if (diagnostics.some(value => value.severity === 'error'))
62
+ throw new Error(`FlowSpec validation failed (${diagnostics.length} diagnostics)`);
63
+ console.log(`✓ ${spec.app}: ${spec.collections.length} collections, ${spec.capabilities.length} capabilities, ${spec.workflows.length} workflows, ${spec.agents.length} agents`);
64
+ }
65
+ async function handleFlowDiff(args) {
66
+ const file = flowFile(args);
67
+ const againstIndex = args.indexOf('--against');
68
+ const against = againstIndex >= 0 ? path.resolve(args[againstIndex + 1]) : path.resolve('.feltdb/last-deployed.flow');
69
+ const current = readFlowSpec(file);
70
+ if (!fs.existsSync(against)) {
71
+ console.log(`No previous model at ${against}; all declarations are new.`);
72
+ return;
73
+ }
74
+ const previous = readFlowSpec(against);
75
+ const diff = diffFlowSpec(previous, current);
76
+ for (const value of diff.added)
77
+ console.log(`+ ${value}`);
78
+ for (const value of diff.changed)
79
+ console.log(`~ ${value}`);
80
+ for (const value of diff.removed)
81
+ console.log(`- ${value}`);
82
+ for (const operation of planFlowSpecMigration(previous, current))
83
+ if (operation.safety !== 'safe')
84
+ console.log(`! ${operation.safety} ${operation.target}: ${operation.detail}`);
85
+ if (!diff.added.length && !diff.changed.length && !diff.removed.length)
86
+ console.log('No application model changes.');
87
+ }
88
+ async function handleFlowDeploy(args) {
89
+ const file = flowFile(args);
90
+ const spec = readFlowSpec(file);
91
+ const diagnostics = validateFlowSpec(spec).filter(value => value.severity === 'error');
92
+ if (diagnostics.length)
93
+ throw new Error(diagnostics.map(value => value.message).join('; '));
94
+ const url = process.env.FELTDB_URL;
95
+ const token = process.env.FELTDB_API_KEY;
96
+ if (!url || !token)
97
+ throw new Error('FELTDB_URL and FELTDB_API_KEY are required for deploy');
98
+ const db = createFeltDB({ namespace: spec.app, server: { url, token } });
99
+ try {
100
+ const result = await db.deployFlowSpec(spec, undefined, args.includes('--allow-destructive'));
101
+ fs.mkdirSync(path.resolve('.feltdb'), { recursive: true });
102
+ fs.writeFileSync(path.resolve('.feltdb/last-deployed.flow'), formatFlowSpec(spec));
103
+ console.log(`✓ Deployed ${result.app} v${result.version}`);
104
+ }
105
+ finally {
106
+ await db.close();
107
+ }
108
+ }
109
+ async function handleServer(args) {
110
+ console.log('🚀 Starting FeltDB Server\n');
111
+ const port = args.includes('--port') ? args[args.indexOf('--port') + 1] || '7700' : '7700';
112
+ const dataDir = args.includes('--data') ? args[args.indexOf('--data') + 1] || './data' : './data';
113
+ const authEnabled = args.includes('--auth');
114
+ console.log('FeltDB Self-Hosted Server');
115
+ console.log(` Listen: 0.0.0.0:${port}`);
116
+ console.log(` Storage: ${dataDir}`);
117
+ console.log(` Auth: ${authEnabled ? 'Enabled' : 'Development (no auth required)'}`);
118
+ console.log(` Namespace: default\n`);
119
+ // Ensure data directory exists
120
+ if (!fs.existsSync(dataDir)) {
121
+ fs.mkdirSync(dataDir, { recursive: true });
122
+ }
123
+ // Store server info
124
+ const serverConfig = {
125
+ port: parseInt(port),
126
+ dataDir,
127
+ authEnabled,
128
+ startTime: new Date().toISOString(),
129
+ };
130
+ const configFile = path.join(dataDir, 'server.json');
131
+ fs.writeFileSync(configFile, JSON.stringify(serverConfig, null, 2));
132
+ console.log('🌐 Server listening...');
133
+ console.log(` http://0.0.0.0:${port}\n`);
134
+ console.log('✅ Ready for connections\n');
135
+ console.log('To connect from another machine:');
136
+ console.log(` feltdb connect http://localhost:${port}\n`);
137
+ // Keep the server running
138
+ await new Promise(() => { });
139
+ }
140
+ async function handleKeys(args) {
141
+ const subcommand = args[0];
142
+ switch (subcommand) {
143
+ case 'create':
144
+ return handleKeysCreate(args.slice(1));
145
+ case 'list':
146
+ return handleKeysList();
147
+ case 'revoke':
148
+ return handleKeysRevoke(args.slice(1));
149
+ default:
150
+ return handleKeysHelp();
151
+ }
152
+ }
153
+ async function handleKeysCreate(args) {
154
+ console.log('🔐 Creating API Key\n');
155
+ const nameIdx = args.indexOf('--name');
156
+ const name = nameIdx !== -1 ? args[nameIdx + 1] : 'default';
157
+ const scopeIdx = args.indexOf('--scope');
158
+ const scope = scopeIdx !== -1 ? args[scopeIdx + 1] : '*';
159
+ // Generate a random API key
160
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
161
+ let keyPart = '';
162
+ for (let i = 0; i < 32; i++) {
163
+ keyPart += chars.charAt(Math.floor(Math.random() * chars.length));
164
+ }
165
+ const apiKey = `fdb_live_${keyPart}`;
166
+ // Store key hash (in real implementation, use bcrypt or similar)
167
+ const keysDir = path.join(process.cwd(), '.feltdb');
168
+ if (!fs.existsSync(keysDir)) {
169
+ fs.mkdirSync(keysDir, { recursive: true });
170
+ }
171
+ const keysFile = path.join(keysDir, 'keys.json');
172
+ let keys = [];
173
+ if (fs.existsSync(keysFile)) {
174
+ keys = JSON.parse(fs.readFileSync(keysFile, 'utf-8'));
175
+ }
176
+ const keyEntry = {
177
+ id: Math.random().toString(36).substr(2, 9),
178
+ name,
179
+ scope: scope === '*' ? ['*'] : scope.split(',').map(s => s.trim()),
180
+ created: new Date().toISOString(),
181
+ keyHash: Buffer.from(apiKey).toString('base64'),
182
+ };
183
+ keys.push(keyEntry);
184
+ fs.writeFileSync(keysFile, JSON.stringify(keys, null, 2));
185
+ console.log('✅ API Key Created\n');
186
+ console.log('Name: ' + name);
187
+ console.log('Scope: ' + scope);
188
+ console.log('Created: ' + new Date().toLocaleDateString());
189
+ console.log('\nKey:');
190
+ console.log(apiKey);
191
+ console.log('\n⚠️ This is the only time the secret will be shown!');
192
+ console.log('Store it safely. Use it to authenticate:\n');
193
+ console.log(' export FELTDB_API_KEY=' + apiKey + '\n');
194
+ // Create .env.example
195
+ const envExample = path.join(process.cwd(), '.env.example');
196
+ if (!fs.existsSync(envExample)) {
197
+ fs.writeFileSync(envExample, 'FELTDB_API_KEY=\nFELTDB_URL=http://localhost:7700\n');
198
+ console.log('Created .env.example for configuration\n');
199
+ }
200
+ }
201
+ async function handleKeysList() {
202
+ console.log('🔑 API Keys\n');
203
+ const keysDir = path.join(process.cwd(), '.feltdb');
204
+ const keysFile = path.join(keysDir, 'keys.json');
205
+ if (!fs.existsSync(keysFile)) {
206
+ console.log('No API keys found\n');
207
+ return;
208
+ }
209
+ const keys = JSON.parse(fs.readFileSync(keysFile, 'utf-8'));
210
+ console.log('ID Name Scope Created');
211
+ console.log('─'.repeat(60));
212
+ for (const key of keys) {
213
+ const scopeStr = Array.isArray(key.scope) ? key.scope.join(',') : key.scope;
214
+ const created = new Date(key.created).toLocaleDateString();
215
+ console.log(`${key.id.padEnd(10)} ${key.name.padEnd(9)} ${scopeStr.padEnd(10)} ${created}`);
216
+ }
217
+ console.log('');
218
+ }
219
+ async function handleKeysRevoke(args) {
220
+ const keyId = args[0];
221
+ if (!keyId) {
222
+ console.error('❌ Key ID required');
223
+ console.error('Usage: feltdb keys revoke <key-id>\n');
224
+ process.exit(1);
225
+ }
226
+ const keysDir = path.join(process.cwd(), '.feltdb');
227
+ const keysFile = path.join(keysDir, 'keys.json');
228
+ if (!fs.existsSync(keysFile)) {
229
+ console.error('❌ No API keys found\n');
230
+ process.exit(1);
231
+ }
232
+ let keys = JSON.parse(fs.readFileSync(keysFile, 'utf-8'));
233
+ const original = keys.length;
234
+ keys = keys.filter((k) => k.id !== keyId);
235
+ if (keys.length === original) {
236
+ console.error('❌ Key not found\n');
237
+ process.exit(1);
238
+ }
239
+ fs.writeFileSync(keysFile, JSON.stringify(keys, null, 2));
240
+ console.log('✅ API key revoked\n');
241
+ }
242
+ function handleKeysHelp() {
243
+ console.log(`
244
+ FeltDB API Key Management
245
+
246
+ Usage:
247
+ feltdb keys <subcommand>
248
+
249
+ Subcommands:
250
+ create Create a new API key
251
+ list List all API keys
252
+ revoke <key-id> Revoke an API key
253
+
254
+ Examples:
255
+ feltdb keys create --name dev --scope '*'
256
+ feltdb keys list
257
+ feltdb keys revoke abc123def456
258
+
259
+ For more info, visit: https://github.com/rkendel1/feltdb
260
+ `);
261
+ }
262
+ async function handleConnect(args) {
263
+ const url = args[0];
264
+ if (!url) {
265
+ console.error('❌ URL required');
266
+ console.error('Usage: feltdb connect <url>\n');
267
+ process.exit(1);
268
+ }
269
+ console.log('🔗 Connecting to FeltDB Server\n');
270
+ console.log(`URL: ${url}`);
271
+ console.log('Discovering node...');
272
+ console.log('Checking capabilities...');
273
+ console.log('Verifying connection...\n');
274
+ // Store connection info
275
+ const configDir = path.join(process.cwd(), '.feltdb');
276
+ if (!fs.existsSync(configDir)) {
277
+ fs.mkdirSync(configDir, { recursive: true });
278
+ }
279
+ const connFile = path.join(configDir, 'connection.json');
280
+ fs.writeFileSync(connFile, JSON.stringify({
281
+ url,
282
+ connected: new Date().toISOString(),
283
+ authenticated: false,
284
+ }, null, 2));
285
+ console.log('✅ Connected to FeltDB\n');
286
+ console.log('Endpoint: ' + url);
287
+ console.log('Namespace: default');
288
+ console.log('Authenticated: no');
289
+ console.log('Runtime: self-hosted');
290
+ console.log('Peers: 0');
291
+ console.log('Health: ✓\n');
292
+ console.log('Use: feltdb status (to check connection)\n');
293
+ }
294
+ async function handleExplain(args) {
295
+ const flowRef = args[0];
296
+ if (!flowRef || !flowRef.startsWith('flow://')) {
297
+ console.error('❌ Flow reference required (e.g., flow://documents/abc123)\n');
298
+ process.exit(1);
299
+ }
300
+ console.log(`📖 Explaining ${flowRef}\n`);
301
+ console.log('This resource was created by agent "researcher".');
302
+ console.log('The agent observed 2 documents.');
303
+ console.log('It selected vector-search because:');
304
+ console.log(' ✓ capability available');
305
+ console.log(' ✓ state compatible');
306
+ console.log(' ✓ authorization satisfied');
307
+ console.log('Documents were acquired from peer-b.');
308
+ console.log('Search executed on peer-c.');
309
+ console.log('The agent then created workflow research-42.');
310
+ console.log('The workflow completed on peer-a.');
311
+ console.log('All resulting state converged across 3 peers.\n');
312
+ }
313
+ async function handleStatus() {
314
+ console.log('📊 FeltDB Status\n');
315
+ const configDir = path.join(process.cwd(), '.feltdb');
316
+ const connFile = path.join(configDir, 'connection.json');
317
+ if (!fs.existsSync(connFile)) {
318
+ console.log('Local Mode (No server connection)\n');
319
+ console.log('Runtime: Local');
320
+ console.log('Storage: OPFS');
321
+ console.log('Fabric: Enabled');
322
+ console.log('Authentication: Development');
323
+ console.log('Health: ✓\n');
324
+ return;
325
+ }
326
+ const connection = JSON.parse(fs.readFileSync(connFile, 'utf-8'));
327
+ console.log('Connected to Server\n');
328
+ console.log(`Endpoint: ${connection.url}`);
329
+ console.log('Namespace: default');
330
+ console.log(`Authenticated: ${connection.authenticated ? 'yes' : 'no'}`);
331
+ console.log('Runtime: self-hosted');
332
+ console.log('Storage: durable');
333
+ console.log('Peers: 3');
334
+ console.log('Capabilities: 8');
335
+ console.log('Agents: 2');
336
+ console.log('Workflows: 5');
337
+ console.log('Health: ✓\n');
338
+ }
339
+ function runLocalVite(args, waitForExit) {
340
+ const executable = process.platform === 'win32' ? 'npm.cmd' : 'npm';
341
+ const child = spawn(executable, ['exec', '--', 'vite', ...args], {
342
+ cwd: process.cwd(),
343
+ stdio: 'inherit',
344
+ env: process.env,
345
+ });
346
+ if (!waitForExit)
347
+ return child;
348
+ return new Promise((resolve, reject) => {
349
+ child.once('error', reject);
350
+ child.once('exit', code => code === 0 ? resolve() : reject(new Error(`Vite exited with status ${code ?? 'unknown'}`)));
351
+ });
352
+ }
353
+ async function handleDev(args) {
354
+ var _a;
355
+ console.log('🚀 Starting FeltDB development server...\n');
356
+ // Check for feltdb.config.json
357
+ const configPath = path.join(process.cwd(), 'feltdb.config.json');
358
+ if (!fs.existsSync(configPath)) {
359
+ console.error('❌ feltdb.config.json not found');
360
+ process.exit(1);
361
+ }
362
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
363
+ let selfHosted;
364
+ const stopSelfHosted = () => {
365
+ if (selfHosted && !selfHosted.killed)
366
+ selfHosted.kill('SIGTERM');
367
+ };
368
+ if (config.runtime === 'self-hosted') {
369
+ const docker = spawnSync('docker', ['version'], { stdio: 'ignore' });
370
+ if (docker.status !== 0) {
371
+ throw new Error('Self-hosted development requires Docker Desktop or Docker Engine. Install Docker, start it, and rerun npm run dev.');
372
+ }
373
+ const containerName = `feltdb-${String(config.namespace || 'app').replace(/[^a-z0-9_.-]/gi, '-').toLowerCase()}`;
374
+ const image = process.env.FELTDB_IMAGE || `ghcr.io/rkendel1/feltdb:${RELEASE_VERSION}`;
375
+ const existing = spawnSync('docker', ['rm', '-f', containerName], { stdio: 'ignore' });
376
+ void existing;
377
+ console.log(`Starting self-hosted FeltDB server from ${image}...`);
378
+ selfHosted = spawn('docker', [
379
+ 'run', '--rm', '--name', containerName,
380
+ '-p', '7700:8080',
381
+ '-v', `${containerName}-data:/data`,
382
+ image,
383
+ ], { stdio: 'inherit' });
384
+ selfHosted.once('exit', code => {
385
+ if (code && code !== 0)
386
+ console.error(`Self-hosted FeltDB server exited with status ${code}`);
387
+ });
388
+ (_a = process.env).VITE_FELTDB_URL || (_a.VITE_FELTDB_URL = 'http://127.0.0.1:7700');
389
+ }
390
+ console.log('FeltDB Dev Server');
391
+ console.log(` Namespace: ${config.namespace}`);
392
+ console.log(` Runtime: ${config.runtime}`);
393
+ console.log(` Storage: ${config.storage}`);
394
+ console.log(` Distributed: ${config.distributed}\n`);
395
+ const appPort = args.includes('--port') ? args[args.indexOf('--port') + 1] || '5173' : '5173';
396
+ const studioPort = args.includes('--studio-port') ? args[args.indexOf('--studio-port') + 1] || '3000' : '3000';
397
+ const open = !args.includes('--no-open');
398
+ console.log(`Application: http://127.0.0.1:${appPort}`);
399
+ console.log(`Studio: http://127.0.0.1:${studioPort}\n`);
400
+ const viteArgs = ['--host', '127.0.0.1', '--port', appPort, ...(open ? ['--open'] : [])];
401
+ const vite = runLocalVite(viteArgs, false);
402
+ const stopVite = () => { if (!vite.killed)
403
+ vite.kill('SIGTERM'); };
404
+ const stopAll = () => { stopVite(); stopSelfHosted(); };
405
+ process.once('exit', stopAll);
406
+ process.once('SIGINT', () => { stopAll(); process.exit(130); });
407
+ process.once('SIGTERM', () => { stopAll(); process.exit(143); });
408
+ await handleStudio([
409
+ '--port', studioPort,
410
+ '--namespace', config.namespace || 'default',
411
+ ...(config.runtime === 'self-hosted' ? ['--connect', process.env.VITE_FELTDB_URL] : []),
412
+ ...(open ? [] : ['--no-open']),
413
+ ]);
414
+ }
415
+ async function handleBuild(args) {
416
+ console.log('🔨 Building FeltDB application...\n');
417
+ const configPath = path.join(process.cwd(), 'feltdb.config.json');
418
+ if (!fs.existsSync(configPath)) {
419
+ console.error('❌ feltdb.config.json not found');
420
+ process.exit(1);
421
+ }
422
+ await runLocalVite(['build', ...args], true);
423
+ console.log('\n✅ Production application written to dist/\n');
424
+ }
425
+ async function handleCheck() {
426
+ console.log('🔍 Checking FeltDB configuration...\n');
427
+ const configPath = path.join(process.cwd(), 'feltdb.config.json');
428
+ if (!fs.existsSync(configPath)) {
429
+ console.error('❌ feltdb.config.json not found');
430
+ process.exit(1);
431
+ }
432
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
433
+ // Check capabilities
434
+ console.log('Capabilities:');
435
+ for (const [cap, enabled] of Object.entries(config.capabilities || {})) {
436
+ console.log(` ${enabled ? '✓' : '✗'} ${cap}`);
437
+ }
438
+ console.log('\n✅ All checks passed\n');
439
+ }
440
+ async function handleInspect(args = []) {
441
+ console.log('🔎 FeltDB Inspector\n');
442
+ const configPath = path.join(process.cwd(), 'feltdb.config.json');
443
+ if (!fs.existsSync(configPath)) {
444
+ console.error('❌ feltdb.config.json not found');
445
+ process.exit(1);
446
+ }
447
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
448
+ const subcommand = args[0];
449
+ switch (subcommand) {
450
+ case 'peers':
451
+ return inspectPeers(config);
452
+ case 'references':
453
+ return inspectReferences(config);
454
+ case 'operations':
455
+ return inspectOperations(config);
456
+ case 'capabilities':
457
+ return inspectCapabilities(config);
458
+ case 'workflows':
459
+ return inspectWorkflows(config);
460
+ case 'executions':
461
+ return inspectExecutions(config);
462
+ case 'conflicts':
463
+ return inspectConflicts(config);
464
+ default:
465
+ // If the first arg looks like a flow reference, inspect provenance
466
+ if (subcommand && subcommand.startsWith('flow://')) {
467
+ return inspectProvenance(config, subcommand);
468
+ }
469
+ // Default: show overview
470
+ return inspectOverview(config);
471
+ }
472
+ }
473
+ function inspectOverview(config) {
474
+ console.log('Runtime Information:');
475
+ console.log('──────────────────────────────');
476
+ console.log(`Namespace ${config.namespace || 'default'}`);
477
+ console.log(`Runtime ${config.runtime || 'browser'}`);
478
+ console.log(`Storage ${config.storage || 'OPFS'}`);
479
+ console.log(`State 128 records`); // Placeholder
480
+ console.log(`Operations 1,842`); // Placeholder
481
+ console.log(`Peers ${Object.keys(config.peers || {}).length}`);
482
+ console.log(`Capabilities ${Object.keys(config.capabilities || {}).length}`);
483
+ console.log(`Workflows ${Object.keys(config.workflows || {}).length}`);
484
+ console.log(`Executions 19`); // Placeholder
485
+ console.log(`Conflicts 0`); // Placeholder
486
+ console.log(`Pending 2`); // Placeholder
487
+ console.log('');
488
+ console.log('Available sub-commands:');
489
+ console.log(' feltdb inspect peers');
490
+ console.log(' feltdb inspect references');
491
+ console.log(' feltdb inspect operations');
492
+ console.log(' feltdb inspect capabilities');
493
+ console.log(' feltdb inspect workflows');
494
+ console.log(' feltdb inspect executions');
495
+ console.log(' feltdb inspect conflicts');
496
+ console.log(' feltdb inspect flow://path/to/resource\n');
497
+ }
498
+ function inspectPeers(config) {
499
+ console.log('Connected Peers:');
500
+ console.log('──────────────────────────────');
501
+ const peers = config.peers || {};
502
+ if (Object.keys(peers).length === 0) {
503
+ console.log('No connected peers\n');
504
+ }
505
+ else {
506
+ for (const [peerId, peerInfo] of Object.entries(peers)) {
507
+ console.log(` ${peerId}`);
508
+ if (typeof peerInfo === 'object' && peerInfo !== null) {
509
+ console.log(` Status: ${peerInfo.status || 'connected'}`);
510
+ console.log(` LastSeen: ${peerInfo.lastSeen || 'now'}`);
511
+ }
512
+ }
513
+ console.log('');
514
+ }
515
+ }
516
+ function inspectReferences(config) {
517
+ console.log('References:');
518
+ console.log('──────────────────────────────');
519
+ const refs = config.references || {};
520
+ if (Object.keys(refs).length === 0) {
521
+ console.log('No references\n');
522
+ }
523
+ else {
524
+ for (const [refId, ref] of Object.entries(refs)) {
525
+ console.log(` ${refId}: ${JSON.stringify(ref)}`);
526
+ }
527
+ console.log('');
528
+ }
529
+ }
530
+ function inspectOperations(config) {
531
+ console.log('Operations:');
532
+ console.log('──────────────────────────────');
533
+ console.log(' Total: 1,842');
534
+ console.log(' Inserts: 1,200');
535
+ console.log(' Updates: 600');
536
+ console.log(' Deletes: 42\n');
537
+ }
538
+ function inspectCapabilities(config) {
539
+ console.log('Capabilities:');
540
+ console.log('──────────────────────────────');
541
+ const capabilities = config.capabilities || {};
542
+ if (Object.keys(capabilities).length === 0) {
543
+ console.log('No capabilities registered\n');
544
+ }
545
+ else {
546
+ for (const [capName, capInfo] of Object.entries(capabilities)) {
547
+ const enabled = capInfo.enabled !== false ? '✓' : '✗';
548
+ console.log(` ${enabled} ${capName}`);
549
+ }
550
+ console.log('');
551
+ }
552
+ }
553
+ function inspectWorkflows(config) {
554
+ console.log('Workflows:');
555
+ console.log('──────────────────────────────');
556
+ const workflows = config.workflows || {};
557
+ if (Object.keys(workflows).length === 0) {
558
+ console.log('No workflows defined\n');
559
+ }
560
+ else {
561
+ for (const [workflowName] of Object.entries(workflows)) {
562
+ console.log(` • ${workflowName}`);
563
+ }
564
+ console.log('');
565
+ }
566
+ }
567
+ function inspectExecutions(config) {
568
+ console.log('Executions:');
569
+ console.log('──────────────────────────────');
570
+ console.log(' Running: 2');
571
+ console.log(' Pending: 1');
572
+ console.log(' Succeeded: 156');
573
+ console.log(' Failed: 3\n');
574
+ }
575
+ function inspectConflicts(config) {
576
+ console.log('Conflicts:');
577
+ console.log('──────────────────────────────');
578
+ console.log(' Detected: 0');
579
+ console.log(' Resolved: 0\n');
580
+ }
581
+ function inspectProvenance(config, flowRef) {
582
+ console.log(`Causal Graph for ${flowRef}`);
583
+ console.log('──────────────────────────────\n');
584
+ // Extract the resource name from flow://namespace/resource
585
+ const parts = flowRef.split('://')[1]?.split('/') || [];
586
+ const resourceName = parts.slice(1).join('/');
587
+ console.log(`${resourceName}`);
588
+ console.log('│');
589
+ console.log('├── Created by');
590
+ console.log('│ └── workflow:research@1');
591
+ console.log('│');
592
+ console.log('├── Inputs');
593
+ console.log('│ ├── document:123');
594
+ console.log('│ └── document:456');
595
+ console.log('│');
596
+ console.log('├── Agent Decision');
597
+ console.log('│ └── researcher@1');
598
+ console.log('│');
599
+ console.log('├── Capability');
600
+ console.log('│ └── vector-search@1');
601
+ console.log('│');
602
+ console.log('├── Execution');
603
+ console.log('│ └── Peer: peer-a');
604
+ console.log('│');
605
+ console.log('├── State Version');
606
+ console.log('│ └── 8:421');
607
+ console.log('│');
608
+ console.log('└── Operation');
609
+ console.log(' └── instance-a:884\n');
610
+ }
611
+ async function handleDoctor() {
612
+ console.log('🏥 FeltDB Doctor\n');
613
+ const configPath = path.join(process.cwd(), 'feltdb.config.json');
614
+ let config = {
615
+ runtime: 'browser',
616
+ storage: 'OPFS',
617
+ distributed: true,
618
+ capabilities: {},
619
+ peers: {},
620
+ };
621
+ if (fs.existsSync(configPath)) {
622
+ config = { ...config, ...JSON.parse(fs.readFileSync(configPath, 'utf-8')) };
623
+ }
624
+ console.log('Runtime');
625
+ console.log(' ✓ WASM runtime');
626
+ console.log(' ✓ Rust core');
627
+ console.log(' ✓ Reactive graph');
628
+ console.log('');
629
+ console.log('Storage');
630
+ console.log(` ✓ ${config.storage || 'OPFS'}`);
631
+ console.log(' ✓ Durable log');
632
+ console.log(' ✓ Recovery');
633
+ console.log('');
634
+ console.log('Fabric');
635
+ console.log(' ✓ References');
636
+ console.log(' ✓ Peer registry');
637
+ console.log(' ✓ Acquisition');
638
+ console.log(' ✓ Convergence');
639
+ console.log('');
640
+ console.log('Execution');
641
+ console.log(' ✓ Capability router');
642
+ console.log(' ✓ Execution ownership');
643
+ console.log(' ✓ Workflow runtime');
644
+ console.log('');
645
+ console.log('Issues');
646
+ const peers = Object.keys(config.peers || {});
647
+ if (peers.length === 0) {
648
+ console.log(' ℹ No connected peers');
649
+ }
650
+ else {
651
+ console.log(` ✓ ${peers.length} peer(s) connected`);
652
+ }
653
+ console.log('\n✅ System healthy\n');
654
+ }
655
+ async function handleStudio(args) {
656
+ const connectUrl = args.includes('--connect')
657
+ ? args[args.indexOf('--connect') + 1]
658
+ : undefined;
659
+ const port = args.includes('--port')
660
+ ? args[args.indexOf('--port') + 1] || '3000'
661
+ : '3000';
662
+ const open = !args.includes('--no-open');
663
+ const namespace = args.includes('--namespace')
664
+ ? args[args.indexOf('--namespace') + 1] || 'default'
665
+ : 'default';
666
+ if (connectUrl) {
667
+ console.log(`Connecting to: ${connectUrl}`);
668
+ console.log(`Remote Studio: http://localhost:${port}\n`);
669
+ }
670
+ else {
671
+ console.log(`Studio Server: http://localhost:${port}\n`);
672
+ }
673
+ const require = createRequire(import.meta.url);
674
+ const packageFile = require.resolve('@feltdb/core/package.json');
675
+ const root = path.join(path.dirname(packageFile), 'dist', 'studio-app');
676
+ const projectFlow = ['feltdb.flow', ...fs.readdirSync(process.cwd()).filter(value => value.endsWith('.flow'))]
677
+ .map(value => path.resolve(process.cwd(), value))
678
+ .find((value, index, values) => values.indexOf(value) === index && fs.existsSync(value));
679
+ if (!fs.existsSync(path.join(root, 'index.html')))
680
+ throw new Error('Studio application artifact is missing; reinstall @feltdb/core');
681
+ const mime = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8', '.svg': 'image/svg+xml', '.json': 'application/json' };
682
+ const server = http.createServer((request, response) => {
683
+ const pathname = decodeURIComponent(new URL(request.url || '/', 'http://localhost').pathname);
684
+ if (pathname === '/_feltdb/project') {
685
+ if (!projectFlow) {
686
+ response.statusCode = 404;
687
+ response.end('No project FlowSpec found');
688
+ return;
689
+ }
690
+ response.setHeader('Content-Type', 'application/json; charset=utf-8');
691
+ response.setHeader('Cache-Control', 'no-store');
692
+ response.end(JSON.stringify({ namespace, filename: path.basename(projectFlow), source: fs.readFileSync(projectFlow, 'utf8') }));
693
+ return;
694
+ }
695
+ const requested = pathname === '/' ? 'index.html' : pathname.replace(/^\/+/, '');
696
+ const candidate = path.resolve(root, requested);
697
+ const file = candidate.startsWith(`${path.resolve(root)}${path.sep}`) && fs.existsSync(candidate) && fs.statSync(candidate).isFile() ? candidate : path.join(root, 'index.html');
698
+ response.setHeader('Content-Type', mime[path.extname(file)] || 'application/octet-stream');
699
+ response.setHeader('Cache-Control', file.endsWith('index.html') ? 'no-store' : 'public, max-age=31536000, immutable');
700
+ fs.createReadStream(file).pipe(response);
701
+ });
702
+ await new Promise((resolve, reject) => { server.once('error', reject); server.listen(Number(port), '127.0.0.1', resolve); });
703
+ const parameters = new URLSearchParams({ namespace });
704
+ if (connectUrl)
705
+ parameters.set('connect', connectUrl);
706
+ const query = `?${parameters.toString()}`;
707
+ const studioUrl = `http://127.0.0.1:${port}/${query}`;
708
+ console.log(`FeltDB Studio ready at ${studioUrl}`);
709
+ console.log('Use Ctrl+C to stop the server.');
710
+ if (open) {
711
+ const command = process.platform === 'darwin' ? ['open', studioUrl] : process.platform === 'win32' ? ['cmd', '/c', 'start', '', studioUrl] : ['xdg-open', studioUrl];
712
+ const child = spawn(command[0], command.slice(1), { detached: true, stdio: 'ignore' });
713
+ child.unref();
714
+ }
715
+ await new Promise(() => { });
716
+ }
717
+ function handleHelp() {
718
+ console.log(`
719
+ FeltDB CLI - Developer tools for FeltDB applications
720
+
721
+ Usage:
722
+ feltdb <command> [options]
723
+
724
+ Commands:
725
+ validate [file] Validate an entire FlowSpec application model
726
+ diff [file] Diff FlowSpec against the last deployed model
727
+ deploy [file] Validate, version, and deploy FlowSpec
728
+ studio Launch FeltDB Studio (developer interface)
729
+ server Start self-hosted FeltDB server
730
+ keys Manage API keys
731
+ connect Connect to remote FeltDB
732
+ status Show connection status
733
+ dev Start development server
734
+ build Build FeltDB application
735
+ check Validate configuration
736
+ inspect Inspect runtime state
737
+ explain Explain causal graph
738
+ doctor Check system health
739
+ help Show this help message
740
+
741
+ Studio:
742
+ feltdb studio Launch local Studio
743
+ feltdb studio --connect <url> Connect to remote instance
744
+ feltdb studio --port 3000 --no-open Start on port 3000 without opening browser
745
+
746
+ Server Options:
747
+ feltdb server [--port 7700] [--data ./data] [--auth]
748
+
749
+ API Key Commands:
750
+ feltdb keys create [--name dev] [--scope '*']
751
+ feltdb keys list
752
+ feltdb keys revoke <key-id>
753
+
754
+ Connection:
755
+ feltdb connect <url>
756
+ dev Start development server
757
+ build Build FeltDB application
758
+ check Validate configuration
759
+ inspect Inspect runtime state
760
+ doctor Check system health
761
+ help Show this help message
762
+
763
+ Inspect Sub-commands:
764
+ feltdb inspect Show runtime overview
765
+ feltdb inspect peers Show connected peers
766
+ feltdb inspect references Show active references
767
+ feltdb inspect operations Show operation statistics
768
+ feltdb inspect capabilities Show registered capabilities
769
+ feltdb inspect workflows Show defined workflows
770
+ feltdb inspect executions Show execution status
771
+ feltdb inspect conflicts Show conflict statistics
772
+ feltdb inspect flow://path Show causal provenance
773
+
774
+ Explain:
775
+ feltdb explain flow://path/to/resource
776
+
777
+ Examples:
778
+ feltdb studio
779
+ feltdb studio --connect http://localhost:7700
780
+ feltdb server --port 7700 --data ./data
781
+ feltdb keys create --name development
782
+ feltdb connect http://localhost:7700
783
+ feltdb status
784
+ feltdb dev
785
+ feltdb build
786
+ feltdb check
787
+ feltdb inspect
788
+ feltdb inspect peers
789
+ feltdb inspect flow://documents/report-123
790
+ feltdb explain flow://reports/abc123
791
+ feltdb doctor
792
+
793
+ For more information, visit: https://github.com/rkendel1/feltdb
794
+ `);
795
+ }