@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.
- package/README.md +9 -0
- package/bin/create-feltdb.js +3 -0
- package/bin/feltdb.js +3 -0
- package/dist/analytics-backend.js +1 -1
- package/dist/cli/api-client.js +236 -0
- package/dist/cli/cli.js +18 -0
- package/dist/cli/commands.js +795 -0
- package/dist/cli/config.js +66 -0
- package/dist/cli/index.js +399 -0
- package/dist/create/application-identity.js +132 -0
- package/dist/create/cli-scripts-generator.js +211 -0
- package/dist/create/cli.js +251 -0
- package/dist/create/create.js +1862 -0
- package/dist/create/docker-compose-generator.js +258 -0
- package/dist/create/index.js +4 -0
- package/dist/create/package-versions.js +4 -0
- package/dist/create/runtime-templates.js +272 -0
- package/dist/index-backend.js +1 -1
- package/dist/react/useFeltDB.d.ts +1 -1
- package/dist/react/useFeltDB.js +1 -1
- package/dist/studio/KeyManagementPanel-B0s0xAXz.js +298 -0
- package/dist/studio/components/KeyManagementPanel.d.ts.map +1 -1
- package/dist/studio/components/KeyManagementPanel.js +1 -1
- package/dist/studio/components/index.js +2 -2
- package/dist/studio/{components-BRYUceo9.js → components-BAycgZhP.js} +1 -1
- package/dist/studio/index.js +2 -2
- package/dist/studio-app/assets/{feltdb_wasm-BXMn9UxO.js → feltdb_wasm-Bb1Pg6qz.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-2_wVudcZ.wasm +0 -0
- package/dist/studio-app/assets/{index-B-lZjdtI.js → index-DKVLtS37.js} +2 -2
- package/dist/studio-app/index.html +1 -1
- package/dist/wasm/feltdb_wasm.d.ts +461 -0
- package/dist/wasm/feltdb_wasm.js +1690 -0
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/wasm/feltdb_wasm_bg.wasm.d.ts +84 -0
- package/dist/wasm/package.json +16 -0
- package/package.json +11 -5
- package/dist/studio/KeyManagementPanel-BOvWTRyH.js +0 -246
- 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
|
+
}
|