@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,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
+ }