@feltdb/core 0.7.3 → 0.8.0
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 +25 -0
- package/dist/application-development.d.ts +78 -0
- package/dist/application-development.d.ts.map +1 -0
- package/dist/application-development.js +153 -0
- package/dist/application-manifest.d.ts +6 -1
- package/dist/application-manifest.d.ts.map +1 -1
- package/dist/application-manifest.js +10 -3
- package/dist/canonical-application.d.ts +27 -0
- package/dist/canonical-application.d.ts.map +1 -0
- package/dist/canonical-application.js +46 -0
- package/dist/cli/ai-model-config.js +97 -0
- package/dist/cli/cli.js +1 -1
- package/dist/cli/commands.js +664 -120
- package/dist/cli/index.js +1 -1
- package/dist/cli/managed-environment.js +6 -0
- package/dist/cli/managed-project.js +6 -0
- package/dist/cli/proposal-client.js +63 -0
- package/dist/cli/publish-engine.js +165 -0
- package/dist/cli/source-sync.js +183 -0
- package/dist/create/cli.js +11 -2
- package/dist/create/create.js +38 -16
- package/dist/create/managed-account.js +82 -11
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/crates/feltdb/src/application.rs +41 -1
- package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +84 -6
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +126 -38
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +72 -34
- package/dist/create/server-source/crates/feltdb/src/lib.rs +20 -2
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +12 -24
- package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +11 -3
- package/dist/create/server-source/crates/feltdb-server/src/authenticated_principal.rs +0 -1
- package/dist/create/server-source/crates/feltdb-server/src/certification_harness.rs +23 -22
- package/dist/create/server-source/crates/feltdb-server/src/delegation_token.rs +5 -15
- package/dist/create/server-source/crates/feltdb-server/src/durable_operations.rs +13 -28
- package/dist/create/server-source/crates/feltdb-server/src/identity.rs +111 -3
- package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +28 -7
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +5 -5
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +1960 -143
- package/dist/create/server-source/crates/feltdb-server/src/managed_diagnostics.rs +10 -30
- package/dist/create/server-source/crates/feltdb-server/src/membership_policy.rs +12 -4
- package/dist/create/server-source/crates/feltdb-server/src/request_telemetry.rs +8 -6
- package/dist/create/server-source/crates/feltdb-server/src/snapshot_cursor.rs +10 -14
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +195 -13
- package/dist/create/server-source/crates/feltdb-server/src/tenant_policies.rs +9 -21
- package/dist/create/server-source/crates/feltdb-server/src/transaction_idempotency.rs +5 -3
- package/dist/create/server-source/crates/feltdb-server/src/transaction_recovery.rs +9 -8
- package/dist/create/server-source/crates/feltdb-server/tests/revision_recovery_integration_test.rs +1 -4
- 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 +174 -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 +1 -0
- package/dist/flowspec.d.ts.map +1 -1
- package/dist/flowspec.js +66 -11
- package/dist/http-client.d.ts +135 -14
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +79 -31
- 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 +5 -0
- package/dist/index-core.d.ts.map +1 -1
- package/dist/index-core.js +5 -0
- package/dist/module-intent.d.ts +37 -0
- package/dist/module-intent.d.ts.map +1 -0
- package/dist/module-intent.js +81 -0
- package/dist/module.d.ts +76 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +61 -0
- package/dist/proposal.d.ts +161 -0
- package/dist/proposal.d.ts.map +1 -0
- package/dist/proposal.js +232 -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 +2 -1
- package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
- package/dist/studio/components/AskFeltDB.d.ts +21 -0
- package/dist/studio/components/AskFeltDB.d.ts.map +1 -0
- package/dist/studio/components/ContractInspector.d.ts +7 -0
- package/dist/studio/components/ContractInspector.d.ts.map +1 -0
- package/dist/studio/components/ManagedInstancePanel.d.ts.map +1 -1
- package/dist/studio/components/ManagedInstancePanel.js +1 -5
- package/dist/studio/components/ModuleExplorer.d.ts +6 -0
- package/dist/studio/components/ModuleExplorer.d.ts.map +1 -0
- package/dist/studio/components/ProposalBrowser.d.ts +12 -0
- package/dist/studio/components/ProposalBrowser.d.ts.map +1 -0
- package/dist/studio/components/StateExplorer.d.ts +6 -7
- package/dist/studio/components/StateExplorer.d.ts.map +1 -1
- package/dist/studio/components/index.d.ts +4 -0
- package/dist/studio/components/index.d.ts.map +1 -1
- package/dist/studio/components/index.js +4 -4
- package/dist/studio/components-CNSNrmA9.js +2787 -0
- package/dist/studio/index.js +214 -157
- package/dist/studio/studio.css +1 -1
- package/dist/studio/utils/index.d.ts +2 -0
- package/dist/studio/utils/index.d.ts.map +1 -1
- package/dist/studio/utils/index.js +14 -12
- package/dist/studio/utils/managed-instance.d.ts +0 -4
- package/dist/studio/utils/managed-instance.d.ts.map +1 -1
- package/dist/studio/utils/managed-instance.js +0 -4
- package/dist/studio/utils/proposal-api.d.ts +53 -0
- package/dist/studio/utils/proposal-api.d.ts.map +1 -0
- package/dist/studio/utils/proposal-api.js +91 -0
- package/dist/studio/utils/service-api.d.ts +81 -0
- package/dist/studio/utils/service-api.d.ts.map +1 -0
- package/dist/studio/utils/service-api.js +51 -0
- package/dist/studio-app/assets/dist-CCOk39Uc.js +1 -0
- package/dist/studio-app/assets/{feltdb_wasm-DVKsw75S.js → feltdb_wasm-CSB6KVgw.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-Dd1fnO9U.wasm +0 -0
- package/dist/studio-app/assets/index-Bncji3aN.js +43 -0
- package/dist/studio-app/assets/{index-C1GWyazR.css → index-yazaWMUN.css} +1 -1
- 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 +124 -2
- package/package.json +1 -1
- package/dist/studio/components-Dxuhrv8_.js +0 -1832
- package/dist/studio-app/assets/feltdb_wasm_bg-DNyNf0yy.wasm +0 -0
- package/dist/studio-app/assets/index-B5tnmvSD.js +0 -28
package/dist/cli/commands.js
CHANGED
|
@@ -8,12 +8,19 @@ import net from 'net';
|
|
|
8
8
|
import { createRequire } from 'module';
|
|
9
9
|
import { spawn, spawnSync } from 'child_process';
|
|
10
10
|
import { randomBytes } from 'crypto';
|
|
11
|
-
import {
|
|
11
|
+
import { createInterface } from 'node:readline/promises';
|
|
12
|
+
import { createFeltDB, createModuleImplementationProposal, diffFlowSpec, formatFlowSpec, InvestigationLifecycleManager, listFeltDBModules, parseFlowSpec, planFlowSpecMigration, renderProposalDiagnostic, resolveFeltDBModule, resolveModuleIntent, startLocalDevelopmentAuthority, validateFlowSpec } from '@feltdb/core';
|
|
12
13
|
import { discoverWorkspace, ensureWorkspaceGitIgnored, generatePairingToken, persistPairingToken, displayWorkspaceStatus, initializeWorkspace, startPairingDiscoveryServer } from './workspace-integration.js';
|
|
13
14
|
import { applicationUrlCandidate, detectApplication, discoverApplicationUrl } from './application.js';
|
|
14
15
|
import { resolveApplicationLifecycle, startManagedApplication, waitForManagedApplication } from './application-lifecycle.js';
|
|
15
16
|
import { createDevelopmentSession, developmentSessionEnvironment, developmentSessionSummary, transitionDevelopmentSession } from './development-session.js';
|
|
16
17
|
import { openApplication, openStudio } from './browser-opening.js';
|
|
18
|
+
import { publishApplication } from './publish-engine.js';
|
|
19
|
+
import { applyFlowProposalAtomic, assertProposalSourcePlan, describeProposal, loadLocalDevelopmentContract, repositoryContext, reviewFlowProposal, syncGeneratedSource } from './source-sync.js';
|
|
20
|
+
import { configureAiModel, generateConfiguredFlowProposal, publicAiModelConfiguration } from './ai-model-config.js';
|
|
21
|
+
import { proposalService } from './proposal-client.js';
|
|
22
|
+
import { resolveManagedAuthorityEnvironment } from './managed-environment.js';
|
|
23
|
+
import { readManagedProjectState } from './managed-project.js';
|
|
17
24
|
function loadProjectEnvironment(file = path.resolve('.env.local')) {
|
|
18
25
|
if (!fs.existsSync(file))
|
|
19
26
|
return;
|
|
@@ -79,11 +86,319 @@ export async function handleCommand(command, args) {
|
|
|
79
86
|
return handleFlowDeploy(args);
|
|
80
87
|
case 'publish':
|
|
81
88
|
return handleManagedPublish(args);
|
|
89
|
+
case 'ai':
|
|
90
|
+
return handleAi(args);
|
|
91
|
+
case 'proposals':
|
|
92
|
+
return handleProposals(args);
|
|
93
|
+
case 'module':
|
|
94
|
+
case 'modules':
|
|
95
|
+
return handleModules(args);
|
|
96
|
+
case 'sync':
|
|
97
|
+
return handleSourceSync(args);
|
|
82
98
|
case 'help':
|
|
83
99
|
default:
|
|
84
100
|
return handleHelp();
|
|
85
101
|
}
|
|
86
102
|
}
|
|
103
|
+
function handleModules(args) {
|
|
104
|
+
const action = args[0] || 'list';
|
|
105
|
+
if (action === 'list') {
|
|
106
|
+
for (const module of listFeltDBModules())
|
|
107
|
+
console.log(`${module.id}\t${module.version}\t${module.compatibility.join(', ')}`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (action === 'inspect' && args[1]) {
|
|
111
|
+
const flowPath = path.resolve('feltdb.flow');
|
|
112
|
+
const spec = args.includes('--application') && fs.existsSync(flowPath) ? parseFlowSpec(fs.readFileSync(flowPath, 'utf8')) : undefined;
|
|
113
|
+
const declaration = spec?.modules?.find(value => value.name === args[1]);
|
|
114
|
+
const module = resolveFeltDBModule(declaration ?? { name: args[1], provider: args.find((value, index) => index > 1 && !value.startsWith('--')) || 'stripe-compatible' });
|
|
115
|
+
const usage = spec?.collections.flatMap(collection => collection.fields.filter(field => field.type.startsWith(`ref ${module.id}.`)).map(field => `${collection.name}.${field.name} -> ${field.type.slice(4)}`)) ?? [];
|
|
116
|
+
console.log(JSON.stringify({ ...module, application_usage: usage }, null, 2));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
throw new Error('Usage: feltdb module [list|inspect <id> [provider]]');
|
|
120
|
+
}
|
|
121
|
+
function proposalFile(args) {
|
|
122
|
+
const value = args.find(argument => !argument.startsWith('--') && !['review', 'apply'].includes(argument));
|
|
123
|
+
if (!value)
|
|
124
|
+
throw new Error('A proposal JSON file is required');
|
|
125
|
+
return path.resolve(value);
|
|
126
|
+
}
|
|
127
|
+
function validateRepositoryBuild(args) {
|
|
128
|
+
const packagePath = path.resolve('package.json');
|
|
129
|
+
const projectPackage = fs.existsSync(packagePath) ? JSON.parse(fs.readFileSync(packagePath, 'utf8')) : {};
|
|
130
|
+
if (args.includes('--no-build'))
|
|
131
|
+
return;
|
|
132
|
+
const verification = ['typecheck', 'test', 'build'].filter(name => projectPackage.scripts?.[name]);
|
|
133
|
+
if (!verification.length)
|
|
134
|
+
return;
|
|
135
|
+
console.log('Validating the updated application before recording applied status…');
|
|
136
|
+
for (const name of verification) {
|
|
137
|
+
const checked = spawnSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', name], { cwd: process.cwd(), stdio: 'inherit' });
|
|
138
|
+
if (checked.status !== 0)
|
|
139
|
+
throw new Error(`APPLICATION_VALIDATION_FAILED: ${name} exited ${checked.status ?? 'without a status'}; repository restored and proposal remains approved`);
|
|
140
|
+
console.log(`✓ ${name} passed`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function handleAi(args) {
|
|
144
|
+
loadProjectEnvironment();
|
|
145
|
+
if (args[0] === 'model') {
|
|
146
|
+
const action = args[1] || 'show';
|
|
147
|
+
if (action === 'show') {
|
|
148
|
+
const model = publicAiModelConfiguration();
|
|
149
|
+
console.log(`Provider: ${model.provider}\nModel: ${model.model}\nLocation: ${model.local ? 'local browser (private)' : model.baseUrl}\nCredential: ${model.credentialConfigured ? 'configured securely' : 'not configured'}`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (action === 'set') {
|
|
153
|
+
const option = (name) => args.includes(name) ? args[args.indexOf(name) + 1] : undefined;
|
|
154
|
+
const provider = (option('--provider') || 'local-webllm');
|
|
155
|
+
if (!['local-webllm', 'openai-compatible'].includes(provider))
|
|
156
|
+
throw new Error('Provider must be local-webllm or openai-compatible');
|
|
157
|
+
const modelName = option('--model');
|
|
158
|
+
if (!modelName)
|
|
159
|
+
throw new Error('--model is required');
|
|
160
|
+
const apiKey = args.includes('--api-key-stdin') ? fs.readFileSync(0, 'utf8').trim() : process.env.FELTDB_AI_API_KEY;
|
|
161
|
+
const configured = configureAiModel({ provider, model: modelName, baseUrl: option('--base-url'), apiKey: apiKey || undefined,
|
|
162
|
+
credentialRequired: !args.includes('--no-credential') });
|
|
163
|
+
console.log(`✓ AI model configured: ${configured.provider} / ${configured.model}`);
|
|
164
|
+
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.');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
throw new Error('Usage: feltdb ai model [show|set --provider <local-webllm|openai-compatible> --model <id> [--base-url <url>] [--api-key-stdin|--no-credential]]');
|
|
168
|
+
}
|
|
169
|
+
if (!args.length || args.every(argument => ['--dry-run', '--preview', '--ephemeral', '--no-open'].includes(argument))) {
|
|
170
|
+
console.log('Opening the local Ask FeltDB experience. Generation stays in your browser via WebGPU.');
|
|
171
|
+
return handleStudio(args, studioUrl => console.log(`Ask FeltDB: ${studioUrl}`));
|
|
172
|
+
}
|
|
173
|
+
const action = args[0];
|
|
174
|
+
if (action === 'proposals')
|
|
175
|
+
return handleProposals(['list', ...args.slice(1)]);
|
|
176
|
+
if (action === 'proposal') {
|
|
177
|
+
const id = args[1];
|
|
178
|
+
if (!id)
|
|
179
|
+
throw new Error('Usage: feltdb ai proposal <id> [--history]');
|
|
180
|
+
return handleProposals(['get', id, ...args.slice(2)]);
|
|
181
|
+
}
|
|
182
|
+
if (action === 'approve') {
|
|
183
|
+
const id = args[1];
|
|
184
|
+
if (!id)
|
|
185
|
+
throw new Error('Usage: feltdb ai approve <id> [--yes]');
|
|
186
|
+
const client = proposalService();
|
|
187
|
+
const proposal = await client.get(id);
|
|
188
|
+
const readiness = await client.status(id);
|
|
189
|
+
if (proposal.status !== 'previewed')
|
|
190
|
+
throw new Error(`Cannot approve Proposal ${id}. Current status: ${proposal.status}. A current preview is required.`);
|
|
191
|
+
if (readiness.blockers.length)
|
|
192
|
+
throw new Error(`Cannot approve Proposal ${id}. ${readiness.blockers.join(', ')}. Regenerate the Proposal before approval.`);
|
|
193
|
+
if (!proposal.preview_id)
|
|
194
|
+
throw new Error(`Cannot approve Proposal ${id}. Preview is missing.`);
|
|
195
|
+
await client.getPreview(proposal.preview_id);
|
|
196
|
+
console.log(renderProposalDiagnostic(proposal, { readiness }).join('\n'));
|
|
197
|
+
console.log('Repository source will NOT be modified by approval.');
|
|
198
|
+
let approved = args.includes('--yes');
|
|
199
|
+
if (!approved && process.stdin.isTTY) {
|
|
200
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
201
|
+
approved = /^y(?:es)?$/i.test(await prompt.question('Approve this proposal? [y/N] '));
|
|
202
|
+
prompt.close();
|
|
203
|
+
}
|
|
204
|
+
if (!approved) {
|
|
205
|
+
console.log('Approval cancelled.');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const value = await client.approve(id, 'cli-developer', args.includes('--approve-authorization'));
|
|
209
|
+
console.log(`✓ Proposal ${value.id} approved`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (action === 'context') {
|
|
213
|
+
const context = await loadLocalDevelopmentContract();
|
|
214
|
+
console.log(JSON.stringify({ snapshot: context.snapshot, flowHash: context.flowHash }, null, 2));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (action === 'review') {
|
|
218
|
+
const input = JSON.parse(fs.readFileSync(proposalFile(args.slice(1)), 'utf8'));
|
|
219
|
+
const proposal = input;
|
|
220
|
+
const review = await reviewFlowProposal(proposal);
|
|
221
|
+
console.log(`\n${proposal.summary || 'FeltDB change proposal'}`);
|
|
222
|
+
for (const line of describeProposal(review))
|
|
223
|
+
console.log(` ${line}`);
|
|
224
|
+
if (!describeProposal(review).length)
|
|
225
|
+
console.log(' No contract changes.');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (action === 'apply') {
|
|
229
|
+
const target = args.slice(1).find(value => !value.startsWith('--'));
|
|
230
|
+
if (!target)
|
|
231
|
+
throw new Error('A proposal id or JSON file is required');
|
|
232
|
+
const stored = target.startsWith('p_') ? await proposalService().get(target) : undefined;
|
|
233
|
+
const proposal = stored ? { baseContractHash: stored.base_contract_hash, baseFlowHash: stored.base_flow_hash,
|
|
234
|
+
proposedFlow: stored.proposed_flow, summary: stored.summary, changes: stored.contract_diff, warnings: stored.warnings }
|
|
235
|
+
: JSON.parse(fs.readFileSync(path.resolve(target), 'utf8'));
|
|
236
|
+
if (stored && stored.status !== 'approved')
|
|
237
|
+
throw new Error(`Cannot apply Proposal ${stored.id}. Current status: ${stored.status}. Approval is required. Run: feltdb ai approve ${stored.id}`);
|
|
238
|
+
if (!stored && !args.includes('--approve'))
|
|
239
|
+
throw new Error('Explicit approval is required: rerun with --approve');
|
|
240
|
+
const current = await loadLocalDevelopmentContract();
|
|
241
|
+
if (stored && (stored.application_id !== current.snapshot.application.application_id || stored.environment !== current.snapshot.application.environment)) {
|
|
242
|
+
throw new Error(`PROPOSAL_SCOPE_MISMATCH: ${stored.application_id}/${stored.environment} cannot apply to ${current.snapshot.application.application_id}/${current.snapshot.application.environment}`);
|
|
243
|
+
}
|
|
244
|
+
const alreadyWritten = stored && await import('@feltdb/core').then(module => module.flowSourceHash(stored.proposed_flow)) === current.flowHash;
|
|
245
|
+
if (alreadyWritten) {
|
|
246
|
+
console.log('Detected an approved proposal whose flow is already present; reconciling the interrupted application.');
|
|
247
|
+
const recoveredReview = await reviewFlowProposal({ ...proposal, baseContractHash: current.snapshot.contract_hash, baseFlowHash: current.flowHash });
|
|
248
|
+
await applyFlowProposalAtomic({ ...proposal, baseContractHash: current.snapshot.contract_hash, baseFlowHash: current.flowHash, proposedFlow: current.source }, process.cwd(), {
|
|
249
|
+
approveAuthorization: true, replaceGenerated: args.includes('--replace-generated'), verify: () => validateRepositoryBuild(args),
|
|
250
|
+
});
|
|
251
|
+
await proposalService().transition(stored.id, 'applied', 'cli-repository-applier', true);
|
|
252
|
+
console.log(`✓ Reconciled and marked ${stored.id} applied`);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (stored) {
|
|
256
|
+
const client = proposalService();
|
|
257
|
+
const readiness = await client.status(stored.id);
|
|
258
|
+
if (readiness.blockers.length)
|
|
259
|
+
throw new Error(`Cannot apply Proposal ${stored.id}. Proposal is stale: ${readiness.blockers.join(', ')}. Proposal contract: ${stored.base_contract_hash}. Current contract: ${readiness.current_contract_hash}. Regenerate the Proposal before applying.`);
|
|
260
|
+
if (!stored.preview_id)
|
|
261
|
+
throw new Error(`Cannot apply Proposal ${stored.id}. A current preview is required.`);
|
|
262
|
+
await client.getPreview(stored.preview_id);
|
|
263
|
+
assertProposalSourcePlan(stored.source_plan);
|
|
264
|
+
const repository = await repositoryContext(process.cwd(), stored.source_plan.files.map(value => value.path));
|
|
265
|
+
if (repository.dirty.length && !args.includes('--allow-dirty'))
|
|
266
|
+
throw new Error(`Cannot apply Proposal ${stored.id}. Repository conflict: ${repository.dirty.join(', ')}. Resolve the conflict and retry.`);
|
|
267
|
+
}
|
|
268
|
+
const reviewOnly = await reviewFlowProposal(proposal);
|
|
269
|
+
console.log(`\n${proposal.summary || 'FeltDB change proposal'}`);
|
|
270
|
+
for (const line of describeProposal(reviewOnly))
|
|
271
|
+
console.log(` ${line}`);
|
|
272
|
+
console.log(` Source impact: feltdb.flow, src/feltdb/generated-contract.ts`);
|
|
273
|
+
if (args.includes('--dry-run') || args.includes('--preview')) {
|
|
274
|
+
console.log('✓ Preview complete. No files, flow, contract, or published state were changed.');
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
let confirmed = args.includes('--yes');
|
|
278
|
+
if (!confirmed && process.stdin.isTTY) {
|
|
279
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
280
|
+
confirmed = /^y(?:es)?$/i.test(await prompt.question(`Apply ${stored?.id || 'this proposal'} to repository source? [y/N] `));
|
|
281
|
+
prompt.close();
|
|
282
|
+
}
|
|
283
|
+
if (!confirmed)
|
|
284
|
+
throw new Error('Apply cancelled. Re-run with --yes for non-interactive application.');
|
|
285
|
+
const review = await applyFlowProposalAtomic(proposal, process.cwd(), {
|
|
286
|
+
allowDirty: args.includes('--allow-dirty'),
|
|
287
|
+
approveAuthorization: args.includes('--approve-authorization'),
|
|
288
|
+
replaceGenerated: args.includes('--replace-generated'),
|
|
289
|
+
verify: () => validateRepositoryBuild(args),
|
|
290
|
+
});
|
|
291
|
+
console.log(`✓ Applied validated flow proposal (${review.proposedContract.hashes.application})`);
|
|
292
|
+
for (const file of review.written)
|
|
293
|
+
console.log(` ${file}`);
|
|
294
|
+
if (stored)
|
|
295
|
+
await proposalService().transition(stored.id, 'applied', 'cli-repository-applier', true);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const developerIntent = args.filter(value => !value.startsWith('--')).join(' ').trim();
|
|
299
|
+
if (developerIntent) {
|
|
300
|
+
const resolved = resolveModuleIntent(developerIntent);
|
|
301
|
+
if (resolved) {
|
|
302
|
+
console.log('Understanding application...');
|
|
303
|
+
const current = await loadLocalDevelopmentContract();
|
|
304
|
+
console.log('✓ Application contract loaded');
|
|
305
|
+
const proposal = await createModuleImplementationProposal({ intent: developerIntent, snapshot: current.snapshot, currentFlow: current.source });
|
|
306
|
+
console.log(`✓ ${proposal.implementation?.module.name} ${proposal.implementation?.module.version} resolved`);
|
|
307
|
+
console.log(`✓ Billing owner: ${proposal.implementation?.owner}`);
|
|
308
|
+
const review = await reviewFlowProposal(proposal);
|
|
309
|
+
if (proposal.implementation?.alreadyInstalled) {
|
|
310
|
+
console.log(`${proposal.implementation.module.name} is already installed.`);
|
|
311
|
+
const missing = [...review.diff.added, ...review.diff.changed];
|
|
312
|
+
if (!missing.length) {
|
|
313
|
+
console.log('Current implementation is complete. No proposal created.');
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
console.log('Missing:');
|
|
317
|
+
for (const value of missing)
|
|
318
|
+
console.log(` ${value}`);
|
|
319
|
+
console.log('Creating a completion proposal.');
|
|
320
|
+
}
|
|
321
|
+
console.log('Generating implementation proposal...');
|
|
322
|
+
const client = proposalService();
|
|
323
|
+
const parent = (await client.list({ status: 'applied', limit: 100 })).proposals.find(value => value.metadata?.module === resolved.module);
|
|
324
|
+
let stored = await client.create({ application_id: current.snapshot.application.application_id, environment: current.snapshot.application.environment,
|
|
325
|
+
base_contract_hash: proposal.baseContractHash, base_flow_hash: proposal.baseFlowHash, proposed_flow: proposal.proposedFlow,
|
|
326
|
+
proposal_version: 1, contract_diff: [], source_plan: { files: [] },
|
|
327
|
+
summary: proposal.summary, warnings: proposal.warnings, actor: 'cli-developer', parent_proposal_id: parent?.id, metadata: { created_from: 'module-intent', module: resolved.module, module_version: resolved.version } });
|
|
328
|
+
await client.validate(stored.id, 'feltdb-validator', review.proposedContract.manifest);
|
|
329
|
+
const preview = await client.preview(stored.id, 'cli-developer');
|
|
330
|
+
stored = preview.proposal;
|
|
331
|
+
const readiness = await client.status(stored.id);
|
|
332
|
+
console.log(renderProposalDiagnostic(stored, { readiness, preview: preview }).join('\n'));
|
|
333
|
+
console.log(`Use:\n feltdb ai proposal ${stored.id}`);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
throw new Error('Usage: feltdb ai ["implement <module>"|context|proposals|proposal <id>|approve <id>|review <proposal.json>|apply <id>]');
|
|
338
|
+
}
|
|
339
|
+
async function handleProposals(args) {
|
|
340
|
+
loadProjectEnvironment();
|
|
341
|
+
const client = proposalService();
|
|
342
|
+
const action = args[0] || 'list';
|
|
343
|
+
const id = args[1];
|
|
344
|
+
if (action === 'list') {
|
|
345
|
+
const option = (name) => args.includes(name) ? args[args.indexOf(name) + 1] : undefined;
|
|
346
|
+
const result = await client.list({ status: option('--status'), environment: option('--environment'), limit: option('--limit') ? Number(option('--limit')) : undefined, cursor: option('--cursor') });
|
|
347
|
+
if (args.includes('--json')) {
|
|
348
|
+
console.log(JSON.stringify(result, null, 2));
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const { proposals } = result;
|
|
352
|
+
console.log('PROPOSALS');
|
|
353
|
+
for (const value of proposals)
|
|
354
|
+
console.log(`${value.id}\t${value.status.toUpperCase()}\t${value.summary}\t${value.created_at}`);
|
|
355
|
+
if (result.pagination?.nextCursor)
|
|
356
|
+
console.log(`Next: --cursor ${result.pagination.nextCursor}`);
|
|
357
|
+
if (!proposals.length)
|
|
358
|
+
console.log('No proposals.');
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (!id)
|
|
362
|
+
throw new Error(`feltdb proposals ${action} requires a proposal id`);
|
|
363
|
+
if (action === 'get') {
|
|
364
|
+
const proposal = await client.get(id);
|
|
365
|
+
const [readiness, history] = await Promise.all([client.status(id), client.history(id)]);
|
|
366
|
+
const preview = proposal.preview_id ? await client.getPreview(proposal.preview_id).catch(() => undefined) : undefined;
|
|
367
|
+
const result = { proposal, readiness, preview, history: history.events };
|
|
368
|
+
if (args.includes('--json'))
|
|
369
|
+
console.log(JSON.stringify(result, null, 2));
|
|
370
|
+
else
|
|
371
|
+
console.log(renderProposalDiagnostic(proposal, { readiness, history: history.events, preview: preview }).join('\n'));
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (action === 'diff') {
|
|
375
|
+
const value = await client.get(id);
|
|
376
|
+
console.log(value.contract_diff.join('\n'));
|
|
377
|
+
console.log(JSON.stringify(value.source_plan, null, 2));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (action === 'approve' || action === 'reject') {
|
|
381
|
+
const value = action === 'approve' ? await client.approve(id, 'local-developer', args.includes('--approve-authorization')) : await client.reject(id, 'local-developer');
|
|
382
|
+
console.log(`✓ ${value.id} ${value.status}`);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
throw new Error('Usage: feltdb proposals [list|get|diff|approve|reject] [id]');
|
|
386
|
+
}
|
|
387
|
+
async function handleSourceSync(args) {
|
|
388
|
+
loadProjectEnvironment();
|
|
389
|
+
const context = await loadLocalDevelopmentContract();
|
|
390
|
+
const proposal = {
|
|
391
|
+
baseContractHash: context.snapshot.contract_hash,
|
|
392
|
+
baseFlowHash: context.flowHash,
|
|
393
|
+
proposedFlow: context.source,
|
|
394
|
+
summary: 'Synchronize generated source with feltdb.flow',
|
|
395
|
+
changes: [], warnings: [],
|
|
396
|
+
};
|
|
397
|
+
const review = await reviewFlowProposal(proposal);
|
|
398
|
+
const result = syncGeneratedSource(review, process.cwd(), { replace: args.includes('--replace-generated') });
|
|
399
|
+
for (const file of result.written)
|
|
400
|
+
console.log(`✓ Synchronized ${file}`);
|
|
401
|
+
}
|
|
87
402
|
/**
|
|
88
403
|
* Inspect the durable investigation store.
|
|
89
404
|
*
|
|
@@ -234,83 +549,29 @@ async function handleFlowDeploy(args) {
|
|
|
234
549
|
}
|
|
235
550
|
}
|
|
236
551
|
async function handleManagedPublish(args) {
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const token = process.env.VITE_FELTDB_MANAGED_API_KEY;
|
|
246
|
-
const tenantId = process.env.VITE_FELTDB_MANAGED_TENANT_ID;
|
|
247
|
-
const applicationId = process.env.VITE_FELTDB_MANAGED_APPLICATION_ID;
|
|
248
|
-
const namespace = process.env.VITE_FELTDB_MANAGED_NAMESPACE || config.namespace;
|
|
249
|
-
const environment = process.env.VITE_FELTDB_MANAGED_ENVIRONMENT || 'production';
|
|
250
|
-
const missing = [
|
|
251
|
-
['VITE_FELTDB_MANAGED_URL', url],
|
|
252
|
-
['VITE_FELTDB_MANAGED_API_KEY', token],
|
|
253
|
-
['VITE_FELTDB_MANAGED_TENANT_ID', tenantId],
|
|
254
|
-
['VITE_FELTDB_MANAGED_APPLICATION_ID', applicationId],
|
|
255
|
-
['VITE_FELTDB_MANAGED_NAMESPACE', namespace],
|
|
256
|
-
].filter(([, value]) => !value).map(([name]) => name);
|
|
257
|
-
if (missing.length)
|
|
258
|
-
throw new Error(`Managed publish configuration is incomplete in .env.local: ${missing.join(', ')}`);
|
|
259
|
-
const file = flowFile(args);
|
|
260
|
-
const spec = readFlowSpec(file);
|
|
261
|
-
const diagnostics = validateFlowSpec(spec).filter(value => value.severity === 'error');
|
|
262
|
-
if (diagnostics.length)
|
|
263
|
-
throw new Error(diagnostics.map(value => value.message).join('; '));
|
|
264
|
-
console.log('FeltDB Publish');
|
|
265
|
-
console.log(`Application: ${spec.app}`);
|
|
266
|
-
console.log(`Environment: ${environment}`);
|
|
267
|
-
console.log(`Namespace: ${namespace}`);
|
|
268
|
-
console.log(`API: ${url}`);
|
|
269
|
-
console.log('✓ Configuration loaded');
|
|
270
|
-
if (!args.includes('--no-build')) {
|
|
271
|
-
const manifestPath = path.resolve('package.json');
|
|
272
|
-
if (!fs.existsSync(manifestPath))
|
|
273
|
-
throw new Error('package.json is required to build the application');
|
|
274
|
-
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
275
|
-
if (!manifest.scripts?.build)
|
|
276
|
-
throw new Error('package.json must define a build script for managed publish');
|
|
277
|
-
const build = spawnSync('npm', ['run', 'build'], { cwd: process.cwd(), stdio: 'inherit' });
|
|
278
|
-
if (build.error)
|
|
279
|
-
throw build.error;
|
|
280
|
-
if (build.status !== 0)
|
|
281
|
-
throw new Error(`Application build failed with exit code ${build.status}`);
|
|
282
|
-
}
|
|
283
|
-
console.log('✓ Build completed');
|
|
284
|
-
console.log('✓ Application validated');
|
|
285
|
-
const remote = createFeltDB({ namespace, server: { url: url, token: token } });
|
|
286
|
-
try {
|
|
287
|
-
const current = await remote.collection('_flow_apps').get(spec.app);
|
|
288
|
-
const migration = planFlowSpecMigration(current?.spec ?? emptyFlowSpec(spec.app), spec);
|
|
289
|
-
const destructive = migration.filter(value => value.safety === 'destructive');
|
|
290
|
-
if (destructive.length && !args.includes('--allow-destructive')) {
|
|
291
|
-
throw new Error(`Publish contains destructive changes (${destructive.map(value => value.target).join(', ')}); review them and rerun with --allow-destructive`);
|
|
552
|
+
const valueAfter = (flag) => { const index = args.indexOf(flag); return index >= 0 ? args[index + 1] : undefined; };
|
|
553
|
+
const confirm = async (question) => {
|
|
554
|
+
if (!process.stdin.isTTY)
|
|
555
|
+
return false;
|
|
556
|
+
const readline = await import('node:readline/promises');
|
|
557
|
+
const prompt = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
558
|
+
try {
|
|
559
|
+
return !/^(n|no)$/i.test((await prompt.question(`${question} [Y/n] `)).trim());
|
|
292
560
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
console.log(`Deployment: ${publishState.deploymentId}`);
|
|
308
|
-
console.log(`Version: ${result.version}`);
|
|
309
|
-
console.log('Your app is live.');
|
|
310
|
-
}
|
|
311
|
-
finally {
|
|
312
|
-
await remote.close();
|
|
313
|
-
}
|
|
561
|
+
finally {
|
|
562
|
+
prompt.close();
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
await publishApplication({
|
|
566
|
+
environment: valueAfter('--environment'),
|
|
567
|
+
message: valueAfter('--message'),
|
|
568
|
+
dryRun: args.includes('--dry-run'),
|
|
569
|
+
status: args.includes('--status'),
|
|
570
|
+
yes: args.includes('--yes'),
|
|
571
|
+
approveDestructive: args.includes('--approve-destructive-changes') || args.includes('--allow-destructive'),
|
|
572
|
+
noBuild: args.includes('--no-build'),
|
|
573
|
+
confirm,
|
|
574
|
+
});
|
|
314
575
|
}
|
|
315
576
|
async function handleServer(args) {
|
|
316
577
|
const require = createRequire(import.meta.url);
|
|
@@ -500,17 +761,41 @@ For more info, visit: https://github.com/rkendel1/feltdb
|
|
|
500
761
|
`);
|
|
501
762
|
}
|
|
502
763
|
async function handleConnect(args) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
764
|
+
loadProjectEnvironment();
|
|
765
|
+
const managed = readManagedProjectState();
|
|
766
|
+
const projectFile = path.resolve('feltdb.config.json');
|
|
767
|
+
const project = fs.existsSync(projectFile) ? JSON.parse(fs.readFileSync(projectFile, 'utf8')) : {};
|
|
768
|
+
const url = (args[0]
|
|
769
|
+
|| managed.url
|
|
770
|
+
|| process.env.VITE_FELTDB_MANAGED_URL
|
|
771
|
+
|| process.env.VITE_FELTDB_URL
|
|
772
|
+
|| project.server?.url
|
|
773
|
+
|| (project.runtime === 'browser' ? '' : 'http://localhost:7700')).replace(/\/$/, '');
|
|
774
|
+
if (!url)
|
|
775
|
+
throw new Error('This project uses the embedded browser runtime; there is no server endpoint to connect to. Run npm run dev.');
|
|
509
776
|
console.log('🔗 Connecting to FeltDB Server\n');
|
|
510
777
|
console.log(`URL: ${url}`);
|
|
511
|
-
console.log('Discovering node...');
|
|
512
|
-
console.log('Checking capabilities...');
|
|
513
778
|
console.log('Verifying connection...\n');
|
|
779
|
+
const controller = new AbortController();
|
|
780
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
781
|
+
let health;
|
|
782
|
+
try {
|
|
783
|
+
const response = await fetch(`${url}/health`, {
|
|
784
|
+
headers: process.env.FELTDB_TOKEN || process.env.VITE_FELTDB_API_KEY
|
|
785
|
+
? { Authorization: `Bearer ${process.env.FELTDB_TOKEN || process.env.VITE_FELTDB_API_KEY}` }
|
|
786
|
+
: {},
|
|
787
|
+
signal: controller.signal,
|
|
788
|
+
});
|
|
789
|
+
if (!response.ok)
|
|
790
|
+
throw new Error(`HTTP ${response.status}`);
|
|
791
|
+
health = await response.json();
|
|
792
|
+
}
|
|
793
|
+
catch (error) {
|
|
794
|
+
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}`);
|
|
795
|
+
}
|
|
796
|
+
finally {
|
|
797
|
+
clearTimeout(timeout);
|
|
798
|
+
}
|
|
514
799
|
// Store connection info
|
|
515
800
|
const configDir = path.join(process.cwd(), '.feltdb');
|
|
516
801
|
if (!fs.existsSync(configDir)) {
|
|
@@ -520,15 +805,16 @@ async function handleConnect(args) {
|
|
|
520
805
|
fs.writeFileSync(connFile, JSON.stringify({
|
|
521
806
|
url,
|
|
522
807
|
connected: new Date().toISOString(),
|
|
523
|
-
authenticated:
|
|
808
|
+
authenticated: Boolean(process.env.FELTDB_TOKEN || process.env.VITE_FELTDB_API_KEY),
|
|
809
|
+
namespace: managed.namespace || project.namespace || 'default',
|
|
810
|
+
runtime: project.runtime || 'self-hosted',
|
|
524
811
|
}, null, 2));
|
|
525
812
|
console.log('✅ Connected to FeltDB\n');
|
|
526
813
|
console.log('Endpoint: ' + url);
|
|
527
|
-
console.log(
|
|
528
|
-
console.log(
|
|
529
|
-
console.log(
|
|
530
|
-
console.log(
|
|
531
|
-
console.log('Health: ✓\n');
|
|
814
|
+
console.log(`Namespace: ${managed.namespace || project.namespace || 'default'}`);
|
|
815
|
+
console.log(`Authenticated: ${process.env.FELTDB_TOKEN || process.env.VITE_FELTDB_API_KEY ? 'yes' : 'no'}`);
|
|
816
|
+
console.log(`Runtime: ${project.runtime || 'self-hosted'}`);
|
|
817
|
+
console.log(`Health: ${health.status || 'healthy'}\n`);
|
|
532
818
|
console.log('Use: feltdb status (to check connection)\n');
|
|
533
819
|
}
|
|
534
820
|
async function handleExplain(args) {
|
|
@@ -561,20 +847,39 @@ async function handleStatus() {
|
|
|
561
847
|
console.log('Fabric: Enabled');
|
|
562
848
|
console.log('Authentication: Development');
|
|
563
849
|
console.log('Health: ✓\n');
|
|
850
|
+
await displayContractStatus();
|
|
564
851
|
return;
|
|
565
852
|
}
|
|
566
853
|
const connection = JSON.parse(fs.readFileSync(connFile, 'utf-8'));
|
|
567
854
|
console.log('Connected to Server\n');
|
|
568
855
|
console.log(`Endpoint: ${connection.url}`);
|
|
569
|
-
console.log(
|
|
856
|
+
console.log(`Namespace: ${connection.namespace || 'default'}`);
|
|
570
857
|
console.log(`Authenticated: ${connection.authenticated ? 'yes' : 'no'}`);
|
|
571
|
-
console.log(
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
858
|
+
console.log(`Runtime: ${connection.runtime || 'self-hosted'}`);
|
|
859
|
+
try {
|
|
860
|
+
const response = await fetch(`${String(connection.url).replace(/\/$/, '')}/health`, { signal: AbortSignal.timeout(5000) });
|
|
861
|
+
if (!response.ok)
|
|
862
|
+
throw new Error(`HTTP ${response.status}`);
|
|
863
|
+
const health = await response.json();
|
|
864
|
+
console.log(`Health: ${health.status || 'healthy'}\n`);
|
|
865
|
+
}
|
|
866
|
+
catch {
|
|
867
|
+
console.log('Health: unavailable');
|
|
868
|
+
console.log('Start the configured runtime and run feltdb connect again.\n');
|
|
869
|
+
}
|
|
870
|
+
await displayContractStatus();
|
|
871
|
+
}
|
|
872
|
+
async function displayContractStatus() {
|
|
873
|
+
if (!fs.existsSync(path.resolve('feltdb.flow')))
|
|
874
|
+
return;
|
|
875
|
+
loadProjectEnvironment();
|
|
876
|
+
const current = await loadLocalDevelopmentContract();
|
|
877
|
+
const publishedFile = path.resolve('.feltdb/last-published.json');
|
|
878
|
+
const published = fs.existsSync(publishedFile) ? JSON.parse(fs.readFileSync(publishedFile, 'utf8')) : undefined;
|
|
879
|
+
console.log('Application contract');
|
|
880
|
+
console.log(`Source: ${current.snapshot.contract_hash}`);
|
|
881
|
+
console.log(`Published: ${published?.contractHash || 'unknown'}`);
|
|
882
|
+
console.log(`Drift: ${!published?.contractHash ? 'unknown' : published.contractHash === current.snapshot.contract_hash ? 'synchronized' : 'source differs from published'}\n`);
|
|
578
883
|
}
|
|
579
884
|
async function handleWorkspace(args) {
|
|
580
885
|
const subcommand = args[0] || 'status';
|
|
@@ -622,7 +927,7 @@ async function handleWorkspaceConnect(args) {
|
|
|
622
927
|
console.log(' (when @feltdb/client workspace APIs are available)\n');
|
|
623
928
|
}
|
|
624
929
|
async function handleDev(args) {
|
|
625
|
-
var _a
|
|
930
|
+
var _a;
|
|
626
931
|
const projectDir = process.cwd();
|
|
627
932
|
if (args.includes('--help')) {
|
|
628
933
|
console.log('Usage: feltdb dev [--app-url URL] [--studio-port 7701] [--authority-port 7700] [--discovery-port 7799] [--no-open]');
|
|
@@ -675,14 +980,10 @@ async function handleDev(args) {
|
|
|
675
980
|
? { ...configDefaults, ...JSON.parse(fs.readFileSync(configPath, 'utf-8')) }
|
|
676
981
|
: configDefaults;
|
|
677
982
|
if (config.runtime === 'managed') {
|
|
678
|
-
(
|
|
679
|
-
(_b = process.env).VITE_FELTDB_API_KEY || (_b.VITE_FELTDB_API_KEY = process.env.VITE_FELTDB_MANAGED_API_KEY);
|
|
680
|
-
}
|
|
681
|
-
if (config.runtime === 'managed' && (!process.env.VITE_FELTDB_URL || !process.env.VITE_FELTDB_API_KEY)) {
|
|
682
|
-
throw new Error('Managed runtime requires VITE_FELTDB_MANAGED_URL and VITE_FELTDB_MANAGED_API_KEY in .env.local. Re-run managed setup or add your managed connection.');
|
|
983
|
+
resolveManagedAuthorityEnvironment(process.env);
|
|
683
984
|
}
|
|
684
985
|
const runtimeNamespace = config.runtime === 'managed'
|
|
685
|
-
?
|
|
986
|
+
? readManagedProjectState(projectDir).namespace || config.namespace
|
|
686
987
|
: config.namespace;
|
|
687
988
|
const applicationLifecycle = resolveApplicationLifecycle(config);
|
|
688
989
|
// Application URL precedence:
|
|
@@ -771,15 +1072,21 @@ async function handleDev(args) {
|
|
|
771
1072
|
if (compose.status !== 0)
|
|
772
1073
|
throw new Error(`docker compose failed with status ${compose.status ?? 'unknown'}`);
|
|
773
1074
|
selfHostedStarted = true;
|
|
774
|
-
(
|
|
1075
|
+
(_a = process.env).VITE_FELTDB_URL || (_a.VITE_FELTDB_URL = 'http://127.0.0.1:7700');
|
|
775
1076
|
}
|
|
776
1077
|
if (!process.env.VITE_FELTDB_URL) {
|
|
777
1078
|
const authorityPort = Number(args.includes('--authority-port') ? args[args.indexOf('--authority-port') + 1] || '7700' : '7700');
|
|
778
1079
|
localAuthority = await startLocalDevelopmentAuthority({
|
|
779
|
-
dataDir: path.join(projectDir, '.feltdb', 'authority'),
|
|
1080
|
+
dataDir: path.join(projectDir, '.feltdb-data', 'authority'),
|
|
780
1081
|
port: authorityPort,
|
|
1082
|
+
proposalContext: () => loadLocalDevelopmentContract(projectDir),
|
|
781
1083
|
});
|
|
782
1084
|
process.env.VITE_FELTDB_URL = localAuthority.endpoint;
|
|
1085
|
+
const connectionsPath = path.join(projectDir, '.feltdb', 'connections.json');
|
|
1086
|
+
const connections = fs.existsSync(connectionsPath) ? JSON.parse(fs.readFileSync(connectionsPath, 'utf8')) : {};
|
|
1087
|
+
fs.writeFileSync(connectionsPath, `${JSON.stringify({ ...connections, developmentAuthority: {
|
|
1088
|
+
endpoint: localAuthority.endpoint, proposalAuthorityToken: localAuthority.proposalAuthorityToken,
|
|
1089
|
+
} }, null, 2)}\n`, { mode: 0o600 });
|
|
783
1090
|
}
|
|
784
1091
|
const authorityUrl = process.env.VITE_FELTDB_URL;
|
|
785
1092
|
if (!authorityUrl)
|
|
@@ -1239,10 +1546,11 @@ async function handleStudio(args, onReady) {
|
|
|
1239
1546
|
loadProjectEnvironment();
|
|
1240
1547
|
const projectConfigPath = path.resolve('feltdb.config.json');
|
|
1241
1548
|
const projectConfig = fs.existsSync(projectConfigPath) ? JSON.parse(fs.readFileSync(projectConfigPath, 'utf8')) : {};
|
|
1549
|
+
const managedProject = readManagedProjectState();
|
|
1242
1550
|
const projectIsManaged = projectConfig.runtime === 'managed';
|
|
1243
1551
|
const connectUrl = args.includes('--connect')
|
|
1244
1552
|
? args[args.indexOf('--connect') + 1]
|
|
1245
|
-
: projectIsManaged ? process.env.VITE_FELTDB_MANAGED_URL : undefined;
|
|
1553
|
+
: projectIsManaged ? process.env.VITE_FELTDB_MANAGED_URL || managedProject.url : undefined;
|
|
1246
1554
|
const port = args.includes('--port')
|
|
1247
1555
|
? args[args.indexOf('--port') + 1] || '7701'
|
|
1248
1556
|
: '7701';
|
|
@@ -1252,10 +1560,16 @@ async function handleStudio(args, onReady) {
|
|
|
1252
1560
|
const open = !args.includes('--no-open');
|
|
1253
1561
|
const namespace = args.includes('--namespace')
|
|
1254
1562
|
? args[args.indexOf('--namespace') + 1] || 'default'
|
|
1255
|
-
: projectIsManaged ?
|
|
1563
|
+
: projectIsManaged ? managedProject.namespace || projectConfig.namespace || 'default' : 'default';
|
|
1256
1564
|
const runtime = args.includes('--runtime')
|
|
1257
1565
|
? args[args.indexOf('--runtime') + 1] || 'browser'
|
|
1258
1566
|
: projectIsManaged ? 'managed' : connectUrl ? 'remote' : 'browser';
|
|
1567
|
+
const applicationId = args.includes('--application')
|
|
1568
|
+
? args[args.indexOf('--application') + 1] || ''
|
|
1569
|
+
: process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || managedProject.applicationId || '';
|
|
1570
|
+
const environment = args.includes('--environment')
|
|
1571
|
+
? args[args.indexOf('--environment') + 1] || 'production'
|
|
1572
|
+
: managedProject.environment || 'production';
|
|
1259
1573
|
const appUrl = args.includes('--app-url')
|
|
1260
1574
|
? args[args.indexOf('--app-url') + 1]
|
|
1261
1575
|
: undefined;
|
|
@@ -1286,9 +1600,13 @@ async function handleStudio(args, onReady) {
|
|
|
1286
1600
|
console.log('⚠️ Studio application artifacts not found.');
|
|
1287
1601
|
return;
|
|
1288
1602
|
}
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1603
|
+
const canonicalFlow = path.resolve(process.cwd(), 'feltdb.flow');
|
|
1604
|
+
const projectFlow = fs.existsSync(canonicalFlow) ? canonicalFlow : undefined;
|
|
1605
|
+
const studioMutationToken = randomBytes(24).toString('hex');
|
|
1606
|
+
const projectPackage = fs.existsSync(path.resolve('package.json')) ? JSON.parse(fs.readFileSync(path.resolve('package.json'), 'utf8')) : {};
|
|
1607
|
+
const webllmEnabled = Boolean(projectPackage.dependencies?.['@feltdb/webllm'] || projectPackage.devDependencies?.['@feltdb/webllm']);
|
|
1608
|
+
const previewOnly = args.includes('--ephemeral');
|
|
1609
|
+
const proposalClient = proposalService(process.cwd(), session.authorityUrl);
|
|
1292
1610
|
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' };
|
|
1293
1611
|
const server = http.createServer(async (request, response) => {
|
|
1294
1612
|
const pathname = decodeURIComponent(new URL(request.url || '/', 'http://localhost').pathname);
|
|
@@ -1297,14 +1615,210 @@ async function handleStudio(args, onReady) {
|
|
|
1297
1615
|
response.setHeader('Cache-Control', 'no-store');
|
|
1298
1616
|
response.setHeader('Pragma', 'no-cache');
|
|
1299
1617
|
const managedPublish = runtime === 'managed' ? {
|
|
1300
|
-
url:
|
|
1301
|
-
namespace:
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1618
|
+
url: connectUrl || managedProject.url || '',
|
|
1619
|
+
namespace: managedProject.namespace || namespace,
|
|
1620
|
+
tenantId: managedProject.tenantId || '',
|
|
1621
|
+
applicationId: process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || managedProject.applicationId || '',
|
|
1622
|
+
environment: managedProject.environment || 'production',
|
|
1623
|
+
publishEndpoint: '/_feltdb/publish',
|
|
1306
1624
|
} : undefined;
|
|
1307
|
-
response.end(JSON.stringify({ token: connectUrl ? process.env.VITE_FELTDB_API_KEY ||
|
|
1625
|
+
response.end(JSON.stringify({ token: connectUrl ? process.env.VITE_FELTDB_API_KEY || '' : '', managedPublish, session, sourceSync: { enabled: true, webllmEnabled, previewOnly, token: studioMutationToken, aiModel: publicAiModelConfiguration() } }));
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
if (pathname === '/_feltdb/contract') {
|
|
1629
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1630
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
1631
|
+
try {
|
|
1632
|
+
const current = await loadLocalDevelopmentContract();
|
|
1633
|
+
const lastPublished = fs.existsSync(path.resolve('.feltdb/last-published.json'))
|
|
1634
|
+
? JSON.parse(fs.readFileSync(path.resolve('.feltdb/last-published.json'), 'utf8')) : undefined;
|
|
1635
|
+
let runningContract;
|
|
1636
|
+
let runningSnapshot;
|
|
1637
|
+
const applicationId = process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || managedProject.applicationId || lastPublished?.applicationId;
|
|
1638
|
+
if (connectUrl && applicationId) {
|
|
1639
|
+
try {
|
|
1640
|
+
const endpoint = new URL('/v1/application', connectUrl);
|
|
1641
|
+
endpoint.searchParams.set('application_id', applicationId);
|
|
1642
|
+
endpoint.searchParams.set('environment', managedProject.environment || lastPublished?.environment || 'production');
|
|
1643
|
+
const headers = {};
|
|
1644
|
+
const credential = process.env.FELTDB_MANAGED_CONTROL_API_KEY || process.env.VITE_FELTDB_API_KEY;
|
|
1645
|
+
if (credential)
|
|
1646
|
+
headers.authorization = `Bearer ${credential}`;
|
|
1647
|
+
const discovered = await fetch(endpoint, { headers });
|
|
1648
|
+
if (discovered.ok) {
|
|
1649
|
+
const application = await discovered.json();
|
|
1650
|
+
runningSnapshot = application.snapshot;
|
|
1651
|
+
runningContract = runningSnapshot?.contract_hash ?? application.contract_hash;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
catch { /* Drift remains explicitly unknown while the runtime is unavailable. */ }
|
|
1655
|
+
}
|
|
1656
|
+
const knownContracts = [current.snapshot.contract_hash, runningContract, lastPublished?.contractHash].filter(Boolean);
|
|
1657
|
+
response.end(JSON.stringify({ snapshot: runningSnapshot ?? current.snapshot, flowHash: current.flowHash, source: current.source,
|
|
1658
|
+
drift: { source: current.snapshot.contract_hash, running: runningContract, published: lastPublished?.contractHash,
|
|
1659
|
+
synchronized: new Set(knownContracts).size <= 1 } }));
|
|
1660
|
+
}
|
|
1661
|
+
catch (error) {
|
|
1662
|
+
response.statusCode = 400;
|
|
1663
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
1664
|
+
}
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
if (pathname === '/_feltdb/repository-readiness' && request.method === 'GET') {
|
|
1668
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1669
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
1670
|
+
try {
|
|
1671
|
+
const proposalId = new URL(request.url || '/', 'http://localhost').searchParams.get('proposal');
|
|
1672
|
+
if (!proposalId)
|
|
1673
|
+
throw new Error('proposal is required');
|
|
1674
|
+
const proposal = await proposalClient.get(proposalId);
|
|
1675
|
+
assertProposalSourcePlan(proposal.source_plan);
|
|
1676
|
+
const local = await loadLocalDevelopmentContract();
|
|
1677
|
+
const repository = await repositoryContext(process.cwd(), proposal.source_plan.files.map(value => value.path));
|
|
1678
|
+
const contractMatches = local.snapshot.contract_hash === proposal.base_contract_hash;
|
|
1679
|
+
const flowMatches = local.flowHash === proposal.base_flow_hash;
|
|
1680
|
+
response.end(JSON.stringify({ ready: contractMatches && flowMatches && repository.dirty.length === 0,
|
|
1681
|
+
contractMatches, flowMatches, gitClean: repository.dirty.length === 0, conflicts: repository.dirty,
|
|
1682
|
+
branch: repository.branch, commit: repository.commit }));
|
|
1683
|
+
}
|
|
1684
|
+
catch (error) {
|
|
1685
|
+
response.statusCode = 409;
|
|
1686
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
1687
|
+
}
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
if (pathname === '/_feltdb/proposals/list' && request.method === 'GET') {
|
|
1691
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1692
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
1693
|
+
try {
|
|
1694
|
+
response.end(JSON.stringify(await proposalClient.list()));
|
|
1695
|
+
}
|
|
1696
|
+
catch (error) {
|
|
1697
|
+
response.statusCode = 503;
|
|
1698
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
1699
|
+
}
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
const proposalDetail = pathname.match(/^\/_feltdb\/proposals\/(p_[A-Za-z0-9]+)$/);
|
|
1703
|
+
if (proposalDetail && request.method === 'GET') {
|
|
1704
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1705
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
1706
|
+
try {
|
|
1707
|
+
response.end(JSON.stringify({ proposal: await proposalClient.get(proposalDetail[1]), history: (await proposalClient.history(proposalDetail[1])).events }));
|
|
1708
|
+
}
|
|
1709
|
+
catch (error) {
|
|
1710
|
+
response.statusCode = 404;
|
|
1711
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
1712
|
+
}
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
// Studio proposes and previews. CLI applies to the repository. FeltDB validates
|
|
1716
|
+
// and stores the proposal as application state. Human approves. Nothing else writes source.
|
|
1717
|
+
if (pathname === '/_feltdb/proposals' || pathname === '/_feltdb/proposals/approve' || pathname === '/_feltdb/proposals/reject') {
|
|
1718
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1719
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
1720
|
+
if (request.method !== 'POST') {
|
|
1721
|
+
response.statusCode = 405;
|
|
1722
|
+
response.end(JSON.stringify({ error: 'POST required' }));
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
if (request.headers['x-feltdb-studio-token'] !== studioMutationToken) {
|
|
1726
|
+
response.statusCode = 403;
|
|
1727
|
+
response.end(JSON.stringify({ error: 'Invalid Studio source-sync token' }));
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
try {
|
|
1731
|
+
const chunks = [];
|
|
1732
|
+
for await (const chunk of request)
|
|
1733
|
+
chunks.push(Buffer.from(chunk));
|
|
1734
|
+
const input = chunks.length ? JSON.parse(Buffer.concat(chunks).toString('utf8')) : {};
|
|
1735
|
+
if (pathname.endsWith('/approve') || pathname.endsWith('/reject')) {
|
|
1736
|
+
if (previewOnly)
|
|
1737
|
+
throw new Error('Preview-only sessions cannot approve or reject persisted proposals');
|
|
1738
|
+
if (input.confirm !== true)
|
|
1739
|
+
throw new Error('Explicit proposal confirmation is required');
|
|
1740
|
+
const proposal = await proposalClient.transition(String(input.proposalId || ''), pathname.endsWith('/approve') ? 'approved' : 'rejected', 'studio-developer', false, input.approveAuthorization === true);
|
|
1741
|
+
response.end(JSON.stringify({ status: proposal.status, proposalId: proposal.id,
|
|
1742
|
+
command: proposal.status === 'approved' ? `feltdb ai apply ${proposal.id}` : undefined }));
|
|
1743
|
+
}
|
|
1744
|
+
else {
|
|
1745
|
+
const proposal = input.proposal;
|
|
1746
|
+
const review = await reviewFlowProposal(proposal);
|
|
1747
|
+
const changes = describeProposal(review);
|
|
1748
|
+
let stored;
|
|
1749
|
+
if (!previewOnly) {
|
|
1750
|
+
const current = await loadLocalDevelopmentContract();
|
|
1751
|
+
stored = await proposalClient.create({ application_id: current.snapshot.application.application_id,
|
|
1752
|
+
environment: current.snapshot.application.environment, base_contract_hash: proposal.baseContractHash,
|
|
1753
|
+
base_flow_hash: proposal.baseFlowHash, proposed_flow: proposal.proposedFlow, summary: proposal.summary,
|
|
1754
|
+
proposal_version: 1, contract_diff: [], source_plan: { files: [] },
|
|
1755
|
+
warnings: proposal.warnings, actor: 'studio-developer', metadata: { model: publicAiModelConfiguration().model, created_from: 'studio' } });
|
|
1756
|
+
await proposalClient.validate(stored.id, 'feltdb-validator', review.proposedContract.manifest);
|
|
1757
|
+
stored = (await proposalClient.preview(stored.id, 'studio-developer')).proposal;
|
|
1758
|
+
}
|
|
1759
|
+
response.end(JSON.stringify({ status: 'previewed', proposalId: stored?.id ?? 'ephemeral', changes,
|
|
1760
|
+
sourcePlan: stored?.source_plan.files.map(value => value.path) ?? ['feltdb.flow', 'src/feltdb/generated-contract.ts'], previewOnly,
|
|
1761
|
+
authorizationChanges: review.authorizationChanges, migration: review.migration,
|
|
1762
|
+
contractHash: review.proposedContract.hashes.application }));
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
catch (error) {
|
|
1766
|
+
response.statusCode = 400;
|
|
1767
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
1768
|
+
}
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
if (pathname === '/_feltdb/ai/generate') {
|
|
1772
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1773
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
1774
|
+
if (request.method !== 'POST') {
|
|
1775
|
+
response.statusCode = 405;
|
|
1776
|
+
response.end(JSON.stringify({ error: 'POST required' }));
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
if (request.headers['x-feltdb-studio-token'] !== studioMutationToken) {
|
|
1780
|
+
response.statusCode = 403;
|
|
1781
|
+
response.end(JSON.stringify({ error: 'Invalid Studio token' }));
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
try {
|
|
1785
|
+
const chunks = [];
|
|
1786
|
+
for await (const chunk of request)
|
|
1787
|
+
chunks.push(Buffer.from(chunk));
|
|
1788
|
+
const input = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
1789
|
+
if (!input.intent?.trim())
|
|
1790
|
+
throw new Error('An AI change request is required');
|
|
1791
|
+
const current = await loadLocalDevelopmentContract();
|
|
1792
|
+
const proposal = await generateConfiguredFlowProposal({ intent: input.intent, snapshot: current.snapshot, currentFlow: current.source });
|
|
1793
|
+
response.end(JSON.stringify({ proposal }));
|
|
1794
|
+
}
|
|
1795
|
+
catch (error) {
|
|
1796
|
+
response.statusCode = 400;
|
|
1797
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
1798
|
+
}
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
if (pathname === '/_feltdb/publish') {
|
|
1802
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1803
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
1804
|
+
if (request.method !== 'POST') {
|
|
1805
|
+
response.statusCode = 405;
|
|
1806
|
+
response.end(JSON.stringify({ error: 'POST required' }));
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
try {
|
|
1810
|
+
const chunks = [];
|
|
1811
|
+
for await (const chunk of request)
|
|
1812
|
+
chunks.push(Buffer.from(chunk));
|
|
1813
|
+
const input = chunks.length ? JSON.parse(Buffer.concat(chunks).toString('utf8')) : {};
|
|
1814
|
+
const messages = [];
|
|
1815
|
+
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) });
|
|
1816
|
+
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 }));
|
|
1817
|
+
}
|
|
1818
|
+
catch (error) {
|
|
1819
|
+
response.statusCode = 400;
|
|
1820
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
1821
|
+
}
|
|
1308
1822
|
return;
|
|
1309
1823
|
}
|
|
1310
1824
|
if (pathname === '/_feltdb/project') {
|
|
@@ -1375,6 +1889,9 @@ async function handleStudio(args, onReady) {
|
|
|
1375
1889
|
const parameters = new URLSearchParams({ namespace, runtime });
|
|
1376
1890
|
if (connectUrl)
|
|
1377
1891
|
parameters.set('connect', connectUrl);
|
|
1892
|
+
if (applicationId)
|
|
1893
|
+
parameters.set('application_id', applicationId);
|
|
1894
|
+
parameters.set('environment', environment);
|
|
1378
1895
|
if (appUrl)
|
|
1379
1896
|
parameters.set('app', appUrl);
|
|
1380
1897
|
if (session.workspaceId)
|
|
@@ -1406,6 +1923,24 @@ Commands:
|
|
|
1406
1923
|
diff [file] Diff FlowSpec against the last deployed model
|
|
1407
1924
|
deploy [file] Validate, version, and deploy FlowSpec
|
|
1408
1925
|
publish [file] Build and publish/update a managed application
|
|
1926
|
+
ai Open the canonical interactive local-AI workflow
|
|
1927
|
+
ai --dry-run Open an ephemeral preview-only AI session
|
|
1928
|
+
ai model show Show the selected local or user-provided model
|
|
1929
|
+
ai model set Select WebLLM or an OpenAI-compatible model
|
|
1930
|
+
ai context Show the secret-free contract grounding snapshot
|
|
1931
|
+
ai proposals List bounded proposals [--status/--environment/--json]
|
|
1932
|
+
ai proposal <id> Show canonical proposal, readiness, preview, and history
|
|
1933
|
+
ai approve <id> Approve a current preview [--yes]
|
|
1934
|
+
ai review <file> Validate and review a WebLLM flow proposal
|
|
1935
|
+
ai apply <id> Atomically apply an approved proposal [--yes]
|
|
1936
|
+
proposals List durable FeltDB application proposals
|
|
1937
|
+
module list List deterministic FeltDB modules
|
|
1938
|
+
module inspect <id> Inspect a resolved module semantic contract
|
|
1939
|
+
proposals get <id> Show a proposal and its lifecycle history
|
|
1940
|
+
proposals diff <id> Show contract and structured source impact
|
|
1941
|
+
proposals approve <id> Approve a previewed proposal
|
|
1942
|
+
proposals reject <id> Reject a proposed/validated/previewed proposal
|
|
1943
|
+
sync Regenerate owned source from feltdb.flow
|
|
1409
1944
|
studio Launch FeltDB Studio (developer interface)
|
|
1410
1945
|
server Start self-hosted FeltDB server
|
|
1411
1946
|
keys Manage API keys
|
|
@@ -1423,6 +1958,7 @@ Commands:
|
|
|
1423
1958
|
Studio:
|
|
1424
1959
|
feltdb studio Launch local Studio
|
|
1425
1960
|
feltdb studio --connect <url> Connect to remote instance
|
|
1961
|
+
--application <id> --environment <name> Select one application environment
|
|
1426
1962
|
feltdb studio --port 7701 --no-open Start on port 7701 without opening browser
|
|
1427
1963
|
|
|
1428
1964
|
Server Options:
|
|
@@ -1444,6 +1980,13 @@ Connection:
|
|
|
1444
1980
|
doctor Check system health
|
|
1445
1981
|
help Show this help message
|
|
1446
1982
|
|
|
1983
|
+
Publish:
|
|
1984
|
+
feltdb publish --dry-run Validate and preview without activation
|
|
1985
|
+
feltdb publish --yes Publish to the configured environment
|
|
1986
|
+
feltdb publish --status Compare local and active cloud versions
|
|
1987
|
+
feltdb publish --environment staging Override the configured environment
|
|
1988
|
+
feltdb publish --approve-destructive-changes --yes
|
|
1989
|
+
|
|
1447
1990
|
Inspect Sub-commands:
|
|
1448
1991
|
feltdb inspect Show runtime overview
|
|
1449
1992
|
feltdb inspect peers Show connected peers
|
|
@@ -1467,6 +2010,7 @@ Examples:
|
|
|
1467
2010
|
feltdb status
|
|
1468
2011
|
feltdb dev
|
|
1469
2012
|
feltdb build
|
|
2013
|
+
feltdb publish --dry-run
|
|
1470
2014
|
feltdb check
|
|
1471
2015
|
feltdb inspect
|
|
1472
2016
|
feltdb inspect peers
|