@vorec/cli 0.10.0 → 1.0.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 CHANGED
@@ -49,9 +49,8 @@ Create a `vorec.json` file describing the actions to record:
49
49
  | Command | What it does |
50
50
  |---------|-------------|
51
51
  | `vorec init` | Save your API key |
52
- | `vorec run <manifest>` | Record and upload |
53
- | `vorec run <manifest> --auto` | Record, upload, and generate narration |
54
- | `vorec run <manifest> --auto --skip-record` | Resume an existing project |
52
+ | `vorec run <manifest>` | Record, upload, and generate narrated tutorial |
53
+ | `vorec run <manifest> --skip-record --video <file>` | Use existing video instead of recording |
55
54
  | `vorec upload <video>` | Upload a video file directly |
56
55
  | `vorec status` | Check project status |
57
56
 
@@ -1,5 +1,4 @@
1
1
  interface RunOptions {
2
- auto: boolean;
3
2
  skipRecord: boolean;
4
3
  video: string;
5
4
  trackedActions: string;
@@ -3,27 +3,12 @@ import ora from 'ora';
3
3
  import chalk from 'chalk';
4
4
  import { loadManifest } from '../manifest.js';
5
5
  import { apiCall, uploadVideo } from '../api.js';
6
- import { saveProjectState, loadProjectState } from '../config.js';
6
+ import { saveProjectState } from '../config.js';
7
7
  export async function runCommand(manifestPath, opts) {
8
8
  const manifest = loadManifest(manifestPath);
9
9
  console.log(chalk.bold(`Vorec: ${manifest.title}`));
10
10
  console.log(`Actions: ${manifest.actions.length}, URL: ${manifest.url}\n`);
11
- // Check if we're resuming an existing project (--skip-record with saved state)
12
- const savedState = loadProjectState();
13
- const resuming = opts.skipRecord && savedState?.projectId && savedState?.status === 'analyzing';
14
- if (resuming) {
15
- // Resume: skip recording + upload, go straight to analysis
16
- console.log(`Resuming project: ${savedState.projectId}`);
17
- if (opts.auto) {
18
- await runAnalysis(savedState.projectId, manifest);
19
- }
20
- else {
21
- const status = await apiCall('get-status', { projectId: savedState.projectId });
22
- console.log(`\n${chalk.bold('Editor:')} ${status.editor_url}`);
23
- }
24
- return;
25
- }
26
- // Fresh run: record → upload → (optionally analyze)
11
+ // Record upload generate
27
12
  let videoPath = opts.video;
28
13
  let trackedActions = [];
29
14
  let videoDuration = 0;
@@ -64,6 +49,15 @@ export async function runCommand(manifestPath, opts) {
64
49
  const ext = videoPath.split('.').pop()?.toLowerCase() || 'mp4';
65
50
  const mimeMap = { mp4: 'video/mp4', webm: 'video/webm', mov: 'video/quicktime', mkv: 'video/x-matroska' };
66
51
  const contentType = mimeMap[ext] || 'video/mp4';
52
+ // Get real video duration from file (ffprobe)
53
+ if (!videoDuration) {
54
+ try {
55
+ const { execSync } = await import('node:child_process');
56
+ const probe = execSync(`ffprobe -v error -show_entries format=duration -of csv=p=0 "${videoPath}"`, { encoding: 'utf-8' });
57
+ videoDuration = parseFloat(probe.trim()) || 0;
58
+ }
59
+ catch { /* ffprobe not available or failed — duration stays 0 */ }
60
+ }
67
61
  // Step 2: Create project + upload
68
62
  const spinner = ora('Creating project...').start();
69
63
  const createResult = await apiCall('create-project', {
@@ -87,7 +81,7 @@ export async function runCommand(manifestPath, opts) {
87
81
  await apiCall('confirm-upload', {
88
82
  projectId: createResult.projectId,
89
83
  storage_key: createResult.storage_key,
90
- video_duration: videoDuration || undefined,
84
+ video_duration: videoDuration > 0 ? videoDuration : undefined,
91
85
  video_width: videoWidth,
92
86
  video_height: videoHeight,
93
87
  });
@@ -98,57 +92,50 @@ export async function runCommand(manifestPath, opts) {
98
92
  status: 'analyzing',
99
93
  });
100
94
  spinner.succeed(`Project created: ${createResult.projectId}`);
101
- if (!opts.auto) {
102
- const status = await apiCall('get-status', { projectId: createResult.projectId });
103
- console.log(`\nVideo uploaded. Run with --auto to continue processing.`);
104
- console.log(`${chalk.bold('Editor:')} ${status.editor_url}`);
105
- return;
106
- }
107
- // Auto mode: continue with analysis
108
- await runAnalysis(createResult.projectId, manifest);
109
- }
110
- async function runAnalysis(projectId, manifest) {
111
- // Analyze video
112
- const descSpinner = ora('Analyzing video...').start();
95
+ // Trigger analysis (runs in background on server)
96
+ const analysisSpinner = ora('Generating narrated tutorial...').start();
113
97
  try {
114
- const descResult = await apiCall('describe-video', {
115
- projectId,
116
- videoContext: manifest.videoContext,
117
- });
118
- descSpinner.succeed(`Video analyzed (${descResult.description?.length || 0} chars)`);
119
- }
120
- catch (err) {
121
- descSpinner.fail(`Analysis failed: ${err.message}`);
122
- process.exit(1);
123
- }
124
- // Generate narration
125
- const narrSpinner = ora('Writing narration...').start();
126
- try {
127
- const narrResult = await apiCall('generate-narration', {
128
- projectId,
98
+ await apiCall('generate-video', {
99
+ projectId: createResult.projectId,
129
100
  language: manifest.language,
130
101
  narrationStyle: manifest.narrationStyle,
102
+ videoContext: manifest.videoContext,
131
103
  customPrompt: manifest.customPrompt,
132
104
  includeIntro: false,
133
105
  });
134
- narrSpinner.succeed(`${narrResult.segments_created} narration segments`);
135
- if (narrResult.segments) {
136
- for (const seg of narrResult.segments) {
137
- console.log(chalk.dim(` [${seg.sort_order}] ${seg.timestamp_seconds.toFixed(1)}s — ${seg.action_name}`));
138
- }
139
- }
140
106
  }
141
107
  catch (err) {
142
- narrSpinner.fail(`Narration failed: ${err.message}`);
108
+ analysisSpinner.fail(`Failed to start analysis: ${err.message}`);
143
109
  process.exit(1);
144
110
  }
145
- // Done
146
- const finalStatus = await apiCall('get-status', { projectId });
147
- console.log(`\n${chalk.bold.green('Done!')} ${finalStatus.segments?.length || 0} segments ready`);
111
+ // Poll until done
112
+ let attempts = 0;
113
+ while (attempts < 120) {
114
+ await new Promise(r => setTimeout(r, 5000));
115
+ const status = await apiCall('get-status', { projectId: createResult.projectId });
116
+ const segCount = status.segments?.length || 0;
117
+ if (status.status === 'editing' || status.status === 'complete') {
118
+ analysisSpinner.succeed(`Done! ${segCount} narration segments`);
119
+ if (status.segments) {
120
+ for (const seg of status.segments) {
121
+ console.log(chalk.dim(` [${seg.sort_order}] ${Number(seg.timestamp_seconds).toFixed(1)}s — ${seg.action_name}`));
122
+ }
123
+ }
124
+ console.log(`\n${chalk.bold('Editor:')} ${status.editor_url}`);
125
+ console.log(chalk.dim('Open the editor to preview narration and generate audio.'));
126
+ saveProjectState({ projectId: createResult.projectId, status: 'complete' });
127
+ return;
128
+ }
129
+ if (status.status === 'draft' || status.status === 'error') {
130
+ analysisSpinner.fail(`Analysis ended with status: ${status.status}`);
131
+ process.exit(1);
132
+ }
133
+ analysisSpinner.text = `Generating narrated tutorial... (${status.status})`;
134
+ attempts++;
135
+ }
136
+ analysisSpinner.fail('Timed out waiting for analysis');
137
+ const finalStatus = await apiCall('get-status', { projectId: createResult.projectId });
148
138
  console.log(`${chalk.bold('Editor:')} ${finalStatus.editor_url}`);
149
- console.log(chalk.dim('Open the editor to preview narration and generate audio.'));
150
- // Clear state so next run creates a fresh project
151
- saveProjectState({ projectId, status: 'complete' });
152
139
  }
153
140
  async function recordAndTrack(manifest) {
154
141
  let pw;
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import { statusCommand } from './commands/status.js';
7
7
  const program = new Command()
8
8
  .name('vorec')
9
9
  .description('Turn screen recordings into narrated tutorials')
10
- .version('0.10.0');
10
+ .version('1.0.0');
11
11
  program
12
12
  .command('init')
13
13
  .description('Save your Vorec API key')
@@ -27,9 +27,8 @@ program
27
27
  .action(uploadCommand);
28
28
  program
29
29
  .command('run <manifest>')
30
- .description('Full pipeline: record → upload narrate audio')
31
- .option('--auto', 'Wait for processing and generate audio', false)
32
- .option('--skip-record', 'Skip Playwright recording (use --video)', false)
30
+ .description('Record screen, upload, and generate narrated tutorial')
31
+ .option('--skip-record', 'Skip recording, use --video instead', false)
33
32
  .option('--video <path>', 'Use existing video file instead of recording')
34
33
  .option('--tracked-actions <path>', 'JSON file with tracked actions (for --skip-record)')
35
34
  .action(runCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vorec/cli",
3
- "version": "0.10.0",
3
+ "version": "1.0.0",
4
4
  "description": "Turn screen recordings into narrated tutorials — CLI for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {