@telepat/rilo 0.1.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/LICENSE +21 -0
- package/README.md +209 -0
- package/index.js +1 -0
- package/models/black-forest-labs__flux-2-pro.json +78 -0
- package/models/black-forest-labs__flux-schnell.json +95 -0
- package/models/bytedance__seedream-4.json +71 -0
- package/models/deepseek-ai__deepseek-v3.json +61 -0
- package/models/google__nano-banana-pro.json +92 -0
- package/models/google__veo-3.1-fast.json +93 -0
- package/models/google__veo-3.1.json +93 -0
- package/models/jaaari__kokoro-82m.json +86 -0
- package/models/kwaivgi__kling-v3-video.json +101 -0
- package/models/minimax__speech-02-turbo.json +141 -0
- package/models/pixverse__pixverse-v5.6.json +113 -0
- package/models/prunaai__z-image-turbo.json +107 -0
- package/models/resemble-ai__chatterbox-turbo.json +102 -0
- package/models/wan-video__wan-2.2-i2v-fast.json +139 -0
- package/package.json +67 -0
- package/src/api/firebaseFunction.js +46 -0
- package/src/api/middleware/auth.js +70 -0
- package/src/api/openapi/generateOpenApi.js +21 -0
- package/src/api/openapi/spec.js +831 -0
- package/src/api/routes/jobs.js +45 -0
- package/src/api/routes/projectAssets.js +63 -0
- package/src/api/routes/projects.js +647 -0
- package/src/api/routes/webhooks.js +13 -0
- package/src/api/server.js +88 -0
- package/src/backends/firebaseClient.js +57 -0
- package/src/backends/outputBackend.js +186 -0
- package/src/backends/projectMetadataBackend.js +550 -0
- package/src/cli/commands/openHome.js +70 -0
- package/src/cli/commands/settingsFlow.js +196 -0
- package/src/cli/index.js +192 -0
- package/src/config/env.js +158 -0
- package/src/config/keystore.js +175 -0
- package/src/config/models.js +281 -0
- package/src/config/settingsSchema.js +214 -0
- package/src/media/ffmpeg.js +144 -0
- package/src/media/files.js +77 -0
- package/src/media/subtitles.js +444 -0
- package/src/observability/apiTrace.js +17 -0
- package/src/observability/logger.js +7 -0
- package/src/observability/metrics.js +10 -0
- package/src/pipeline/inputSanitizer.js +6 -0
- package/src/pipeline/orchestrator.js +1669 -0
- package/src/policy/contentGuardrails.js +30 -0
- package/src/providers/predictions.js +188 -0
- package/src/providers/replicateClient.js +12 -0
- package/src/steps/alignSubtitles.js +156 -0
- package/src/steps/burnInSubtitles.js +22 -0
- package/src/steps/composeFinalVideo.js +57 -0
- package/src/steps/generateKeyframes.js +70 -0
- package/src/steps/generateVideoSegments.js +95 -0
- package/src/steps/generateVoiceover.js +128 -0
- package/src/steps/imageToVideoAdapters.js +100 -0
- package/src/steps/script.js +177 -0
- package/src/steps/textToImageAdapters.js +87 -0
- package/src/store/assetStore.js +5 -0
- package/src/store/jobStore.js +102 -0
- package/src/store/projectAnalyticsStore.js +625 -0
- package/src/store/projectStore.js +684 -0
- package/src/store/settingsStore.js +155 -0
- package/src/store/staleAssetStore.js +63 -0
- package/src/types/job.js +28 -0
- package/src/types/media.js +28 -0
- package/src/worker/processor.js +24 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { env } from '../config/env.js';
|
|
4
|
+
import {
|
|
5
|
+
ensureProject,
|
|
6
|
+
ensureProjectConfig,
|
|
7
|
+
getProjectDir,
|
|
8
|
+
getProjectStoryPath,
|
|
9
|
+
listLocalProjects,
|
|
10
|
+
normalizeAndValidateProjectConfig,
|
|
11
|
+
readProjectArtifacts,
|
|
12
|
+
readProjectConfig,
|
|
13
|
+
readProjectMetadata,
|
|
14
|
+
readProjectScriptAsset,
|
|
15
|
+
readProjectRunState,
|
|
16
|
+
readProjectSync,
|
|
17
|
+
resolveProjectName,
|
|
18
|
+
writeProjectConfig,
|
|
19
|
+
writeProjectMetadata,
|
|
20
|
+
writeProjectStory
|
|
21
|
+
} from '../store/projectStore.js';
|
|
22
|
+
import { getFirebaseClients } from './firebaseClient.js';
|
|
23
|
+
import { listProjectSnapshots } from '../store/staleAssetStore.js';
|
|
24
|
+
import {
|
|
25
|
+
listRunRecords,
|
|
26
|
+
readRunRecord,
|
|
27
|
+
summarizeProjectAnalytics,
|
|
28
|
+
summarizeRun
|
|
29
|
+
} from '../store/projectAnalyticsStore.js';
|
|
30
|
+
|
|
31
|
+
function toPosixPath(inputPath) {
|
|
32
|
+
return inputPath.split(path.sep).join('/');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseJsonLines(rawText) {
|
|
36
|
+
return rawText
|
|
37
|
+
.split('\n')
|
|
38
|
+
.map((line) => line.trim())
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.map((line) => {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(line);
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function buildPromptPayload(runState, scriptAsset, logs = []) {
|
|
51
|
+
const artifacts = runState?.artifacts || {};
|
|
52
|
+
const persisted = scriptAsset && typeof scriptAsset === 'object' ? scriptAsset : {};
|
|
53
|
+
const promptCalls = logs.filter((entry) => entry?.type === 'request_start' && entry?.input?.prompt);
|
|
54
|
+
return {
|
|
55
|
+
script: persisted.script || artifacts.script || '',
|
|
56
|
+
shots: persisted.shots || artifacts.shots || [],
|
|
57
|
+
prompts: promptCalls.map((entry) => ({
|
|
58
|
+
model: entry.model,
|
|
59
|
+
step: entry.trace?.step || '',
|
|
60
|
+
index: Number.isInteger(entry.trace?.index) ? entry.trace.index : null,
|
|
61
|
+
prompt: entry.input.prompt
|
|
62
|
+
}))
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isPlainObject(value) {
|
|
67
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function assertCreateProjectPayload({ story, config, metadata }) {
|
|
71
|
+
if (story !== undefined && typeof story !== 'string') {
|
|
72
|
+
throw new Error('story must be a string when provided');
|
|
73
|
+
}
|
|
74
|
+
if (config !== undefined && !isPlainObject(config)) {
|
|
75
|
+
throw new Error('config must be an object when provided');
|
|
76
|
+
}
|
|
77
|
+
if (metadata !== undefined && !isPlainObject(metadata)) {
|
|
78
|
+
throw new Error('metadata must be an object when provided');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function pathExists(targetPath) {
|
|
83
|
+
try {
|
|
84
|
+
await fs.access(targetPath);
|
|
85
|
+
return true;
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function listFilesRecursively(baseDir) {
|
|
92
|
+
const files = [];
|
|
93
|
+
|
|
94
|
+
async function walk(currentDir) {
|
|
95
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
98
|
+
if (entry.isDirectory()) {
|
|
99
|
+
await walk(fullPath);
|
|
100
|
+
} else {
|
|
101
|
+
files.push(fullPath);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (await pathExists(baseDir)) {
|
|
107
|
+
await walk(baseDir);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return files;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export class LocalProjectMetadataBackend {
|
|
114
|
+
async listProjects() {
|
|
115
|
+
return listLocalProjects();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async createProject({ project, story, config, metadata }) {
|
|
119
|
+
assertCreateProjectPayload({ story, config, metadata });
|
|
120
|
+
const resolved = resolveProjectName(project);
|
|
121
|
+
await ensureProject(resolved);
|
|
122
|
+
await ensureProjectConfig(resolved);
|
|
123
|
+
|
|
124
|
+
if (story) {
|
|
125
|
+
await writeProjectStory(resolved, story);
|
|
126
|
+
} else {
|
|
127
|
+
const storyPath = getProjectStoryPath(resolved);
|
|
128
|
+
if (!(await pathExists(storyPath))) {
|
|
129
|
+
await writeProjectStory(resolved, '');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (config && typeof config === 'object') {
|
|
134
|
+
const existingConfig = await readProjectConfig(resolved);
|
|
135
|
+
await writeProjectConfig(resolved, { ...existingConfig, ...config });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const existingMetadata = await readProjectMetadata(resolved);
|
|
139
|
+
await writeProjectMetadata(resolved, {
|
|
140
|
+
...existingMetadata,
|
|
141
|
+
...(metadata || {}),
|
|
142
|
+
updatedAt: new Date().toISOString(),
|
|
143
|
+
createdAt: existingMetadata.createdAt || new Date().toISOString()
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return this.getProject(resolved);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async getProject(project) {
|
|
150
|
+
const resolved = resolveProjectName(project);
|
|
151
|
+
const projectDir = getProjectDir(resolved);
|
|
152
|
+
await ensureProject(resolved);
|
|
153
|
+
const config = await readProjectConfig(resolved);
|
|
154
|
+
const runState = await readProjectRunState(resolved);
|
|
155
|
+
const metadata = await readProjectMetadata(resolved);
|
|
156
|
+
|
|
157
|
+
let story = '';
|
|
158
|
+
try {
|
|
159
|
+
story = await fs.readFile(getProjectStoryPath(resolved), 'utf8');
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (error.code !== 'ENOENT') {
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const files = await listFilesRecursively(projectDir);
|
|
167
|
+
const assets = files.map((filePath) => ({
|
|
168
|
+
referenceType: 'path',
|
|
169
|
+
path: toPosixPath(path.relative(projectDir, filePath)),
|
|
170
|
+
value: toPosixPath(path.relative(process.cwd(), filePath))
|
|
171
|
+
}));
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
project: resolved,
|
|
175
|
+
backend: 'local',
|
|
176
|
+
config,
|
|
177
|
+
metadata,
|
|
178
|
+
story,
|
|
179
|
+
runState,
|
|
180
|
+
assets
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async getRequestLogs(project, options = {}) {
|
|
185
|
+
const resolved = resolveProjectName(project);
|
|
186
|
+
const projectDir = getProjectDir(resolved);
|
|
187
|
+
const logsPath = path.join(projectDir, 'assets', 'debug', 'api-requests.jsonl');
|
|
188
|
+
const reference = {
|
|
189
|
+
referenceType: 'path',
|
|
190
|
+
path: 'assets/debug/api-requests.jsonl',
|
|
191
|
+
value: toPosixPath(path.relative(process.cwd(), logsPath))
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
if (!(await pathExists(logsPath))) {
|
|
195
|
+
return {
|
|
196
|
+
project: resolved,
|
|
197
|
+
backend: 'local',
|
|
198
|
+
logs: reference,
|
|
199
|
+
entries: []
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const raw = await fs.readFile(logsPath, 'utf8');
|
|
204
|
+
let entries = parseJsonLines(raw);
|
|
205
|
+
const limit = Number(options.limit || 0);
|
|
206
|
+
if (Number.isInteger(limit) && limit > 0) {
|
|
207
|
+
entries = entries.slice(-limit);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
project: resolved,
|
|
212
|
+
backend: 'local',
|
|
213
|
+
logs: reference,
|
|
214
|
+
entries
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async getPromptData(project, options = {}) {
|
|
219
|
+
const resolved = resolveProjectName(project);
|
|
220
|
+
const [runState, scriptAsset] = await Promise.all([
|
|
221
|
+
readProjectRunState(resolved),
|
|
222
|
+
readProjectScriptAsset(resolved)
|
|
223
|
+
]);
|
|
224
|
+
const logsResult = await this.getRequestLogs(resolved, options);
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
project: resolved,
|
|
228
|
+
backend: 'local',
|
|
229
|
+
...buildPromptPayload(runState, scriptAsset, logsResult.entries),
|
|
230
|
+
logs: logsResult.logs
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async getArtifacts(project) {
|
|
235
|
+
const resolved = resolveProjectName(project);
|
|
236
|
+
const artifacts = await readProjectArtifacts(resolved);
|
|
237
|
+
return {
|
|
238
|
+
project: resolved,
|
|
239
|
+
backend: 'local',
|
|
240
|
+
artifacts
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async getSyncStatus(project) {
|
|
245
|
+
const resolved = resolveProjectName(project);
|
|
246
|
+
const sync = await readProjectSync(resolved);
|
|
247
|
+
return {
|
|
248
|
+
project: resolved,
|
|
249
|
+
backend: 'local',
|
|
250
|
+
sync
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async getSnapshots(project) {
|
|
255
|
+
const resolved = resolveProjectName(project);
|
|
256
|
+
const projectDir = getProjectDir(resolved);
|
|
257
|
+
const snapshots = await listProjectSnapshots(projectDir);
|
|
258
|
+
const refs = snapshots.map((snapshot) => ({
|
|
259
|
+
referenceType: 'path',
|
|
260
|
+
path: `${snapshot.root}/${snapshot.name}`,
|
|
261
|
+
value: toPosixPath(path.relative(process.cwd(), path.join(projectDir, snapshot.root, snapshot.name)))
|
|
262
|
+
}));
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
project: resolved,
|
|
266
|
+
backend: 'local',
|
|
267
|
+
snapshots: refs
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async updateMetadata(project, patch) {
|
|
272
|
+
const resolved = resolveProjectName(project);
|
|
273
|
+
const current = await readProjectMetadata(resolved);
|
|
274
|
+
const next = {
|
|
275
|
+
...current,
|
|
276
|
+
...(patch || {}),
|
|
277
|
+
updatedAt: new Date().toISOString(),
|
|
278
|
+
createdAt: current.createdAt || new Date().toISOString()
|
|
279
|
+
};
|
|
280
|
+
await writeProjectMetadata(resolved, next);
|
|
281
|
+
return this.getProject(resolved);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async getAnalyticsSummary(project) {
|
|
285
|
+
const resolved = resolveProjectName(project);
|
|
286
|
+
const runs = await listRunRecords(resolved);
|
|
287
|
+
return {
|
|
288
|
+
project: resolved,
|
|
289
|
+
backend: 'local',
|
|
290
|
+
summary: summarizeProjectAnalytics(runs)
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async getAnalyticsRuns(project, options = {}) {
|
|
295
|
+
const resolved = resolveProjectName(project);
|
|
296
|
+
const runs = await listRunRecords(resolved);
|
|
297
|
+
const limit = Number(options.limit || 0);
|
|
298
|
+
const selected = Number.isInteger(limit) && limit > 0 ? runs.slice(0, limit) : runs;
|
|
299
|
+
return {
|
|
300
|
+
project: resolved,
|
|
301
|
+
backend: 'local',
|
|
302
|
+
runs: selected.map((run) => summarizeRun(run))
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async getAnalyticsRun(project, runId) {
|
|
307
|
+
const resolved = resolveProjectName(project);
|
|
308
|
+
const run = await readRunRecord(resolved, runId);
|
|
309
|
+
if (!run) {
|
|
310
|
+
throw new Error('Run not found');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
project: resolved,
|
|
315
|
+
backend: 'local',
|
|
316
|
+
run
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export class FirebaseProjectMetadataBackend {
|
|
322
|
+
constructor(options = {}) {
|
|
323
|
+
this.db = null;
|
|
324
|
+
this.getFirebaseClientsFn = options.getFirebaseClientsFn || getFirebaseClients;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async ensureInitialized() {
|
|
328
|
+
if (this.db) return;
|
|
329
|
+
const { db } = await this.getFirebaseClientsFn();
|
|
330
|
+
this.db = db;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async listProjects() {
|
|
334
|
+
await this.ensureInitialized();
|
|
335
|
+
const snap = await this.db.collection('projects').get();
|
|
336
|
+
return snap.docs.map((doc) => doc.id).sort((a, b) => a.localeCompare(b));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async createProject({ project, story, config, metadata }) {
|
|
340
|
+
assertCreateProjectPayload({ story, config, metadata });
|
|
341
|
+
await this.ensureInitialized();
|
|
342
|
+
const resolved = resolveProjectName(project);
|
|
343
|
+
|
|
344
|
+
await ensureProject(resolved);
|
|
345
|
+
const existingConfig = await ensureProjectConfig(resolved);
|
|
346
|
+
if (story !== undefined) {
|
|
347
|
+
await writeProjectStory(resolved, story);
|
|
348
|
+
}
|
|
349
|
+
if (metadata && typeof metadata === 'object') {
|
|
350
|
+
await writeProjectMetadata(resolved, metadata);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const projectRef = this.db.collection('projects').doc(resolved);
|
|
354
|
+
await projectRef.set(
|
|
355
|
+
{
|
|
356
|
+
project: resolved,
|
|
357
|
+
backend: 'firebase',
|
|
358
|
+
createdAt: new Date().toISOString(),
|
|
359
|
+
updatedAt: new Date().toISOString()
|
|
360
|
+
},
|
|
361
|
+
{ merge: true }
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
if (story !== undefined) {
|
|
365
|
+
await projectRef.collection('documents').doc('story').set({ markdown: story }, { merge: true });
|
|
366
|
+
}
|
|
367
|
+
if (config && typeof config === 'object') {
|
|
368
|
+
const mergedConfig = normalizeAndValidateProjectConfig({
|
|
369
|
+
...existingConfig,
|
|
370
|
+
...config
|
|
371
|
+
});
|
|
372
|
+
await writeProjectConfig(resolved, mergedConfig);
|
|
373
|
+
await projectRef.collection('documents').doc('config').set(mergedConfig, { merge: false });
|
|
374
|
+
}
|
|
375
|
+
if (metadata && typeof metadata === 'object') {
|
|
376
|
+
await projectRef.collection('documents').doc('metadata').set(metadata, { merge: true });
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return this.getProject(resolved);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async getProject(project) {
|
|
383
|
+
await this.ensureInitialized();
|
|
384
|
+
const resolved = resolveProjectName(project);
|
|
385
|
+
const projectRef = this.db.collection('projects').doc(resolved);
|
|
386
|
+
|
|
387
|
+
const [projectDoc, configDoc, runStateDoc, storyDoc, metadataDoc, manifestDoc] = await Promise.all([
|
|
388
|
+
projectRef.get(),
|
|
389
|
+
projectRef.collection('documents').doc('config').get(),
|
|
390
|
+
projectRef.collection('documents').doc('run-state').get(),
|
|
391
|
+
projectRef.collection('documents').doc('story').get(),
|
|
392
|
+
projectRef.collection('documents').doc('metadata').get(),
|
|
393
|
+
projectRef.collection('documents').doc('assets-manifest').get()
|
|
394
|
+
]);
|
|
395
|
+
|
|
396
|
+
if (!projectDoc.exists && !configDoc.exists && !runStateDoc.exists && !storyDoc.exists) {
|
|
397
|
+
throw new Error('Project not found in Firebase backend');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const refs = manifestDoc.exists ? manifestDoc.data().refs || [] : [];
|
|
401
|
+
const assets = refs.map((ref) => ({
|
|
402
|
+
referenceType: 'url',
|
|
403
|
+
path: ref.relativePath,
|
|
404
|
+
value: ref.url
|
|
405
|
+
}));
|
|
406
|
+
|
|
407
|
+
const resolvedConfig = normalizeAndValidateProjectConfig(configDoc.exists ? configDoc.data() : {});
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
project: resolved,
|
|
411
|
+
backend: 'firebase',
|
|
412
|
+
config: resolvedConfig,
|
|
413
|
+
metadata: metadataDoc.exists ? metadataDoc.data() : {},
|
|
414
|
+
story: storyDoc.exists ? storyDoc.data().markdown || '' : '',
|
|
415
|
+
runState: runStateDoc.exists ? runStateDoc.data() : null,
|
|
416
|
+
assets
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async getRequestLogs(project, options = {}) {
|
|
421
|
+
await this.ensureInitialized();
|
|
422
|
+
const details = await this.getProject(project);
|
|
423
|
+
const logAsset = details.assets.find((asset) => asset.path === 'assets/debug/api-requests.jsonl') || null;
|
|
424
|
+
|
|
425
|
+
if (options.includeEntries === true) {
|
|
426
|
+
throw new Error('includeEntries is not supported for firebase backend logs; use the logs URL');
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
project: details.project,
|
|
431
|
+
backend: 'firebase',
|
|
432
|
+
logs: logAsset,
|
|
433
|
+
entries: null
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async getPromptData(project, options = {}) {
|
|
438
|
+
const details = await this.getProject(project);
|
|
439
|
+
const scriptAsset = await readProjectScriptAsset(project);
|
|
440
|
+
const logs = await this.getRequestLogs(project, options);
|
|
441
|
+
return {
|
|
442
|
+
project: details.project,
|
|
443
|
+
backend: 'firebase',
|
|
444
|
+
script: scriptAsset?.script || details.runState?.artifacts?.script || '',
|
|
445
|
+
shots: scriptAsset?.shots || details.runState?.artifacts?.shots || [],
|
|
446
|
+
prompts: [],
|
|
447
|
+
logs: logs.logs
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async getArtifacts(project) {
|
|
452
|
+
await this.ensureInitialized();
|
|
453
|
+
const details = await this.getProject(project);
|
|
454
|
+
return {
|
|
455
|
+
project: details.project,
|
|
456
|
+
backend: 'firebase',
|
|
457
|
+
artifacts: details.runState?.artifacts || null
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async getSyncStatus(project) {
|
|
462
|
+
await this.ensureInitialized();
|
|
463
|
+
const resolved = resolveProjectName(project);
|
|
464
|
+
const docRef = this.db.collection('projects').doc(resolved).collection('documents').doc('sync');
|
|
465
|
+
const doc = await docRef.get();
|
|
466
|
+
return {
|
|
467
|
+
project: resolved,
|
|
468
|
+
backend: 'firebase',
|
|
469
|
+
sync: doc.exists ? doc.data() : null
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async getSnapshots(project) {
|
|
474
|
+
const details = await this.getProject(project);
|
|
475
|
+
const snapshotRefs = details.assets
|
|
476
|
+
.filter((asset) => asset.path.startsWith('snapshots/') || asset.path.startsWith('stale/'))
|
|
477
|
+
.map((asset) => ({
|
|
478
|
+
referenceType: 'url',
|
|
479
|
+
path: asset.path,
|
|
480
|
+
value: asset.value
|
|
481
|
+
}));
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
project: details.project,
|
|
485
|
+
backend: 'firebase',
|
|
486
|
+
snapshots: snapshotRefs
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async updateMetadata(project, patch) {
|
|
491
|
+
await this.ensureInitialized();
|
|
492
|
+
const resolved = resolveProjectName(project);
|
|
493
|
+
const docRef = this.db.collection('projects').doc(resolved).collection('documents').doc('metadata');
|
|
494
|
+
const current = (await docRef.get()).data() || {};
|
|
495
|
+
const next = {
|
|
496
|
+
...current,
|
|
497
|
+
...(patch || {}),
|
|
498
|
+
updatedAt: new Date().toISOString(),
|
|
499
|
+
createdAt: current.createdAt || new Date().toISOString()
|
|
500
|
+
};
|
|
501
|
+
await docRef.set(next, { merge: true });
|
|
502
|
+
return this.getProject(resolved);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async getAnalyticsSummary(project) {
|
|
506
|
+
const resolved = resolveProjectName(project);
|
|
507
|
+
const runs = await listRunRecords(resolved);
|
|
508
|
+
return {
|
|
509
|
+
project: resolved,
|
|
510
|
+
backend: 'firebase',
|
|
511
|
+
summary: summarizeProjectAnalytics(runs)
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async getAnalyticsRuns(project, options = {}) {
|
|
516
|
+
const resolved = resolveProjectName(project);
|
|
517
|
+
const runs = await listRunRecords(resolved);
|
|
518
|
+
const limit = Number(options.limit || 0);
|
|
519
|
+
const selected = Number.isInteger(limit) && limit > 0 ? runs.slice(0, limit) : runs;
|
|
520
|
+
return {
|
|
521
|
+
project: resolved,
|
|
522
|
+
backend: 'firebase',
|
|
523
|
+
runs: selected.map((run) => summarizeRun(run))
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async getAnalyticsRun(project, runId) {
|
|
528
|
+
const resolved = resolveProjectName(project);
|
|
529
|
+
const run = await readRunRecord(resolved, runId);
|
|
530
|
+
if (!run) {
|
|
531
|
+
throw new Error('Run not found');
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
project: resolved,
|
|
536
|
+
backend: 'firebase',
|
|
537
|
+
run
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export function getProjectMetadataBackend(options = {}) {
|
|
543
|
+
const backendType = options.backendType || env.outputBackend;
|
|
544
|
+
if (backendType === 'firebase') {
|
|
545
|
+
return new FirebaseProjectMetadataBackend({
|
|
546
|
+
getFirebaseClientsFn: options.getFirebaseClientsFn
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
return new LocalProjectMetadataBackend();
|
|
550
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
export function getRiloHomeDir() {
|
|
7
|
+
return path.join(os.homedir(), '.rilo');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function buildOpenCommand(targetPath, platform = process.platform) {
|
|
11
|
+
if (platform === 'darwin') {
|
|
12
|
+
return { command: 'open', args: [targetPath] };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (platform === 'linux') {
|
|
16
|
+
return { command: 'xdg-open', args: [targetPath] };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (platform === 'win32') {
|
|
20
|
+
return { command: 'cmd', args: ['/c', 'start', '', targetPath] };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
throw new Error(`Unsupported platform for 'rilo home': ${platform}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function openPath(targetPath, options = {}) {
|
|
27
|
+
const platform = options.platform || process.platform;
|
|
28
|
+
const spawnImpl = options.spawnImpl || spawn;
|
|
29
|
+
const { command, args } = buildOpenCommand(targetPath, platform);
|
|
30
|
+
|
|
31
|
+
await new Promise((resolve, reject) => {
|
|
32
|
+
let child;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
child = spawnImpl(command, args, {
|
|
36
|
+
stdio: 'ignore'
|
|
37
|
+
});
|
|
38
|
+
} catch (error) {
|
|
39
|
+
reject(new Error(`Unable to open ${targetPath}: ${error.message}`));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
child.once('error', (error) => {
|
|
44
|
+
if (error && error.code === 'ENOENT') {
|
|
45
|
+
reject(new Error(`Unable to open ${targetPath}: '${command}' is not available on this system.`));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
reject(new Error(`Unable to open ${targetPath}: ${error.message}`));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
child.once('exit', (code) => {
|
|
53
|
+
if (code === 0) {
|
|
54
|
+
resolve();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
reject(new Error(`Unable to open ${targetPath}: '${command}' exited with code ${code}.`));
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function openHome(options = {}) {
|
|
64
|
+
const targetPath = options.targetPath || getRiloHomeDir();
|
|
65
|
+
|
|
66
|
+
await fs.mkdir(targetPath, { recursive: true });
|
|
67
|
+
await openPath(targetPath, options);
|
|
68
|
+
|
|
69
|
+
console.log(`Opened ${targetPath}`);
|
|
70
|
+
}
|