@feltdb/core 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application-development.d.ts +76 -0
- package/dist/application-development.d.ts.map +1 -0
- package/dist/application-development.js +137 -0
- package/dist/application-manifest.d.ts +5 -1
- package/dist/application-manifest.d.ts.map +1 -1
- package/dist/application-manifest.js +10 -3
- package/dist/canonical-application.d.ts +26 -0
- package/dist/canonical-application.d.ts.map +1 -0
- package/dist/canonical-application.js +45 -0
- package/dist/cli/ai-model-config.js +97 -0
- package/dist/cli/commands.js +485 -30
- package/dist/cli/index.js +1 -1
- package/dist/cli/proposal-client.js +54 -0
- package/dist/cli/publish-engine.js +162 -0
- package/dist/cli/source-sync.js +139 -0
- package/dist/create/cli.js +1 -1
- package/dist/create/create.js +30 -6
- package/dist/create/managed-account.js +14 -7
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/crates/feltdb/src/application.rs +31 -1
- package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +847 -0
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +338 -29
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +72 -34
- package/dist/create/server-source/crates/feltdb/src/lib.rs +1 -0
- package/dist/create/server-source/crates/feltdb-server/src/identity.rs +46 -3
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +315 -16
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +20 -0
- package/dist/create/template/default-project/README.md +57 -0
- package/dist/create/template/default-project/agents/activity-assistant.ts +18 -0
- package/dist/create/template/default-project/agents/project-assistant.ts +19 -0
- package/dist/create/template/default-project/capabilities/activity-summary.ts +14 -0
- package/dist/create/template/default-project/capabilities/project-search.ts +16 -0
- package/dist/create/template/default-project/capabilities/project-summary.ts +15 -0
- package/dist/create/template/default-project/feltdb.flow +169 -0
- package/dist/create/template/default-project/src/App.tsx +20 -0
- package/dist/create/template/default-project/src/context/AuthContext.tsx +27 -0
- package/dist/create/template/default-project/src/feltdb.ts +87 -0
- package/dist/create/template/default-project/src/index.tsx +14 -0
- package/dist/create/template/default-project/src/pages/Activity.tsx +7 -0
- package/dist/create/template/default-project/src/pages/Agents.tsx +15 -0
- package/dist/create/template/default-project/src/pages/Dashboard.tsx +17 -0
- package/dist/create/template/default-project/src/pages/Invitations.tsx +11 -0
- package/dist/create/template/default-project/src/pages/Projects.tsx +11 -0
- package/dist/create/template/default-project/src/pages/SignIn.tsx +8 -0
- package/dist/create/template/default-project/src/pages/SignUp.tsx +8 -0
- package/dist/create/template/default-project/src/styles-application.css +21 -0
- package/dist/create/template/default-project/src/styles.css +1 -0
- package/dist/create/template/default-project/workflows/agent-assisted-summary.ts +1 -0
- package/dist/create/template/default-project/workflows/invitation.ts +30 -0
- package/dist/create/template/default-project/workflows/project-created.ts +2 -0
- package/dist/db.d.ts +49 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +77 -0
- package/dist/error-codes.d.ts +20 -1
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +25 -0
- package/dist/flowspec.d.ts.map +1 -1
- package/dist/flowspec.js +27 -6
- package/dist/http-db.d.ts +15 -2
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +113 -5
- package/dist/index-core.d.ts +3 -0
- package/dist/index-core.d.ts.map +1 -1
- package/dist/index-core.js +3 -0
- package/dist/proposal.d.ts +85 -0
- package/dist/proposal.d.ts.map +1 -0
- package/dist/proposal.js +105 -0
- package/dist/studio/app.d.ts +5 -1
- package/dist/studio/app.d.ts.map +1 -1
- package/dist/studio/components/ApplicationDesigner.d.ts +10 -1
- package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
- package/dist/studio/components/AskFeltDB.d.ts +20 -0
- package/dist/studio/components/AskFeltDB.d.ts.map +1 -0
- package/dist/studio/components/ProposalBrowser.d.ts +3 -0
- package/dist/studio/components/ProposalBrowser.d.ts.map +1 -0
- package/dist/studio/components/index.d.ts +2 -0
- package/dist/studio/components/index.d.ts.map +1 -1
- package/dist/studio/components/index.js +4 -4
- package/dist/studio/{components-C5p2TfIU.js → components-Bm4sjhcX.js} +531 -192
- package/dist/studio/index.js +184 -172
- package/dist/studio/studio.css +1 -1
- package/dist/studio-app/assets/dist-DA1MYSzp.js +1 -0
- package/dist/studio-app/assets/{feltdb_wasm-DVKsw75S.js → feltdb_wasm-B-Ccgccb.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-wH3enJIA.wasm +0 -0
- package/dist/studio-app/assets/{index-C1GWyazR.css → index-BRrmvaKw.css} +1 -1
- package/dist/studio-app/assets/index-CEIqowCh.js +42 -0
- package/dist/studio-app/assets/lib-B2_7fcn7.js +69 -0
- package/dist/studio-app/assets/worker-CARZ5g7K.js +69 -0
- package/dist/studio-app/index.html +2 -2
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/workspace/local-development-authority.d.ts +3 -0
- package/dist/workspace/local-development-authority.d.ts.map +1 -1
- package/dist/workspace/local-development-authority.js +62 -1
- package/package.json +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-DNyNf0yy.wasm +0 -0
- package/dist/studio-app/assets/index-C71X92EK.js +0 -28
package/dist/cli/commands.js
CHANGED
|
@@ -14,6 +14,10 @@ import { applicationUrlCandidate, detectApplication, discoverApplicationUrl } fr
|
|
|
14
14
|
import { resolveApplicationLifecycle, startManagedApplication, waitForManagedApplication } from './application-lifecycle.js';
|
|
15
15
|
import { createDevelopmentSession, developmentSessionEnvironment, developmentSessionSummary, transitionDevelopmentSession } from './development-session.js';
|
|
16
16
|
import { openApplication, openStudio } from './browser-opening.js';
|
|
17
|
+
import { publishApplication } from './publish-engine.js';
|
|
18
|
+
import { applyFlowProposal, describeProposal, loadLocalDevelopmentContract, reviewFlowProposal, syncGeneratedSource } from './source-sync.js';
|
|
19
|
+
import { configureAiModel, generateConfiguredFlowProposal, publicAiModelConfiguration } from './ai-model-config.js';
|
|
20
|
+
import { proposalService } from './proposal-client.js';
|
|
17
21
|
function loadProjectEnvironment(file = path.resolve('.env.local')) {
|
|
18
22
|
if (!fs.existsSync(file))
|
|
19
23
|
return;
|
|
@@ -77,11 +81,179 @@ export async function handleCommand(command, args) {
|
|
|
77
81
|
return handleFlowDiff(args);
|
|
78
82
|
case 'deploy':
|
|
79
83
|
return handleFlowDeploy(args);
|
|
84
|
+
case 'publish':
|
|
85
|
+
return handleManagedPublish(args);
|
|
86
|
+
case 'ai':
|
|
87
|
+
return handleAi(args);
|
|
88
|
+
case 'proposals':
|
|
89
|
+
return handleProposals(args);
|
|
90
|
+
case 'sync':
|
|
91
|
+
return handleSourceSync(args);
|
|
80
92
|
case 'help':
|
|
81
93
|
default:
|
|
82
94
|
return handleHelp();
|
|
83
95
|
}
|
|
84
96
|
}
|
|
97
|
+
function proposalFile(args) {
|
|
98
|
+
const value = args.find(argument => !argument.startsWith('--') && !['review', 'apply'].includes(argument));
|
|
99
|
+
if (!value)
|
|
100
|
+
throw new Error('A proposal JSON file is required');
|
|
101
|
+
return path.resolve(value);
|
|
102
|
+
}
|
|
103
|
+
function validateRepositoryBuild(args) {
|
|
104
|
+
const packagePath = path.resolve('package.json');
|
|
105
|
+
const projectPackage = fs.existsSync(packagePath) ? JSON.parse(fs.readFileSync(packagePath, 'utf8')) : {};
|
|
106
|
+
if (!projectPackage.scripts?.build || args.includes('--no-build'))
|
|
107
|
+
return;
|
|
108
|
+
console.log('Validating the updated application before recording applied status…');
|
|
109
|
+
const built = spawnSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'build'], { cwd: process.cwd(), stdio: 'inherit' });
|
|
110
|
+
if (built.status !== 0)
|
|
111
|
+
throw new Error(`APPLICATION_VALIDATION_FAILED: build exited ${built.status ?? 'without a status'}; proposal remains approved for recovery`);
|
|
112
|
+
}
|
|
113
|
+
async function handleAi(args) {
|
|
114
|
+
loadProjectEnvironment();
|
|
115
|
+
if (args[0] === 'model') {
|
|
116
|
+
const action = args[1] || 'show';
|
|
117
|
+
if (action === 'show') {
|
|
118
|
+
const model = publicAiModelConfiguration();
|
|
119
|
+
console.log(`Provider: ${model.provider}\nModel: ${model.model}\nLocation: ${model.local ? 'local browser (private)' : model.baseUrl}\nCredential: ${model.credentialConfigured ? 'configured securely' : 'not configured'}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (action === 'set') {
|
|
123
|
+
const option = (name) => args.includes(name) ? args[args.indexOf(name) + 1] : undefined;
|
|
124
|
+
const provider = (option('--provider') || 'local-webllm');
|
|
125
|
+
if (!['local-webllm', 'openai-compatible'].includes(provider))
|
|
126
|
+
throw new Error('Provider must be local-webllm or openai-compatible');
|
|
127
|
+
const modelName = option('--model');
|
|
128
|
+
if (!modelName)
|
|
129
|
+
throw new Error('--model is required');
|
|
130
|
+
const apiKey = args.includes('--api-key-stdin') ? fs.readFileSync(0, 'utf8').trim() : process.env.FELTDB_AI_API_KEY;
|
|
131
|
+
const configured = configureAiModel({ provider, model: modelName, baseUrl: option('--base-url'), apiKey: apiKey || undefined,
|
|
132
|
+
credentialRequired: !args.includes('--no-credential') });
|
|
133
|
+
console.log(`✓ AI model configured: ${configured.provider} / ${configured.model}`);
|
|
134
|
+
console.log(provider === 'openai-compatible' ? 'Credential is stored in the operating-system vault; Studio receives only provider metadata.' : 'Inference and prompts stay in the browser.');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
throw new Error('Usage: feltdb ai model [show|set --provider <local-webllm|openai-compatible> --model <id> [--base-url <url>] [--api-key-stdin|--no-credential]]');
|
|
138
|
+
}
|
|
139
|
+
if (!args.length || args.every(argument => ['--dry-run', '--preview', '--ephemeral', '--no-open'].includes(argument))) {
|
|
140
|
+
console.log('Opening the local Ask FeltDB experience. Generation stays in your browser via WebGPU.');
|
|
141
|
+
return handleStudio(args, studioUrl => console.log(`Ask FeltDB: ${studioUrl}`));
|
|
142
|
+
}
|
|
143
|
+
const action = args[0];
|
|
144
|
+
if (action === 'context') {
|
|
145
|
+
const context = await loadLocalDevelopmentContract();
|
|
146
|
+
console.log(JSON.stringify({ snapshot: context.snapshot, flowHash: context.flowHash }, null, 2));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (action === 'review') {
|
|
150
|
+
const input = JSON.parse(fs.readFileSync(proposalFile(args.slice(1)), 'utf8'));
|
|
151
|
+
const proposal = input;
|
|
152
|
+
const review = await reviewFlowProposal(proposal);
|
|
153
|
+
console.log(`\n${proposal.summary || 'FeltDB change proposal'}`);
|
|
154
|
+
for (const line of describeProposal(review))
|
|
155
|
+
console.log(` ${line}`);
|
|
156
|
+
if (!describeProposal(review).length)
|
|
157
|
+
console.log(' No contract changes.');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (action === 'apply') {
|
|
161
|
+
const target = args.slice(1).find(value => !value.startsWith('--'));
|
|
162
|
+
if (!target)
|
|
163
|
+
throw new Error('A proposal id or JSON file is required');
|
|
164
|
+
const stored = target.startsWith('p_') ? await proposalService().get(target) : undefined;
|
|
165
|
+
const proposal = stored ? { baseContractHash: stored.base_contract_hash, baseFlowHash: stored.base_flow_hash,
|
|
166
|
+
proposedFlow: stored.proposed_flow, summary: stored.summary, changes: stored.contract_diff, warnings: stored.warnings }
|
|
167
|
+
: JSON.parse(fs.readFileSync(path.resolve(target), 'utf8'));
|
|
168
|
+
if (stored && stored.status !== 'approved')
|
|
169
|
+
throw new Error(`Proposal must be approved before apply; current status is ${stored.status}`);
|
|
170
|
+
if (!stored && !args.includes('--approve'))
|
|
171
|
+
throw new Error('Explicit approval is required: rerun with --approve');
|
|
172
|
+
const current = await loadLocalDevelopmentContract();
|
|
173
|
+
if (stored && (stored.application_id !== current.snapshot.application.application_id || stored.environment !== current.snapshot.application.environment)) {
|
|
174
|
+
throw new Error(`PROPOSAL_SCOPE_MISMATCH: ${stored.application_id}/${stored.environment} cannot apply to ${current.snapshot.application.application_id}/${current.snapshot.application.environment}`);
|
|
175
|
+
}
|
|
176
|
+
const alreadyWritten = stored && await import('@feltdb/core').then(module => module.flowSourceHash(stored.proposed_flow)) === current.flowHash;
|
|
177
|
+
if (alreadyWritten) {
|
|
178
|
+
console.log('Detected an approved proposal whose flow is already present; reconciling the interrupted application.');
|
|
179
|
+
const recoveredReview = await reviewFlowProposal({ ...proposal, baseContractHash: current.snapshot.contract_hash, baseFlowHash: current.flowHash });
|
|
180
|
+
syncGeneratedSource(recoveredReview, process.cwd(), { replace: args.includes('--replace-generated') });
|
|
181
|
+
validateRepositoryBuild(args);
|
|
182
|
+
await proposalService().transition(stored.id, 'applied', 'cli-repository-applier', true);
|
|
183
|
+
console.log(`✓ Reconciled and marked ${stored.id} applied`);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const reviewOnly = await reviewFlowProposal(proposal);
|
|
187
|
+
console.log(`\n${proposal.summary || 'FeltDB change proposal'}`);
|
|
188
|
+
for (const line of describeProposal(reviewOnly))
|
|
189
|
+
console.log(` ${line}`);
|
|
190
|
+
console.log(` Source impact: feltdb.flow, src/feltdb/generated-contract.ts`);
|
|
191
|
+
if (args.includes('--dry-run') || args.includes('--preview')) {
|
|
192
|
+
console.log('✓ Preview complete. No files, flow, contract, or published state were changed.');
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const review = await applyFlowProposal(proposal, process.cwd(), {
|
|
196
|
+
allowDirty: args.includes('--allow-dirty'),
|
|
197
|
+
approveAuthorization: args.includes('--approve-authorization'),
|
|
198
|
+
replaceGenerated: args.includes('--replace-generated'),
|
|
199
|
+
});
|
|
200
|
+
console.log(`✓ Applied validated flow proposal (${review.proposedContract.hashes.application})`);
|
|
201
|
+
for (const file of review.written)
|
|
202
|
+
console.log(` ${file}`);
|
|
203
|
+
validateRepositoryBuild(args);
|
|
204
|
+
if (stored)
|
|
205
|
+
await proposalService().transition(stored.id, 'applied', 'cli-repository-applier', true);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
throw new Error('Usage: feltdb ai [context|review <proposal.json>|apply <proposal.json>]');
|
|
209
|
+
}
|
|
210
|
+
async function handleProposals(args) {
|
|
211
|
+
loadProjectEnvironment();
|
|
212
|
+
const client = proposalService();
|
|
213
|
+
const action = args[0] || 'list';
|
|
214
|
+
const id = args[1];
|
|
215
|
+
if (action === 'list') {
|
|
216
|
+
const { proposals } = await client.list();
|
|
217
|
+
for (const value of proposals.sort((a, b) => String(b.created_at).localeCompare(String(a.created_at))))
|
|
218
|
+
console.log(`${value.id}\t${value.status}\t${value.summary}`);
|
|
219
|
+
if (!proposals.length)
|
|
220
|
+
console.log('No proposals.');
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (!id)
|
|
224
|
+
throw new Error(`feltdb proposals ${action} requires a proposal id`);
|
|
225
|
+
if (action === 'get') {
|
|
226
|
+
console.log(JSON.stringify({ proposal: await client.get(id), history: (await client.history(id)).events }, null, 2));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (action === 'diff') {
|
|
230
|
+
const value = await client.get(id);
|
|
231
|
+
console.log(value.contract_diff.join('\n'));
|
|
232
|
+
console.log(JSON.stringify(value.source_plan, null, 2));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (action === 'approve' || action === 'reject') {
|
|
236
|
+
const value = await client.transition(id, action === 'approve' ? 'approved' : 'rejected', 'local-developer', false, args.includes('--approve-authorization'));
|
|
237
|
+
console.log(`✓ ${value.id} ${value.status}`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
throw new Error('Usage: feltdb proposals [list|get|diff|approve|reject] [id]');
|
|
241
|
+
}
|
|
242
|
+
async function handleSourceSync(args) {
|
|
243
|
+
loadProjectEnvironment();
|
|
244
|
+
const context = await loadLocalDevelopmentContract();
|
|
245
|
+
const proposal = {
|
|
246
|
+
baseContractHash: context.snapshot.contract_hash,
|
|
247
|
+
baseFlowHash: context.flowHash,
|
|
248
|
+
proposedFlow: context.source,
|
|
249
|
+
summary: 'Synchronize generated source with feltdb.flow',
|
|
250
|
+
changes: [], warnings: [],
|
|
251
|
+
};
|
|
252
|
+
const review = await reviewFlowProposal(proposal);
|
|
253
|
+
const result = syncGeneratedSource(review, process.cwd(), { replace: args.includes('--replace-generated') });
|
|
254
|
+
for (const file of result.written)
|
|
255
|
+
console.log(`✓ Synchronized ${file}`);
|
|
256
|
+
}
|
|
85
257
|
/**
|
|
86
258
|
* Inspect the durable investigation store.
|
|
87
259
|
*
|
|
@@ -231,6 +403,31 @@ async function handleFlowDeploy(args) {
|
|
|
231
403
|
await db.close();
|
|
232
404
|
}
|
|
233
405
|
}
|
|
406
|
+
async function handleManagedPublish(args) {
|
|
407
|
+
const valueAfter = (flag) => { const index = args.indexOf(flag); return index >= 0 ? args[index + 1] : undefined; };
|
|
408
|
+
const confirm = async (question) => {
|
|
409
|
+
if (!process.stdin.isTTY)
|
|
410
|
+
return false;
|
|
411
|
+
const readline = await import('node:readline/promises');
|
|
412
|
+
const prompt = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
413
|
+
try {
|
|
414
|
+
return !/^(n|no)$/i.test((await prompt.question(`${question} [Y/n] `)).trim());
|
|
415
|
+
}
|
|
416
|
+
finally {
|
|
417
|
+
prompt.close();
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
await publishApplication({
|
|
421
|
+
environment: valueAfter('--environment'),
|
|
422
|
+
message: valueAfter('--message'),
|
|
423
|
+
dryRun: args.includes('--dry-run'),
|
|
424
|
+
status: args.includes('--status'),
|
|
425
|
+
yes: args.includes('--yes'),
|
|
426
|
+
approveDestructive: args.includes('--approve-destructive-changes') || args.includes('--allow-destructive'),
|
|
427
|
+
noBuild: args.includes('--no-build'),
|
|
428
|
+
confirm,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
234
431
|
async function handleServer(args) {
|
|
235
432
|
const require = createRequire(import.meta.url);
|
|
236
433
|
const packageRoot = path.dirname(require.resolve('@feltdb/core/package.json'));
|
|
@@ -419,17 +616,39 @@ For more info, visit: https://github.com/rkendel1/feltdb
|
|
|
419
616
|
`);
|
|
420
617
|
}
|
|
421
618
|
async function handleConnect(args) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
process.
|
|
427
|
-
|
|
619
|
+
loadProjectEnvironment();
|
|
620
|
+
const projectFile = path.resolve('feltdb.config.json');
|
|
621
|
+
const project = fs.existsSync(projectFile) ? JSON.parse(fs.readFileSync(projectFile, 'utf8')) : {};
|
|
622
|
+
const url = (args[0]
|
|
623
|
+
|| process.env.VITE_FELTDB_MANAGED_URL
|
|
624
|
+
|| process.env.VITE_FELTDB_URL
|
|
625
|
+
|| project.server?.url
|
|
626
|
+
|| (project.runtime === 'browser' ? '' : 'http://localhost:7700')).replace(/\/$/, '');
|
|
627
|
+
if (!url)
|
|
628
|
+
throw new Error('This project uses the embedded browser runtime; there is no server endpoint to connect to. Run npm run dev.');
|
|
428
629
|
console.log('🔗 Connecting to FeltDB Server\n');
|
|
429
630
|
console.log(`URL: ${url}`);
|
|
430
|
-
console.log('Discovering node...');
|
|
431
|
-
console.log('Checking capabilities...');
|
|
432
631
|
console.log('Verifying connection...\n');
|
|
632
|
+
const controller = new AbortController();
|
|
633
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
634
|
+
let health;
|
|
635
|
+
try {
|
|
636
|
+
const response = await fetch(`${url}/health`, {
|
|
637
|
+
headers: process.env.VITE_FELTDB_MANAGED_API_KEY || process.env.VITE_FELTDB_API_KEY
|
|
638
|
+
? { Authorization: `Bearer ${process.env.VITE_FELTDB_MANAGED_API_KEY || process.env.VITE_FELTDB_API_KEY}` }
|
|
639
|
+
: {},
|
|
640
|
+
signal: controller.signal,
|
|
641
|
+
});
|
|
642
|
+
if (!response.ok)
|
|
643
|
+
throw new Error(`HTTP ${response.status}`);
|
|
644
|
+
health = await response.json();
|
|
645
|
+
}
|
|
646
|
+
catch (error) {
|
|
647
|
+
throw new Error(`FeltDB is not running at ${url}. Start it with npm run feltdb:studio (or the project's configured runtime command). ${error instanceof Error ? error.message : error}`);
|
|
648
|
+
}
|
|
649
|
+
finally {
|
|
650
|
+
clearTimeout(timeout);
|
|
651
|
+
}
|
|
433
652
|
// Store connection info
|
|
434
653
|
const configDir = path.join(process.cwd(), '.feltdb');
|
|
435
654
|
if (!fs.existsSync(configDir)) {
|
|
@@ -439,15 +658,16 @@ async function handleConnect(args) {
|
|
|
439
658
|
fs.writeFileSync(connFile, JSON.stringify({
|
|
440
659
|
url,
|
|
441
660
|
connected: new Date().toISOString(),
|
|
442
|
-
authenticated:
|
|
661
|
+
authenticated: Boolean(process.env.VITE_FELTDB_MANAGED_API_KEY || process.env.VITE_FELTDB_API_KEY),
|
|
662
|
+
namespace: project.namespace || process.env.VITE_FELTDB_MANAGED_NAMESPACE || 'default',
|
|
663
|
+
runtime: project.runtime || 'self-hosted',
|
|
443
664
|
}, null, 2));
|
|
444
665
|
console.log('✅ Connected to FeltDB\n');
|
|
445
666
|
console.log('Endpoint: ' + url);
|
|
446
|
-
console.log(
|
|
447
|
-
console.log(
|
|
448
|
-
console.log(
|
|
449
|
-
console.log(
|
|
450
|
-
console.log('Health: ✓\n');
|
|
667
|
+
console.log(`Namespace: ${project.namespace || process.env.VITE_FELTDB_MANAGED_NAMESPACE || 'default'}`);
|
|
668
|
+
console.log(`Authenticated: ${process.env.VITE_FELTDB_MANAGED_API_KEY || process.env.VITE_FELTDB_API_KEY ? 'yes' : 'no'}`);
|
|
669
|
+
console.log(`Runtime: ${project.runtime || 'self-hosted'}`);
|
|
670
|
+
console.log(`Health: ${health.status || 'healthy'}\n`);
|
|
451
671
|
console.log('Use: feltdb status (to check connection)\n');
|
|
452
672
|
}
|
|
453
673
|
async function handleExplain(args) {
|
|
@@ -480,20 +700,39 @@ async function handleStatus() {
|
|
|
480
700
|
console.log('Fabric: Enabled');
|
|
481
701
|
console.log('Authentication: Development');
|
|
482
702
|
console.log('Health: ✓\n');
|
|
703
|
+
await displayContractStatus();
|
|
483
704
|
return;
|
|
484
705
|
}
|
|
485
706
|
const connection = JSON.parse(fs.readFileSync(connFile, 'utf-8'));
|
|
486
707
|
console.log('Connected to Server\n');
|
|
487
708
|
console.log(`Endpoint: ${connection.url}`);
|
|
488
|
-
console.log(
|
|
709
|
+
console.log(`Namespace: ${connection.namespace || 'default'}`);
|
|
489
710
|
console.log(`Authenticated: ${connection.authenticated ? 'yes' : 'no'}`);
|
|
490
|
-
console.log(
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
711
|
+
console.log(`Runtime: ${connection.runtime || 'self-hosted'}`);
|
|
712
|
+
try {
|
|
713
|
+
const response = await fetch(`${String(connection.url).replace(/\/$/, '')}/health`, { signal: AbortSignal.timeout(5000) });
|
|
714
|
+
if (!response.ok)
|
|
715
|
+
throw new Error(`HTTP ${response.status}`);
|
|
716
|
+
const health = await response.json();
|
|
717
|
+
console.log(`Health: ${health.status || 'healthy'}\n`);
|
|
718
|
+
}
|
|
719
|
+
catch {
|
|
720
|
+
console.log('Health: unavailable');
|
|
721
|
+
console.log('Start the configured runtime and run feltdb connect again.\n');
|
|
722
|
+
}
|
|
723
|
+
await displayContractStatus();
|
|
724
|
+
}
|
|
725
|
+
async function displayContractStatus() {
|
|
726
|
+
if (!fs.existsSync(path.resolve('feltdb.flow')))
|
|
727
|
+
return;
|
|
728
|
+
loadProjectEnvironment();
|
|
729
|
+
const current = await loadLocalDevelopmentContract();
|
|
730
|
+
const publishedFile = path.resolve('.feltdb/last-published.json');
|
|
731
|
+
const published = fs.existsSync(publishedFile) ? JSON.parse(fs.readFileSync(publishedFile, 'utf8')) : undefined;
|
|
732
|
+
console.log('Application contract');
|
|
733
|
+
console.log(`Source: ${current.snapshot.contract_hash}`);
|
|
734
|
+
console.log(`Published: ${published?.contractHash || 'unknown'}`);
|
|
735
|
+
console.log(`Drift: ${!published?.contractHash ? 'unknown' : published.contractHash === current.snapshot.contract_hash ? 'synchronized' : 'source differs from published'}\n`);
|
|
497
736
|
}
|
|
498
737
|
async function handleWorkspace(args) {
|
|
499
738
|
const subcommand = args[0] || 'status';
|
|
@@ -695,10 +934,16 @@ async function handleDev(args) {
|
|
|
695
934
|
if (!process.env.VITE_FELTDB_URL) {
|
|
696
935
|
const authorityPort = Number(args.includes('--authority-port') ? args[args.indexOf('--authority-port') + 1] || '7700' : '7700');
|
|
697
936
|
localAuthority = await startLocalDevelopmentAuthority({
|
|
698
|
-
dataDir: path.join(projectDir, '.feltdb', 'authority'),
|
|
937
|
+
dataDir: path.join(projectDir, '.feltdb-data', 'authority'),
|
|
699
938
|
port: authorityPort,
|
|
939
|
+
proposalContext: () => loadLocalDevelopmentContract(projectDir),
|
|
700
940
|
});
|
|
701
941
|
process.env.VITE_FELTDB_URL = localAuthority.endpoint;
|
|
942
|
+
const connectionsPath = path.join(projectDir, '.feltdb', 'connections.json');
|
|
943
|
+
const connections = fs.existsSync(connectionsPath) ? JSON.parse(fs.readFileSync(connectionsPath, 'utf8')) : {};
|
|
944
|
+
fs.writeFileSync(connectionsPath, `${JSON.stringify({ ...connections, developmentAuthority: {
|
|
945
|
+
endpoint: localAuthority.endpoint, proposalAuthorityToken: localAuthority.proposalAuthorityToken,
|
|
946
|
+
} }, null, 2)}\n`, { mode: 0o600 });
|
|
702
947
|
}
|
|
703
948
|
const authorityUrl = process.env.VITE_FELTDB_URL;
|
|
704
949
|
if (!authorityUrl)
|
|
@@ -1155,9 +1400,13 @@ async function handleDoctor() {
|
|
|
1155
1400
|
console.log('\n✅ System healthy\n');
|
|
1156
1401
|
}
|
|
1157
1402
|
async function handleStudio(args, onReady) {
|
|
1403
|
+
loadProjectEnvironment();
|
|
1404
|
+
const projectConfigPath = path.resolve('feltdb.config.json');
|
|
1405
|
+
const projectConfig = fs.existsSync(projectConfigPath) ? JSON.parse(fs.readFileSync(projectConfigPath, 'utf8')) : {};
|
|
1406
|
+
const projectIsManaged = projectConfig.runtime === 'managed';
|
|
1158
1407
|
const connectUrl = args.includes('--connect')
|
|
1159
1408
|
? args[args.indexOf('--connect') + 1]
|
|
1160
|
-
: undefined;
|
|
1409
|
+
: projectIsManaged ? process.env.VITE_FELTDB_MANAGED_URL : undefined;
|
|
1161
1410
|
const port = args.includes('--port')
|
|
1162
1411
|
? args[args.indexOf('--port') + 1] || '7701'
|
|
1163
1412
|
: '7701';
|
|
@@ -1167,10 +1416,10 @@ async function handleStudio(args, onReady) {
|
|
|
1167
1416
|
const open = !args.includes('--no-open');
|
|
1168
1417
|
const namespace = args.includes('--namespace')
|
|
1169
1418
|
? args[args.indexOf('--namespace') + 1] || 'default'
|
|
1170
|
-
: 'default';
|
|
1419
|
+
: projectIsManaged ? process.env.VITE_FELTDB_MANAGED_NAMESPACE || projectConfig.namespace || 'default' : 'default';
|
|
1171
1420
|
const runtime = args.includes('--runtime')
|
|
1172
1421
|
? args[args.indexOf('--runtime') + 1] || 'browser'
|
|
1173
|
-
: connectUrl ? 'remote' : 'browser';
|
|
1422
|
+
: projectIsManaged ? 'managed' : connectUrl ? 'remote' : 'browser';
|
|
1174
1423
|
const appUrl = args.includes('--app-url')
|
|
1175
1424
|
? args[args.indexOf('--app-url') + 1]
|
|
1176
1425
|
: undefined;
|
|
@@ -1201,9 +1450,13 @@ async function handleStudio(args, onReady) {
|
|
|
1201
1450
|
console.log('⚠️ Studio application artifacts not found.');
|
|
1202
1451
|
return;
|
|
1203
1452
|
}
|
|
1204
|
-
const
|
|
1205
|
-
|
|
1206
|
-
|
|
1453
|
+
const canonicalFlow = path.resolve(process.cwd(), 'feltdb.flow');
|
|
1454
|
+
const projectFlow = fs.existsSync(canonicalFlow) ? canonicalFlow : undefined;
|
|
1455
|
+
const studioMutationToken = randomBytes(24).toString('hex');
|
|
1456
|
+
const projectPackage = fs.existsSync(path.resolve('package.json')) ? JSON.parse(fs.readFileSync(path.resolve('package.json'), 'utf8')) : {};
|
|
1457
|
+
const webllmEnabled = Boolean(projectPackage.dependencies?.['@feltdb/webllm'] || projectPackage.devDependencies?.['@feltdb/webllm']);
|
|
1458
|
+
const previewOnly = args.includes('--ephemeral');
|
|
1459
|
+
const proposalClient = proposalService(process.cwd(), session.authorityUrl);
|
|
1207
1460
|
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' };
|
|
1208
1461
|
const server = http.createServer(async (request, response) => {
|
|
1209
1462
|
const pathname = decodeURIComponent(new URL(request.url || '/', 'http://localhost').pathname);
|
|
@@ -1211,7 +1464,187 @@ async function handleStudio(args, onReady) {
|
|
|
1211
1464
|
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1212
1465
|
response.setHeader('Cache-Control', 'no-store');
|
|
1213
1466
|
response.setHeader('Pragma', 'no-cache');
|
|
1214
|
-
|
|
1467
|
+
const managedPublish = runtime === 'managed' ? {
|
|
1468
|
+
url: process.env.VITE_FELTDB_MANAGED_URL || connectUrl || '',
|
|
1469
|
+
namespace: process.env.VITE_FELTDB_MANAGED_NAMESPACE || namespace,
|
|
1470
|
+
tenantId: process.env.VITE_FELTDB_MANAGED_TENANT_ID || '',
|
|
1471
|
+
applicationId: process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || '',
|
|
1472
|
+
environment: process.env.VITE_FELTDB_MANAGED_ENVIRONMENT || 'production',
|
|
1473
|
+
publishEndpoint: '/_feltdb/publish',
|
|
1474
|
+
} : undefined;
|
|
1475
|
+
response.end(JSON.stringify({ token: connectUrl ? process.env.VITE_FELTDB_API_KEY || process.env.VITE_FELTDB_MANAGED_API_KEY || '' : '', managedPublish, session, sourceSync: { enabled: true, webllmEnabled, previewOnly, token: studioMutationToken, aiModel: publicAiModelConfiguration() } }));
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
if (pathname === '/_feltdb/contract') {
|
|
1479
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1480
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
1481
|
+
try {
|
|
1482
|
+
const current = await loadLocalDevelopmentContract();
|
|
1483
|
+
const lastPublished = fs.existsSync(path.resolve('.feltdb/last-published.json'))
|
|
1484
|
+
? JSON.parse(fs.readFileSync(path.resolve('.feltdb/last-published.json'), 'utf8')) : undefined;
|
|
1485
|
+
let runningContract;
|
|
1486
|
+
let runningSnapshot;
|
|
1487
|
+
const applicationId = process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || lastPublished?.applicationId;
|
|
1488
|
+
if (connectUrl && applicationId) {
|
|
1489
|
+
try {
|
|
1490
|
+
const endpoint = new URL('/v1/application', connectUrl);
|
|
1491
|
+
endpoint.searchParams.set('application_id', applicationId);
|
|
1492
|
+
endpoint.searchParams.set('environment', process.env.VITE_FELTDB_MANAGED_ENVIRONMENT || lastPublished?.environment || 'production');
|
|
1493
|
+
const headers = {};
|
|
1494
|
+
const credential = process.env.FELTDB_MANAGED_CONTROL_API_KEY || process.env.VITE_FELTDB_API_KEY;
|
|
1495
|
+
if (credential)
|
|
1496
|
+
headers.authorization = `Bearer ${credential}`;
|
|
1497
|
+
const discovered = await fetch(endpoint, { headers });
|
|
1498
|
+
if (discovered.ok) {
|
|
1499
|
+
const application = await discovered.json();
|
|
1500
|
+
runningSnapshot = application.snapshot;
|
|
1501
|
+
runningContract = runningSnapshot?.contract_hash ?? application.contract_hash;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
catch { /* Drift remains explicitly unknown while the runtime is unavailable. */ }
|
|
1505
|
+
}
|
|
1506
|
+
const knownContracts = [current.snapshot.contract_hash, runningContract, lastPublished?.contractHash].filter(Boolean);
|
|
1507
|
+
response.end(JSON.stringify({ snapshot: runningSnapshot ?? current.snapshot, flowHash: current.flowHash, source: current.source,
|
|
1508
|
+
drift: { source: current.snapshot.contract_hash, running: runningContract, published: lastPublished?.contractHash,
|
|
1509
|
+
synchronized: new Set(knownContracts).size <= 1 } }));
|
|
1510
|
+
}
|
|
1511
|
+
catch (error) {
|
|
1512
|
+
response.statusCode = 400;
|
|
1513
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
1514
|
+
}
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
if (pathname === '/_feltdb/proposals/list' && request.method === 'GET') {
|
|
1518
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1519
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
1520
|
+
try {
|
|
1521
|
+
response.end(JSON.stringify(await proposalClient.list()));
|
|
1522
|
+
}
|
|
1523
|
+
catch (error) {
|
|
1524
|
+
response.statusCode = 503;
|
|
1525
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
1526
|
+
}
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
const proposalDetail = pathname.match(/^\/_feltdb\/proposals\/(p_[A-Za-z0-9]+)$/);
|
|
1530
|
+
if (proposalDetail && request.method === 'GET') {
|
|
1531
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1532
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
1533
|
+
try {
|
|
1534
|
+
response.end(JSON.stringify({ proposal: await proposalClient.get(proposalDetail[1]), history: (await proposalClient.history(proposalDetail[1])).events }));
|
|
1535
|
+
}
|
|
1536
|
+
catch (error) {
|
|
1537
|
+
response.statusCode = 404;
|
|
1538
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
1539
|
+
}
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
// Studio proposes and previews. CLI applies to the repository. FeltDB validates
|
|
1543
|
+
// and stores the proposal as application state. Human approves. Nothing else writes source.
|
|
1544
|
+
if (pathname === '/_feltdb/proposals' || pathname === '/_feltdb/proposals/approve' || pathname === '/_feltdb/proposals/reject') {
|
|
1545
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1546
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
1547
|
+
if (request.method !== 'POST') {
|
|
1548
|
+
response.statusCode = 405;
|
|
1549
|
+
response.end(JSON.stringify({ error: 'POST required' }));
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
if (request.headers['x-feltdb-studio-token'] !== studioMutationToken) {
|
|
1553
|
+
response.statusCode = 403;
|
|
1554
|
+
response.end(JSON.stringify({ error: 'Invalid Studio source-sync token' }));
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
try {
|
|
1558
|
+
const chunks = [];
|
|
1559
|
+
for await (const chunk of request)
|
|
1560
|
+
chunks.push(Buffer.from(chunk));
|
|
1561
|
+
const input = chunks.length ? JSON.parse(Buffer.concat(chunks).toString('utf8')) : {};
|
|
1562
|
+
if (pathname.endsWith('/approve') || pathname.endsWith('/reject')) {
|
|
1563
|
+
if (previewOnly)
|
|
1564
|
+
throw new Error('Preview-only sessions cannot approve or reject persisted proposals');
|
|
1565
|
+
if (input.confirm !== true)
|
|
1566
|
+
throw new Error('Explicit proposal confirmation is required');
|
|
1567
|
+
const proposal = await proposalClient.transition(String(input.proposalId || ''), pathname.endsWith('/approve') ? 'approved' : 'rejected', 'studio-developer', false, input.approveAuthorization === true);
|
|
1568
|
+
response.end(JSON.stringify({ status: proposal.status, proposalId: proposal.id,
|
|
1569
|
+
command: proposal.status === 'approved' ? `feltdb ai apply ${proposal.id}` : undefined }));
|
|
1570
|
+
}
|
|
1571
|
+
else {
|
|
1572
|
+
const proposal = input.proposal;
|
|
1573
|
+
const review = await reviewFlowProposal(proposal);
|
|
1574
|
+
const changes = describeProposal(review);
|
|
1575
|
+
let stored;
|
|
1576
|
+
if (!previewOnly) {
|
|
1577
|
+
const current = await loadLocalDevelopmentContract();
|
|
1578
|
+
stored = await proposalClient.create({ application_id: current.snapshot.application.application_id,
|
|
1579
|
+
environment: current.snapshot.application.environment, base_contract_hash: proposal.baseContractHash,
|
|
1580
|
+
base_flow_hash: proposal.baseFlowHash, proposed_flow: proposal.proposedFlow, summary: proposal.summary,
|
|
1581
|
+
warnings: proposal.warnings, actor: 'studio-developer', metadata: { model: publicAiModelConfiguration().model, created_from: 'studio' } });
|
|
1582
|
+
await proposalClient.validate(stored.id, 'feltdb-validator', review.proposedContract.manifest);
|
|
1583
|
+
stored = (await proposalClient.preview(stored.id, 'studio-developer')).proposal;
|
|
1584
|
+
}
|
|
1585
|
+
response.end(JSON.stringify({ status: 'previewed', proposalId: stored?.id ?? 'ephemeral', changes,
|
|
1586
|
+
sourcePlan: stored?.source_plan.files.map(value => value.path) ?? ['feltdb.flow', 'src/feltdb/generated-contract.ts'], previewOnly,
|
|
1587
|
+
authorizationChanges: review.authorizationChanges, migration: review.migration,
|
|
1588
|
+
contractHash: review.proposedContract.hashes.application }));
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
catch (error) {
|
|
1592
|
+
response.statusCode = 400;
|
|
1593
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
1594
|
+
}
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
if (pathname === '/_feltdb/ai/generate') {
|
|
1598
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1599
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
1600
|
+
if (request.method !== 'POST') {
|
|
1601
|
+
response.statusCode = 405;
|
|
1602
|
+
response.end(JSON.stringify({ error: 'POST required' }));
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
if (request.headers['x-feltdb-studio-token'] !== studioMutationToken) {
|
|
1606
|
+
response.statusCode = 403;
|
|
1607
|
+
response.end(JSON.stringify({ error: 'Invalid Studio token' }));
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
try {
|
|
1611
|
+
const chunks = [];
|
|
1612
|
+
for await (const chunk of request)
|
|
1613
|
+
chunks.push(Buffer.from(chunk));
|
|
1614
|
+
const input = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
1615
|
+
if (!input.intent?.trim())
|
|
1616
|
+
throw new Error('An AI change request is required');
|
|
1617
|
+
const current = await loadLocalDevelopmentContract();
|
|
1618
|
+
const proposal = await generateConfiguredFlowProposal({ intent: input.intent, snapshot: current.snapshot, currentFlow: current.source });
|
|
1619
|
+
response.end(JSON.stringify({ proposal }));
|
|
1620
|
+
}
|
|
1621
|
+
catch (error) {
|
|
1622
|
+
response.statusCode = 400;
|
|
1623
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
1624
|
+
}
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
if (pathname === '/_feltdb/publish') {
|
|
1628
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1629
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
1630
|
+
if (request.method !== 'POST') {
|
|
1631
|
+
response.statusCode = 405;
|
|
1632
|
+
response.end(JSON.stringify({ error: 'POST required' }));
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
try {
|
|
1636
|
+
const chunks = [];
|
|
1637
|
+
for await (const chunk of request)
|
|
1638
|
+
chunks.push(Buffer.from(chunk));
|
|
1639
|
+
const input = chunks.length ? JSON.parse(Buffer.concat(chunks).toString('utf8')) : {};
|
|
1640
|
+
const messages = [];
|
|
1641
|
+
const result = await publishApplication({ dryRun: Boolean(input.dryRun), status: Boolean(input.status), yes: true, noBuild: true, environment: input.environment, approveDestructive: Boolean(input.approveDestructive), message: input.message, log: message => messages.push(message) });
|
|
1642
|
+
response.end(JSON.stringify({ status: result.status, messages, version: result.revision?.revision_number, validation: result.validation, changes: result.diff?.changes, securityChanges: result.diff?.security_changes }));
|
|
1643
|
+
}
|
|
1644
|
+
catch (error) {
|
|
1645
|
+
response.statusCode = 400;
|
|
1646
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
1647
|
+
}
|
|
1215
1648
|
return;
|
|
1216
1649
|
}
|
|
1217
1650
|
if (pathname === '/_feltdb/project') {
|
|
@@ -1312,6 +1745,20 @@ Commands:
|
|
|
1312
1745
|
validate [file] Validate an entire FlowSpec application model
|
|
1313
1746
|
diff [file] Diff FlowSpec against the last deployed model
|
|
1314
1747
|
deploy [file] Validate, version, and deploy FlowSpec
|
|
1748
|
+
publish [file] Build and publish/update a managed application
|
|
1749
|
+
ai Open the canonical interactive local-AI workflow
|
|
1750
|
+
ai --dry-run Open an ephemeral preview-only AI session
|
|
1751
|
+
ai model show Show the selected local or user-provided model
|
|
1752
|
+
ai model set Select WebLLM or an OpenAI-compatible model
|
|
1753
|
+
ai context Show the secret-free contract grounding snapshot
|
|
1754
|
+
ai review <file> Validate and review a WebLLM flow proposal
|
|
1755
|
+
ai apply <id> Apply an approved proposal and synchronize source
|
|
1756
|
+
proposals List durable FeltDB application proposals
|
|
1757
|
+
proposals get <id> Show a proposal and its lifecycle history
|
|
1758
|
+
proposals diff <id> Show contract and structured source impact
|
|
1759
|
+
proposals approve <id> Approve a previewed proposal
|
|
1760
|
+
proposals reject <id> Reject a proposed/validated/previewed proposal
|
|
1761
|
+
sync Regenerate owned source from feltdb.flow
|
|
1315
1762
|
studio Launch FeltDB Studio (developer interface)
|
|
1316
1763
|
server Start self-hosted FeltDB server
|
|
1317
1764
|
keys Manage API keys
|
|
@@ -1350,6 +1797,13 @@ Connection:
|
|
|
1350
1797
|
doctor Check system health
|
|
1351
1798
|
help Show this help message
|
|
1352
1799
|
|
|
1800
|
+
Publish:
|
|
1801
|
+
feltdb publish --dry-run Validate and preview without activation
|
|
1802
|
+
feltdb publish --yes Publish to the configured environment
|
|
1803
|
+
feltdb publish --status Compare local and active cloud versions
|
|
1804
|
+
feltdb publish --environment staging Override the configured environment
|
|
1805
|
+
feltdb publish --approve-destructive-changes --yes
|
|
1806
|
+
|
|
1353
1807
|
Inspect Sub-commands:
|
|
1354
1808
|
feltdb inspect Show runtime overview
|
|
1355
1809
|
feltdb inspect peers Show connected peers
|
|
@@ -1373,6 +1827,7 @@ Examples:
|
|
|
1373
1827
|
feltdb status
|
|
1374
1828
|
feltdb dev
|
|
1375
1829
|
feltdb build
|
|
1830
|
+
feltdb publish --dry-run
|
|
1376
1831
|
feltdb check
|
|
1377
1832
|
feltdb inspect
|
|
1378
1833
|
feltdb inspect peers
|
package/dist/cli/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import * as path from 'path';
|
|
|
23
23
|
import * as readline from 'readline';
|
|
24
24
|
import { getClient } from './api-client.js';
|
|
25
25
|
import { loadFeltDBConfig, createDefaultConfig, validateModel, } from './config.js';
|
|
26
|
-
const VERSION = '0.7.
|
|
26
|
+
const VERSION = '0.7.4';
|
|
27
27
|
function prompt(question) {
|
|
28
28
|
const rl = readline.createInterface({
|
|
29
29
|
input: process.stdin,
|