@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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +209 -0
  3. package/index.js +1 -0
  4. package/models/black-forest-labs__flux-2-pro.json +78 -0
  5. package/models/black-forest-labs__flux-schnell.json +95 -0
  6. package/models/bytedance__seedream-4.json +71 -0
  7. package/models/deepseek-ai__deepseek-v3.json +61 -0
  8. package/models/google__nano-banana-pro.json +92 -0
  9. package/models/google__veo-3.1-fast.json +93 -0
  10. package/models/google__veo-3.1.json +93 -0
  11. package/models/jaaari__kokoro-82m.json +86 -0
  12. package/models/kwaivgi__kling-v3-video.json +101 -0
  13. package/models/minimax__speech-02-turbo.json +141 -0
  14. package/models/pixverse__pixverse-v5.6.json +113 -0
  15. package/models/prunaai__z-image-turbo.json +107 -0
  16. package/models/resemble-ai__chatterbox-turbo.json +102 -0
  17. package/models/wan-video__wan-2.2-i2v-fast.json +139 -0
  18. package/package.json +67 -0
  19. package/src/api/firebaseFunction.js +46 -0
  20. package/src/api/middleware/auth.js +70 -0
  21. package/src/api/openapi/generateOpenApi.js +21 -0
  22. package/src/api/openapi/spec.js +831 -0
  23. package/src/api/routes/jobs.js +45 -0
  24. package/src/api/routes/projectAssets.js +63 -0
  25. package/src/api/routes/projects.js +647 -0
  26. package/src/api/routes/webhooks.js +13 -0
  27. package/src/api/server.js +88 -0
  28. package/src/backends/firebaseClient.js +57 -0
  29. package/src/backends/outputBackend.js +186 -0
  30. package/src/backends/projectMetadataBackend.js +550 -0
  31. package/src/cli/commands/openHome.js +70 -0
  32. package/src/cli/commands/settingsFlow.js +196 -0
  33. package/src/cli/index.js +192 -0
  34. package/src/config/env.js +158 -0
  35. package/src/config/keystore.js +175 -0
  36. package/src/config/models.js +281 -0
  37. package/src/config/settingsSchema.js +214 -0
  38. package/src/media/ffmpeg.js +144 -0
  39. package/src/media/files.js +77 -0
  40. package/src/media/subtitles.js +444 -0
  41. package/src/observability/apiTrace.js +17 -0
  42. package/src/observability/logger.js +7 -0
  43. package/src/observability/metrics.js +10 -0
  44. package/src/pipeline/inputSanitizer.js +6 -0
  45. package/src/pipeline/orchestrator.js +1669 -0
  46. package/src/policy/contentGuardrails.js +30 -0
  47. package/src/providers/predictions.js +188 -0
  48. package/src/providers/replicateClient.js +12 -0
  49. package/src/steps/alignSubtitles.js +156 -0
  50. package/src/steps/burnInSubtitles.js +22 -0
  51. package/src/steps/composeFinalVideo.js +57 -0
  52. package/src/steps/generateKeyframes.js +70 -0
  53. package/src/steps/generateVideoSegments.js +95 -0
  54. package/src/steps/generateVoiceover.js +128 -0
  55. package/src/steps/imageToVideoAdapters.js +100 -0
  56. package/src/steps/script.js +177 -0
  57. package/src/steps/textToImageAdapters.js +87 -0
  58. package/src/store/assetStore.js +5 -0
  59. package/src/store/jobStore.js +102 -0
  60. package/src/store/projectAnalyticsStore.js +625 -0
  61. package/src/store/projectStore.js +684 -0
  62. package/src/store/settingsStore.js +155 -0
  63. package/src/store/staleAssetStore.js +63 -0
  64. package/src/types/job.js +28 -0
  65. package/src/types/media.js +28 -0
  66. package/src/worker/processor.js +24 -0
@@ -0,0 +1,155 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { PUBLIC_SETTINGS, SECURE_SETTINGS } from '../config/settingsSchema.js';
5
+ import { getSecret, setSecret } from '../config/keystore.js';
6
+
7
+ const CONFIG_FILE = 'config.json';
8
+
9
+ function getRiloDir() {
10
+ return path.join(os.homedir(), '.rilo');
11
+ }
12
+
13
+ export function getConfigFilePath() {
14
+ return path.join(getRiloDir(), CONFIG_FILE);
15
+ }
16
+
17
+ /**
18
+ * Read the public config file (~/.rilo/config.json).
19
+ * Returns an empty object if the file does not exist.
20
+ * @returns {Promise<Record<string, unknown>>}
21
+ */
22
+ export async function readPublicConfig() {
23
+ const filePath = getConfigFilePath();
24
+ try {
25
+ const raw = await fs.readFile(filePath, 'utf8');
26
+ const parsed = JSON.parse(raw);
27
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
28
+ return parsed;
29
+ }
30
+ } catch (err) {
31
+ if (err.code !== 'ENOENT') {
32
+ // File exists but is malformed; surface the error
33
+ throw err;
34
+ }
35
+ }
36
+ return {};
37
+ }
38
+
39
+ /**
40
+ * Write the public config file (~/.rilo/config.json).
41
+ * @param {Record<string, unknown>} data
42
+ */
43
+ async function writePublicConfig(data) {
44
+ const filePath = getConfigFilePath();
45
+ const dir = getRiloDir();
46
+ await fs.mkdir(dir, { recursive: true });
47
+ await fs.writeFile(filePath, JSON.stringify(data, null, 2), { encoding: 'utf8' });
48
+ }
49
+
50
+ /**
51
+ * Resolve the current effective value for a public setting.
52
+ * Precedence: env var > config.json > schema default.
53
+ *
54
+ * @param {import('../config/settingsSchema.js').SETTINGS[0]} setting
55
+ * @param {Record<string, unknown>} storedConfig
56
+ * @returns {{ value: unknown, source: 'env'|'config'|'default' }}
57
+ */
58
+ export function resolvePublicValue(setting, storedConfig) {
59
+ // Check env vars in priority order
60
+ for (const envName of setting.envNames) {
61
+ const raw = process.env[envName];
62
+ if (raw !== undefined && raw !== '') {
63
+ return { value: raw, source: 'env' };
64
+ }
65
+ }
66
+ // Check stored config
67
+ if (Object.prototype.hasOwnProperty.call(storedConfig, setting.configKey)) {
68
+ return { value: storedConfig[setting.configKey], source: 'config' };
69
+ }
70
+ return { value: setting.default, source: 'default' };
71
+ }
72
+
73
+ /**
74
+ * Resolve the current effective status for a secure setting.
75
+ * Returns whether a value is present and where it came from.
76
+ *
77
+ * @param {import('../config/settingsSchema.js').SETTINGS[0]} setting
78
+ * @returns {Promise<{ hasValue: boolean, source: 'env'|'keystore'|'none' }>}
79
+ */
80
+ export async function resolveSecureStatus(setting) {
81
+ for (const envName of setting.envNames) {
82
+ const raw = process.env[envName];
83
+ if (raw !== undefined && raw !== '') {
84
+ return { hasValue: true, source: 'env' };
85
+ }
86
+ }
87
+ const stored = await getSecret(setting.keystoreKey);
88
+ if (stored) return { hasValue: true, source: 'keystore' };
89
+ return { hasValue: false, source: 'none' };
90
+ }
91
+
92
+ /**
93
+ * Save a single public setting to ~/.rilo/config.json.
94
+ * Merges with existing contents.
95
+ *
96
+ * @param {string} configKey - camelCase key from the schema
97
+ * @param {unknown} value
98
+ */
99
+ export async function savePublicSetting(configKey, value) {
100
+ const current = await readPublicConfig();
101
+ current[configKey] = value;
102
+ await writePublicConfig(current);
103
+ }
104
+
105
+ /**
106
+ * Save a secure token to the OS keystore (or encrypted fallback).
107
+ *
108
+ * @param {string} keystoreKey
109
+ * @param {string} value
110
+ */
111
+ export async function saveSecureToken(keystoreKey, value) {
112
+ await setSecret(keystoreKey, value);
113
+ }
114
+
115
+ /**
116
+ * Load a secure token from the keystore.
117
+ * Respects env-var priority just like the rest of the system.
118
+ *
119
+ * @param {import('../config/settingsSchema.js').SETTINGS[0]} setting
120
+ * @returns {Promise<string|null>}
121
+ */
122
+ export async function loadSecureToken(setting) {
123
+ for (const envName of setting.envNames) {
124
+ const raw = process.env[envName];
125
+ if (raw !== undefined && raw !== '') return raw;
126
+ }
127
+ return getSecret(setting.keystoreKey);
128
+ }
129
+
130
+ /**
131
+ * Build a full snapshot of current settings (for display or env-merging).
132
+ * Secure tokens are returned as their actual value (caller must handle masking).
133
+ *
134
+ * @returns {Promise<{ public: Record<string, { value: unknown, source: string }>, secure: Record<string, { value: string|null, source: string }> }>}
135
+ */
136
+ export async function loadAllSettings() {
137
+ const storedConfig = await readPublicConfig();
138
+ const publicResult = {};
139
+ for (const s of PUBLIC_SETTINGS) {
140
+ publicResult[s.id] = resolvePublicValue(s, storedConfig);
141
+ }
142
+
143
+ const secureResult = {};
144
+ for (const s of SECURE_SETTINGS) {
145
+ const status = await resolveSecureStatus(s);
146
+ const value = status.hasValue
147
+ ? (status.source === 'env'
148
+ ? (process.env[s.envNames[0]] || process.env[s.envNames[1]] || '')
149
+ : await getSecret(s.keystoreKey))
150
+ : null;
151
+ secureResult[s.id] = { value, source: status.source };
152
+ }
153
+
154
+ return { public: publicResult, secure: secureResult };
155
+ }
@@ -0,0 +1,63 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import { ensureDir } from '../media/files.js';
5
+
6
+ async function pathExists(targetPath) {
7
+ try {
8
+ await fs.access(targetPath);
9
+ return true;
10
+ } catch {
11
+ return false;
12
+ }
13
+ }
14
+
15
+ function createSnapshotId() {
16
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
17
+ const suffix = crypto.randomBytes(3).toString('hex');
18
+ return `${stamp}-${suffix}`;
19
+ }
20
+
21
+ async function listDirectories(rootPath) {
22
+ if (!(await pathExists(rootPath))) {
23
+ return [];
24
+ }
25
+ const entries = await fs.readdir(rootPath, { withFileTypes: true });
26
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
27
+ }
28
+
29
+ export async function listProjectSnapshots(projectDir) {
30
+ const snapshotsRoot = path.join(projectDir, 'snapshots');
31
+ const legacyStaleRoot = path.join(projectDir, 'stale');
32
+
33
+ const snapshots = await listDirectories(snapshotsRoot);
34
+ const legacy = await listDirectories(legacyStaleRoot);
35
+
36
+ return [...snapshots.map((name) => ({ root: 'snapshots', name })), ...legacy.map((name) => ({ root: 'stale', name }))]
37
+ .sort((a, b) => b.name.localeCompare(a.name));
38
+ }
39
+
40
+ export async function archiveProjectAssets(projectDir) {
41
+ const assetsPath = path.join(projectDir, 'assets');
42
+ const finalVideoPath = path.join(projectDir, 'final.mp4');
43
+
44
+ const hasAssets = await pathExists(assetsPath);
45
+ const hasFinalVideo = await pathExists(finalVideoPath);
46
+ if (!hasAssets && !hasFinalVideo) {
47
+ return null;
48
+ }
49
+
50
+ const snapshotsRoot = path.join(projectDir, 'snapshots');
51
+ const snapshotDir = path.join(snapshotsRoot, createSnapshotId());
52
+ await ensureDir(snapshotDir);
53
+
54
+ if (hasAssets) {
55
+ await fs.rename(assetsPath, path.join(snapshotDir, 'assets'));
56
+ }
57
+
58
+ if (hasFinalVideo) {
59
+ await fs.rename(finalVideoPath, path.join(snapshotDir, 'final.mp4'));
60
+ }
61
+
62
+ return snapshotDir;
63
+ }
@@ -0,0 +1,28 @@
1
+ export const JobStatus = {
2
+ PENDING: 'pending',
3
+ RUNNING: 'running',
4
+ FAILED: 'failed',
5
+ COMPLETED: 'completed'
6
+ };
7
+
8
+ export const JobStep = {
9
+ SCRIPT: 'script',
10
+ VOICE: 'voiceover',
11
+ KEYFRAMES: 'keyframes',
12
+ SEGMENTS: 'segments',
13
+ COMPOSE: 'compose',
14
+ ALIGN: 'align',
15
+ BURNIN: 'burnin'
16
+ };
17
+
18
+ export function emptyStepState() {
19
+ return {
20
+ [JobStep.SCRIPT]: false,
21
+ [JobStep.VOICE]: false,
22
+ [JobStep.KEYFRAMES]: false,
23
+ [JobStep.SEGMENTS]: false,
24
+ [JobStep.COMPOSE]: false,
25
+ [JobStep.ALIGN]: false,
26
+ [JobStep.BURNIN]: false
27
+ };
28
+ }
@@ -0,0 +1,28 @@
1
+ export function emptyPipelineArtifacts() {
2
+ return {
3
+ renderSpecVersion: 2,
4
+ aspectRatio: '9:16',
5
+ keyframeSizeKey: '',
6
+ storyHash: '',
7
+ scriptSourceStoryHash: '',
8
+ scriptHash: '',
9
+ shotHashes: [],
10
+ script: '',
11
+ tone: '',
12
+ shots: [],
13
+ timeline: [],
14
+ voiceoverUrl: '',
15
+ voiceoverPath: '',
16
+ keyframeUrls: [],
17
+ keyframePaths: [],
18
+ segmentUrls: [],
19
+ segmentPaths: [],
20
+ subtitleSeedPath: '',
21
+ subtitleAlignedSrtPath: '',
22
+ subtitleAssPath: '',
23
+ finalBaseVideoPath: '',
24
+ finalCaptionedVideoPath: '',
25
+ finalVideoPath: '',
26
+ subtitlesUrl: ''
27
+ };
28
+ }
@@ -0,0 +1,24 @@
1
+ import { listPendingJobs } from '../store/jobStore.js';
2
+ import { runPipeline } from '../pipeline/orchestrator.js';
3
+ import { logInfo } from '../observability/logger.js';
4
+
5
+ async function tick() {
6
+ const pending = listPendingJobs();
7
+ if (pending.length === 0) return;
8
+
9
+ const [job] = pending;
10
+ logInfo('worker_picking_job', { jobId: job.id });
11
+ await runPipeline(job.id);
12
+ }
13
+
14
+ async function loop() {
15
+ for (;;) {
16
+ await tick();
17
+ await new Promise((resolve) => setTimeout(resolve, 2000));
18
+ }
19
+ }
20
+
21
+ loop().catch((error) => {
22
+ console.error(error.message);
23
+ process.exit(1);
24
+ });