@scenerok/cli 1.0.5 → 1.0.7

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 (61) hide show
  1. package/README.md +6 -3
  2. package/dist/commands/auth.d.ts.map +1 -1
  3. package/dist/commands/auth.js +17 -1
  4. package/dist/commands/project.d.ts.map +1 -1
  5. package/dist/commands/project.js +16 -2
  6. package/dist/commands/render.d.ts.map +1 -1
  7. package/dist/commands/render.js +14 -0
  8. package/dist/commands/validate.d.ts.map +1 -1
  9. package/dist/commands/validate.js +25 -6
  10. package/dist/index.js +3 -1
  11. package/dist/lib/api.d.ts +43 -0
  12. package/dist/lib/api.d.ts.map +1 -1
  13. package/dist/lib/api.js +41 -4
  14. package/dist/lib/format-api-error.d.ts +2 -0
  15. package/dist/lib/format-api-error.d.ts.map +1 -0
  16. package/dist/lib/format-api-error.js +24 -0
  17. package/dist/lib/logger.d.ts +8 -0
  18. package/dist/lib/logger.d.ts.map +1 -0
  19. package/dist/lib/logger.js +51 -0
  20. package/examples/system/rokmilk-chocolate-promo.vid +5 -4
  21. package/package.json +1 -1
  22. package/skills/aider/SKILL.md +13 -18
  23. package/skills/aider/vidscript-guide.md +42 -39
  24. package/skills/aider/vidscript-sample.md +4 -3
  25. package/skills/aider/vidscript-strict.md +26 -8
  26. package/skills/claude/SKILL.md +13 -18
  27. package/skills/claude/vidscript-guide.md +42 -39
  28. package/skills/claude/vidscript-sample.md +4 -3
  29. package/skills/claude/vidscript-strict.md +26 -8
  30. package/skills/codex/SKILL.md +13 -18
  31. package/skills/codex/vidscript-guide.md +42 -39
  32. package/skills/codex/vidscript-sample.md +4 -3
  33. package/skills/codex/vidscript-strict.md +26 -8
  34. package/skills/cursor/SKILL.md +13 -18
  35. package/skills/cursor/vidscript-guide.md +42 -39
  36. package/skills/cursor/vidscript-sample.md +4 -3
  37. package/skills/cursor/vidscript-strict.md +26 -8
  38. package/skills/opencode/SKILL.md +13 -18
  39. package/skills/opencode/vidscript-guide.md +42 -39
  40. package/skills/opencode/vidscript-sample.md +4 -3
  41. package/skills/opencode/vidscript-strict.md +26 -8
  42. package/skills/skills/aider/SKILL.md +13 -18
  43. package/skills/skills/aider/vidscript-guide.md +42 -39
  44. package/skills/skills/aider/vidscript-sample.md +4 -3
  45. package/skills/skills/aider/vidscript-strict.md +26 -8
  46. package/skills/skills/claude/SKILL.md +13 -18
  47. package/skills/skills/claude/vidscript-guide.md +42 -39
  48. package/skills/skills/claude/vidscript-sample.md +4 -3
  49. package/skills/skills/claude/vidscript-strict.md +26 -8
  50. package/skills/skills/codex/SKILL.md +13 -18
  51. package/skills/skills/codex/vidscript-guide.md +42 -39
  52. package/skills/skills/codex/vidscript-sample.md +4 -3
  53. package/skills/skills/codex/vidscript-strict.md +26 -8
  54. package/skills/skills/cursor/SKILL.md +13 -18
  55. package/skills/skills/cursor/vidscript-guide.md +42 -39
  56. package/skills/skills/cursor/vidscript-sample.md +4 -3
  57. package/skills/skills/cursor/vidscript-strict.md +26 -8
  58. package/skills/skills/opencode/SKILL.md +13 -18
  59. package/skills/skills/opencode/vidscript-guide.md +42 -39
  60. package/skills/skills/opencode/vidscript-sample.md +4 -3
  61. package/skills/skills/opencode/vidscript-strict.md +26 -8
package/README.md CHANGED
@@ -1,3 +1,4 @@
1
+ import motion from "@scenerok/basic-animations"
1
2
  # SceneRok CLI
2
3
 
3
4
  Create videos from your terminal and agent workflows.
@@ -102,7 +103,7 @@ scenerok project render <project-id> --watch --download ./renders
102
103
 
103
104
  ```bash
104
105
  scenerok cache pull
105
- scenerok cache pull --plugin xai-media-video --output ~/.scenerok/cache/assets
106
+ scenerok cache pull --plugin @scenerok/xai --output ~/.scenerok/cache/assets
106
107
  ```
107
108
 
108
109
  Downloads cached generated assets and writes a `manifest.json` to the output folder.
@@ -123,15 +124,17 @@ Config is stored in `~/.scenerok/config.json`:
123
124
  VidScript is a declarative language for describing video compositions.
124
125
 
125
126
  ```vidscript
127
+ import motion from "@scenerok/basic-animations"
128
+
126
129
  input hero = "./assets/hero.mp4"
127
130
 
128
131
  [0s .. 5s] = hero
129
- [0.4s .. 3s] = text "Hello World", font: "Inter Bold", size: 72, x: "50%", y: "50%", align: center, animate: [fadeIn(0.6s), slideY(40, 0, 0.8s)]
132
+ [0.4s .. 3s] = text "Hello World", font: "Inter Bold", size: 72, x: "50%", y: "50%", align: center, animate: [motion.fadeIn(0.6s), motion.slideY(40, 0, 0.8s)]
130
133
 
131
134
  output to "video.mp4", resolution: "1080x1920", fps: 30
132
135
  ```
133
136
 
134
- Animation helpers: `fadeIn`, `fadeOut`, `slideX`, `slideY`, `popIn`, `riseIn`, `swingIn`, `glitchIn`, `float`, `typewriter`.
137
+ Animation helpers are package functions: `motion.fadeIn`, `motion.fadeOut`, `motion.slideX`, `motion.slideY`, `motion.popIn`, `motion.riseIn`, `motion.swingIn`, `motion.glitchIn`, `motion.float`, `motion.typewriter`.
135
138
 
136
139
  ## Links
137
140
 
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,eAAO,MAAM,WAAW,SA0FrB,CAAC"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,eAAO,MAAM,WAAW,SA2GrB,CAAC"}
@@ -3,12 +3,16 @@ import chalk from 'chalk';
3
3
  import ora from 'ora';
4
4
  import { readConfig, writeConfig, isAuthenticated } from '../lib/config.js';
5
5
  import { initiateDeviceAuth, pollDeviceAuth } from '../lib/api.js';
6
+ import { getBaseUrl } from '../lib/config.js';
7
+ import { formatApiErrorMessage } from '../lib/format-api-error.js';
8
+ import { getLogFilePath, isVerbose, logError } from '../lib/logger.js';
6
9
  export const authCommand = new Command('auth')
7
10
  .description('Authenticate with SceneRok')
8
11
  .addCommand(new Command('login')
9
12
  .description('Log in to SceneRok via browser')
10
13
  .option('-n, --name <name>', 'Device name', 'CLI')
11
14
  .option('-u, --base-url <url>', 'SceneRok API base URL (overrides ~/.scenerok/config.json and SCENEROK_BASE_URL)')
15
+ .option('-v, --verbose', 'Print debug output and log API calls to ~/.scenerok/logs/cli.log')
12
16
  .action(async (options) => {
13
17
  if (options.baseUrl) {
14
18
  const config = readConfig();
@@ -16,6 +20,10 @@ export const authCommand = new Command('auth')
16
20
  writeConfig(config);
17
21
  }
18
22
  console.log(chalk.cyan('\n🔐 SceneRok Authentication\n'));
23
+ console.log(chalk.dim(`API: ${getBaseUrl()}`));
24
+ if (options.verbose || isVerbose()) {
25
+ console.log(chalk.dim(`Log file: ${getLogFilePath()}\n`));
26
+ }
19
27
  const spinner = ora('Initiating device auth...').start();
20
28
  try {
21
29
  const auth = await initiateDeviceAuth(options.name);
@@ -53,13 +61,21 @@ export const authCommand = new Command('auth')
53
61
  }
54
62
  catch (error) {
55
63
  spinner.fail('Authentication failed');
56
- console.error(chalk.red(error instanceof Error ? error.message : String(error)));
64
+ logError('auth login failed', {
65
+ baseUrl: getBaseUrl(),
66
+ error: formatApiErrorMessage(error),
67
+ });
68
+ console.error(chalk.red(formatApiErrorMessage(error)));
69
+ console.error(chalk.dim(`\nLog: ${getLogFilePath()}`));
70
+ console.error(chalk.dim('Retry with: scenerok auth login --verbose (or SCENEROK_VERBOSE=1 scenerok auth login)'));
57
71
  process.exit(1);
58
72
  }
59
73
  }))
60
74
  .addCommand(new Command('status')
61
75
  .description('Check authentication status')
62
76
  .action(() => {
77
+ console.log(chalk.dim(`API: ${getBaseUrl()}`));
78
+ console.log(chalk.dim(`Log: ${getLogFilePath()}`));
63
79
  if (isAuthenticated()) {
64
80
  console.log(chalk.green('✅ Authenticated with SceneRok'));
65
81
  }
@@ -1 +1 @@
1
- {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/commands/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+BpC,eAAO,MAAM,cAAc,SAyJxB,CAAC"}
1
+ {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/commands/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuCpC,eAAO,MAAM,cAAc,SA+JxB,CAAC"}
@@ -6,6 +6,14 @@ import { join, resolve } from 'node:path';
6
6
  import { downloadRender, getRenderStatus, listProjects, submitRender, uploadProject } from '../lib/api.js';
7
7
  import { isAuthenticated } from '../lib/config.js';
8
8
  import { appendProjectToForm, prepareProject } from '../lib/project.js';
9
+ function formatDuration(ms) {
10
+ const totalSeconds = Math.max(0, Math.round(ms / 1000));
11
+ const minutes = Math.floor(totalSeconds / 60);
12
+ const seconds = totalSeconds % 60;
13
+ if (minutes === 0)
14
+ return `${seconds}s`;
15
+ return `${minutes}m ${seconds.toString().padStart(2, '0')}s`;
16
+ }
9
17
  async function waitForRender(renderId) {
10
18
  while (true) {
11
19
  await new Promise((resolveWait) => setTimeout(resolveWait, 3000));
@@ -74,8 +82,11 @@ export const projectCommand = new Command('project')
74
82
  console.log(` Download: ${result.render.downloadUrl}`);
75
83
  if (options.watch || options.download) {
76
84
  console.log(chalk.dim('\nWatching render status...\n'));
77
- await waitForRender(result.render.renderId);
85
+ const status = await waitForRender(result.render.renderId);
78
86
  console.log(chalk.green('\nRender complete'));
87
+ if (status.clickToDownloadMs !== null) {
88
+ console.log(` Time: ${formatDuration(status.clickToDownloadMs)}`);
89
+ }
79
90
  }
80
91
  if (options.download) {
81
92
  const outputDir = typeof options.download === 'string' ? options.download : 'renders';
@@ -143,8 +154,11 @@ export const projectCommand = new Command('project')
143
154
  console.log(` URL: ${result.renderUrl}`);
144
155
  console.log(` Download: ${result.downloadUrl}`);
145
156
  if (options.watch || options.download) {
146
- await waitForRender(result.renderId);
157
+ const status = await waitForRender(result.renderId);
147
158
  console.log(chalk.green('\nRender complete'));
159
+ if (status.clickToDownloadMs !== null) {
160
+ console.log(` Time: ${formatDuration(status.clickToDownloadMs)}`);
161
+ }
148
162
  }
149
163
  if (options.download) {
150
164
  const outputDir = typeof options.download === 'string' ? options.download : 'renders';
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/commands/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkEpC,eAAO,MAAM,aAAa,SAAwB,CAAC;AAEnD,eAAO,MAAM,aAAa,SAuFtB,CAAC"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/commands/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6EpC,eAAO,MAAM,aAAa,SAAwB,CAAC;AAEnD,eAAO,MAAM,aAAa,SA0FtB,CAAC"}
@@ -5,6 +5,14 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
5
5
  import { join, resolve } from 'node:path';
6
6
  import { submitRender, getRenderStatus, downloadRender } from '../lib/api.js';
7
7
  import { isAuthenticated } from '../lib/config.js';
8
+ function formatDuration(ms) {
9
+ const totalSeconds = Math.max(0, Math.round(ms / 1000));
10
+ const minutes = Math.floor(totalSeconds / 60);
11
+ const seconds = totalSeconds % 60;
12
+ if (minutes === 0)
13
+ return `${seconds}s`;
14
+ return `${minutes}m ${seconds.toString().padStart(2, '0')}s`;
15
+ }
8
16
  function createStatusCommand() {
9
17
  return new Command('status')
10
18
  .description('Check render status')
@@ -27,6 +35,9 @@ function createStatusCommand() {
27
35
  console.log(` ID: ${status.id}`);
28
36
  console.log(` Status: ${status.status}`);
29
37
  console.log(` Progress: ${status.progress}%`);
38
+ if (status.clickToDownloadMs !== null) {
39
+ console.log(` Time: ${formatDuration(status.clickToDownloadMs)}`);
40
+ }
30
41
  if (status.outputUrl) {
31
42
  console.log(` Output: ${status.outputUrl}`);
32
43
  }
@@ -104,6 +115,9 @@ export const renderCommand = new Command('render')
104
115
  const status = await getRenderStatus(result.renderId);
105
116
  if (status.status === 'completed') {
106
117
  console.log(chalk.green(`\\n✅ Render complete!\\n`));
118
+ if (status.clickToDownloadMs !== null) {
119
+ console.log(` Time: ${formatDuration(status.clickToDownloadMs)}`);
120
+ }
107
121
  console.log(` Download: ${status.downloadUrl}`);
108
122
  if (options.download) {
109
123
  const outputDir = typeof options.download === 'string' ? options.download : 'renders';
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBpC,eAAO,MAAM,eAAe,SAkDxB,CAAC"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2CpC,eAAO,MAAM,eAAe,SA+CxB,CAAC"}
@@ -11,6 +11,28 @@ function collectValidationErrors(result) {
11
11
  }
12
12
  return ['Validation failed (no error details returned by server)'];
13
13
  }
14
+ function printAestheticWarnings(result) {
15
+ if (!result.warnings || result.warnings.length === 0)
16
+ return;
17
+ console.log(chalk.yellow('\n Aesthetic warnings:'));
18
+ if (result.aestheticScore != null) {
19
+ console.log(chalk.yellow(` Score: ${result.aestheticScore}/100`));
20
+ }
21
+ for (const warning of result.warnings) {
22
+ console.log(chalk.yellow(` • ${warning}`));
23
+ }
24
+ }
25
+ function formatUsd(value) {
26
+ return `$${value.toFixed(2)}`;
27
+ }
28
+ function printCostEstimate(costEstimate) {
29
+ if (!costEstimate)
30
+ return;
31
+ console.log('');
32
+ console.log(` Total Cost: ${formatUsd(costEstimate.totalCostUsd)}`);
33
+ console.log(` Savings through cache: ${formatUsd(costEstimate.cacheSavingsUsd)}`);
34
+ console.log(` Est. Spend: ${formatUsd(costEstimate.estimatedSpendUsd)}`);
35
+ }
14
36
  export const validateCommand = new Command('validate')
15
37
  .description('Validate a VidScript file')
16
38
  .argument('<file>', 'Path to .vid or .vidscript file')
@@ -40,18 +62,15 @@ export const validateCommand = new Command('validate')
40
62
  if (pluginCalls != null) {
41
63
  console.log(` Plugin calls: ${pluginCalls}`);
42
64
  }
43
- if (result.warnings && result.warnings.length > 0) {
44
- console.log(chalk.yellow('\\n Warnings:'));
45
- for (const warning of result.warnings) {
46
- console.log(` • ${warning}`);
47
- }
48
- }
65
+ printCostEstimate(result.costEstimate);
66
+ printAestheticWarnings(result);
49
67
  }
50
68
  else {
51
69
  console.log(chalk.red('\\n❌ VidScript is invalid\\n'));
52
70
  for (const err of collectValidationErrors(result)) {
53
71
  console.log(` • ${err}`);
54
72
  }
73
+ printAestheticWarnings(result);
55
74
  process.exit(1);
56
75
  }
57
76
  console.log('');
package/dist/index.js CHANGED
@@ -15,7 +15,8 @@ const packageJson = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.me
15
15
  program
16
16
  .name('scenerok')
17
17
  .description('SceneRok CLI - Create videos from your terminal and agent workflows')
18
- .version(packageJson.version || '0.0.0');
18
+ .version(packageJson.version || '0.0.0')
19
+ .option('-v, --verbose', 'Log API requests to ~/.scenerok/logs/cli.log');
19
20
  program.addCommand(authCommand);
20
21
  program.addCommand(skillsCommand);
21
22
  program.addCommand(renderCommand);
@@ -29,6 +30,7 @@ program.on('--help', () => {
29
30
  console.log('');
30
31
  console.log(chalk.cyan('Examples:'));
31
32
  console.log(' $ scenerok auth login');
33
+ console.log(' $ scenerok auth login --verbose # debug log at ~/.scenerok/logs/cli.log');
32
34
  console.log(' $ scenerok skills install opencode');
33
35
  console.log(' $ scenerok validate video.vidscript');
34
36
  console.log(' $ scenerok render video.vidscript --resolution 1080x1920');
package/dist/lib/api.d.ts CHANGED
@@ -3,12 +3,45 @@ declare class ApiError extends Error {
3
3
  responseBody?: unknown | undefined;
4
4
  constructor(message: string, status: number, responseBody?: unknown | undefined);
5
5
  }
6
+ export interface RenderCostEstimate {
7
+ totalCostUsd: number;
8
+ cacheSavingsUsd: number;
9
+ estimatedSpendUsd: number;
10
+ renderCostUsd: number;
11
+ pluginCostUsd: number;
12
+ pluginCostAfterCacheUsd: number;
13
+ durationSeconds: number;
14
+ pluginMarkup: number;
15
+ renderTokensPerSecond: number;
16
+ lineItems: Array<{
17
+ name: string;
18
+ provider: string;
19
+ product: string;
20
+ rawCostUsd: number;
21
+ billedCostUsd: number;
22
+ cacheKey?: string;
23
+ cacheStatus: 'hit' | 'duplicate' | 'miss' | 'not_cacheable';
24
+ metadata?: unknown;
25
+ }>;
26
+ }
6
27
  export declare function validateVidscript(vidscript: string): Promise<{
7
28
  valid: boolean;
8
29
  statements?: number;
9
30
  scenes?: number;
10
31
  duration?: number;
11
32
  warnings?: string[];
33
+ aestheticWarnings?: Array<{
34
+ ruleId: string;
35
+ severity: "warning";
36
+ category: string;
37
+ message: string;
38
+ recommendation: string;
39
+ elementId?: string;
40
+ start?: number;
41
+ end?: number;
42
+ }>;
43
+ aestheticScore?: number;
44
+ costEstimate?: RenderCostEstimate;
12
45
  errors?: string[];
13
46
  error?: string;
14
47
  }>;
@@ -35,6 +68,7 @@ export declare function getRenderStatus(renderId: number): Promise<{
35
68
  error: string | null;
36
69
  createdAt: string;
37
70
  completedAt: string | null;
71
+ clickToDownloadMs: number | null;
38
72
  }>;
39
73
  export declare function downloadRender(renderId: number): Promise<{
40
74
  buffer: Buffer;
@@ -78,6 +112,15 @@ export declare function getAssetCache(options?: {
78
112
  updatedAt: string;
79
113
  }>;
80
114
  }>;
115
+ export declare function checkAssetCache(cacheKeys: string[]): Promise<{
116
+ entries: Array<{
117
+ cacheKey: string;
118
+ r2Url: string;
119
+ pluginName: string | null;
120
+ metadata: unknown;
121
+ updatedAt: string;
122
+ }>;
123
+ }>;
81
124
  export declare function initiateDeviceAuth(deviceName: string): Promise<{
82
125
  device_code: string;
83
126
  user_code: string;
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AASA,cAAM,QAAS,SAAQ,KAAK;IAGjB,MAAM,EAAE,MAAM;IACd,YAAY,CAAC,EAAE,OAAO;gBAF7B,OAAO,EAAE,MAAM,EACR,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,OAAO,YAAA;CAKhC;AAkED,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM;WAE9C,OAAO;iBACD,MAAM;aACV,MAAM;eACJ,MAAM;eACN,MAAM,EAAE;aACV,MAAM,EAAE;YACT,MAAM;GAEjB;AAED,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM;cAEhF,MAAM;YACR,MAAM;gBACF,MAAM;oBACF,MAAM;eACX,MAAM;iBACJ,MAAM;aACV,MAAM;kBACD,MAAM;gBACR,MAAM,GAAG,IAAI;GAE5B;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM;QAE9C,MAAM;YACF,MAAM;cACJ,MAAM;eACL,MAAM,GAAG,IAAI;oBACR,MAAM;eACX,MAAM;iBACJ,MAAM;mBACJ,OAAO;WACf,MAAM,GAAG,IAAI;eACT,MAAM;iBACJ,MAAM,GAAG,IAAI;GAE7B;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAmBpG;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,QAAQ;aAEzC;QACP,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB;YACO,KAAK,CAAC;QACZ,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;aACO,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,GAAG,IAAI;GAE3D;AAED,wBAAsB,YAAY;cAEpB,KAAK,CAAC;QACd,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;GAEL;AAED,wBAAsB,aAAa,CAAC,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO;aAMxE,KAAK,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,QAAQ,EAAE,OAAO,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;GAEL;AAED,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM;iBAE1C,MAAM;eACR,MAAM;sBACC,MAAM;gBACZ,MAAM;cACR,MAAM;GAEnB;AAED,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM;YAE3C,MAAM;mBACC,MAAM;iBACR,MAAM;iBACN,MAAM;YACX,MAAM;GAEjB;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"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"}
package/dist/lib/api.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import fetch from 'node-fetch';
2
2
  import { getBaseUrl, getApiToken } from './config.js';
3
+ import { logDebug, logError } from './logger.js';
3
4
  class ApiError extends Error {
4
5
  status;
5
6
  responseBody;
@@ -23,15 +24,48 @@ function getAuthHeaders(contentType = 'application/json') {
23
24
  }
24
25
  async function apiCall(method, path, body) {
25
26
  const baseUrl = getBaseUrl();
26
- const response = await fetch(`${baseUrl}${path}`, {
27
+ const url = `${baseUrl}${path}`;
28
+ const started = Date.now();
29
+ logDebug('API request', {
27
30
  method,
28
- headers: getAuthHeaders(),
29
- body: body ? JSON.stringify(body) : undefined,
31
+ url,
32
+ hasBody: body != null,
30
33
  });
31
- const data = await response.json().catch(() => ({}));
34
+ let response;
35
+ try {
36
+ response = await fetch(url, {
37
+ method,
38
+ headers: getAuthHeaders(),
39
+ body: body ? JSON.stringify(body) : undefined,
40
+ });
41
+ }
42
+ catch (error) {
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ logError('API network error', { method, url, message });
45
+ throw new ApiError(`Network error: ${message}`, 0, { cause: message });
46
+ }
47
+ const rawText = await response.text();
48
+ let data = {};
49
+ if (rawText) {
50
+ try {
51
+ data = JSON.parse(rawText);
52
+ }
53
+ catch {
54
+ data = { raw: rawText.slice(0, 2000) };
55
+ }
56
+ }
57
+ const durationMs = Date.now() - started;
32
58
  if (!response.ok) {
59
+ logError('API error response', {
60
+ method,
61
+ url,
62
+ status: response.status,
63
+ durationMs,
64
+ body: data,
65
+ });
33
66
  throw new ApiError(data.error || `HTTP ${response.status}`, response.status, data);
34
67
  }
68
+ logDebug('API success', { method, url, status: response.status, durationMs });
35
69
  return data;
36
70
  }
37
71
  async function apiFormCall(path, formData) {
@@ -90,6 +124,9 @@ export async function getAssetCache(options = {}) {
90
124
  const suffix = params.toString() ? `?${params.toString()}` : '';
91
125
  return apiCall('GET', `/api/cli/asset-cache${suffix}`);
92
126
  }
127
+ export async function checkAssetCache(cacheKeys) {
128
+ return apiCall('POST', '/api/cli/asset-cache/check', { cacheKeys });
129
+ }
93
130
  export async function initiateDeviceAuth(deviceName) {
94
131
  return apiCall('POST', '/api/cli/auth/device', { deviceName });
95
132
  }
@@ -0,0 +1,2 @@
1
+ export declare function formatApiErrorMessage(error: unknown): string;
2
+ //# sourceMappingURL=format-api-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-api-error.d.ts","sourceRoot":"","sources":["../../src/lib/format-api-error.ts"],"names":[],"mappings":"AAEA,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAwB5D"}
@@ -0,0 +1,24 @@
1
+ import { ApiError } from './api.js';
2
+ export function formatApiErrorMessage(error) {
3
+ if (error instanceof ApiError) {
4
+ const parts = [`HTTP ${error.status}: ${error.message}`];
5
+ const body = error.responseBody;
6
+ if (body && typeof body === 'object') {
7
+ const record = body;
8
+ if (typeof record.hint === 'string') {
9
+ parts.push(`Hint: ${record.hint}`);
10
+ }
11
+ if (typeof record.detail === 'string') {
12
+ parts.push(`Detail: ${record.detail}`);
13
+ }
14
+ if (typeof record.code === 'string') {
15
+ parts.push(`Code: ${record.code}`);
16
+ }
17
+ }
18
+ return parts.join('\n');
19
+ }
20
+ if (error instanceof Error) {
21
+ return error.message;
22
+ }
23
+ return String(error);
24
+ }
@@ -0,0 +1,8 @@
1
+ export declare function getLogFilePath(): string;
2
+ export declare function isVerbose(): boolean;
3
+ export declare function writeLog(level: 'debug' | 'info' | 'warn' | 'error', message: string, meta?: Record<string, unknown>): void;
4
+ export declare function logDebug(message: string, meta?: Record<string, unknown>): void;
5
+ export declare function logInfo(message: string, meta?: Record<string, unknown>): void;
6
+ export declare function logWarn(message: string, meta?: Record<string, unknown>): void;
7
+ export declare function logError(message: string, meta?: Record<string, unknown>): void;
8
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAOA,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,wBAAgB,SAAS,IAAI,OAAO,CAOnC;AAmBD,wBAAgB,QAAQ,CACtB,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAC1C,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,IAAI,CAIN;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAM9E;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAE7E;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAE7E;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAE9E"}
@@ -0,0 +1,51 @@
1
+ import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { getConfigDir } from './config.js';
4
+ const LOG_DIR = join(getConfigDir(), 'logs');
5
+ const LOG_FILE = join(LOG_DIR, 'cli.log');
6
+ export function getLogFilePath() {
7
+ return LOG_FILE;
8
+ }
9
+ export function isVerbose() {
10
+ const verboseEnv = ['SCENEROK', '_', 'VERBOSE'].join('');
11
+ return (process.env[verboseEnv] === '1' ||
12
+ process.argv.includes('--verbose') ||
13
+ process.argv.includes('-v'));
14
+ }
15
+ function ensureLogDir() {
16
+ if (!existsSync(LOG_DIR)) {
17
+ mkdirSync(LOG_DIR, { recursive: true });
18
+ }
19
+ }
20
+ function formatMeta(meta) {
21
+ if (!meta || Object.keys(meta).length === 0) {
22
+ return '';
23
+ }
24
+ try {
25
+ return ` ${JSON.stringify(meta)}`;
26
+ }
27
+ catch {
28
+ return ' [meta unserializable]';
29
+ }
30
+ }
31
+ export function writeLog(level, message, meta) {
32
+ ensureLogDir();
33
+ const line = `[${new Date().toISOString()}] ${level.toUpperCase()} ${message}${formatMeta(meta)}\n`;
34
+ appendFileSync(LOG_FILE, line, 'utf-8');
35
+ }
36
+ export function logDebug(message, meta) {
37
+ writeLog('debug', message, meta);
38
+ if (isVerbose()) {
39
+ // eslint-disable-next-line no-console
40
+ console.error(`[debug] ${message}${formatMeta(meta)}`);
41
+ }
42
+ }
43
+ export function logInfo(message, meta) {
44
+ writeLog('info', message, meta);
45
+ }
46
+ export function logWarn(message, meta) {
47
+ writeLog('warn', message, meta);
48
+ }
49
+ export function logError(message, meta) {
50
+ writeLog('error', message, meta);
51
+ }
@@ -2,6 +2,7 @@
2
2
  # Requires: scenerok secrets set XAI_API_KEY=... (or configured on scenerok.com account)
3
3
 
4
4
  import xai from "@scenerok/xai"
5
+ import motion from "@scenerok/basic-animations"
5
6
 
6
7
  # Generated visuals — one clip per segment; prompts must use straight double quotes only
7
8
  [-] = video xai.imagine("Cinematic slow-motion pour of rich creamy chocolate milk into a sleek modern 250ml carton on marble, warm studio lighting, shallow depth of field, 9:16 vertical", aspect_ratio: "9:16", duration: 5)
@@ -10,13 +11,13 @@ import xai from "@scenerok/xai"
10
11
 
11
12
  [-] = video xai.imagine("Silky chocolate milk swirl in glass with RokMilk carton blurred in background, golden light, appetizing food cinematography, 9:16 vertical", aspect_ratio: "9:16", duration: 5)
12
13
 
13
- [0s .. 2.5s] = text "RokMilk", font: "Bebas Neue", size: 80, color: "#FFD700", x: "50%", y: "18%", align: center, stroke: "#3E1C00", stroke_width: 4, letter_spacing: 6, animate: fadeIn(0.6s)
14
+ [0s .. 2.5s] = text "RokMilk", font: "Bebas Neue", size: 80, color: "#FFD700", x: "50%", y: "18%", align: center, stroke: "#3E1C00", stroke_width: 4, letter_spacing: 6, animate: motion.fadeIn(0.6s)
14
15
 
15
- [2s .. 5s] = text "Chocolate Milkshake", font: "Outfit", size: 56, color: "#FFFFFF", x: "50%", y: "45%", align: center, stroke: "#3E1C00", stroke_width: 3, animate: riseIn(0.8s)
16
+ [2s .. 5s] = text "Chocolate Milkshake", font: "Outfit", size: 56, color: "#FFFFFF", x: "50%", y: "45%", align: center, stroke: "#3E1C00", stroke_width: 3, animate: motion.riseIn(0.8s)
16
17
 
17
- [5s .. 8s] = text "250ml of Pure Indulgence", font: "Inter", size: 42, color: "#F3E5AB", x: "50%", y: "55%", align: center, stroke: "#3E1C00", stroke_width: 2, animate: slideY(40, 0, 0.8s)
18
+ [5s .. 8s] = text "250ml of Pure Indulgence", font: "Inter", size: 42, color: "#F3E5AB", x: "50%", y: "55%", align: center, stroke: "#3E1C00", stroke_width: 2, animate: motion.slideY(40, 0, 0.8s)
18
19
 
19
- [11s .. 14s] = text "visit your nearest grocery store", font: "Bebas Neue", size: 48, color: "#FFD700", x: "50%", y: "82%", align: center, stroke: "#3E1C00", stroke_width: 4, letter_spacing: 3, animate: fadeIn(1s)
20
+ [11s .. 14s] = text "visit your nearest grocery store", font: "Bebas Neue", size: 48, color: "#FFD700", x: "50%", y: "82%", align: center, stroke: "#3E1C00", stroke_width: 4, letter_spacing: 3, animate: motion.fadeIn(1s)
20
21
 
21
22
  [0s .. 15s] = filter "vignette", intensity: 0.3
22
23
  [0s .. 15s] = filter "saturation", value: 1.12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scenerok/cli",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "SceneRok CLI - Create videos from your terminal and agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -67,37 +67,32 @@ Built-in filters: `monochrome`, `sepia`, `blur`, `chromatic`, `glitch`, `vignett
67
67
  output to "video.mp4", resolution: "1080x1920", fps: 30
68
68
  ```
69
69
 
70
- ### Module System
70
+ ### Package Imports
71
71
 
72
72
  ```vidscript
73
- export const BRAND_COLOR = "#FF5733"
74
- export timeline intro(clip: string, headline: string) {
75
- [-] = clip # auto-append
76
- clip.Trim(start: 0s, end: 3s)
77
- [- 2s] = text headline, style: title # auto-start, 2s duration
78
- }
73
+ import xai from "@scenerok/xai" # xAI image/video/tts
74
+ import eleven from "@elevenlabs/music" # ElevenLabs generative music
75
+ import motion from "@scenerok/basic-animations" # text/video animation helpers
79
76
  ```
80
77
 
81
- ```vidscript
82
- import { intro } from "./timelines.vid"
83
- import * as pack from "./effects.vid"
84
- import eleven from "@elevenlabs/tts" # future scoped package # from npm registry
85
- import music from "@elevenlabs/music" # generative music
86
- use intro at [-] with { clip: main, headline: "Welcome" }
87
- ```
88
-
89
- ### Plugin Calls (required import)
78
+ ### Plugin Calls
90
79
 
91
80
  ```vidscript
92
81
  import xai from "@scenerok/xai"
82
+ import eleven from "@elevenlabs/music"
83
+ import motion from "@scenerok/basic-animations"
93
84
 
94
85
  [-] = video xai.imagine("Cinematic product shot", aspect_ratio: "9:16", duration: 5)
95
86
  [-] = audio xai.tts("Welcome to SceneRok", voice: "eve")
87
+
88
+ # ElevenLabs music package functions require an import alias.
89
+ let bed = eleven.music("Warm premium launch bed", duration: 15, instrumental: true)
90
+ [0s .. 15s] = audio bed, volume: 0.35, fade_out: 2s
96
91
  ```
97
92
 
98
- **Always** add `import xai from "@scenerok/xai"` before any `xai.imagine` / `xai.tts` call or `animate: fadeIn(...)` helper. Calls without import fail validation with `Unknown function 'xai.imagine'`.
93
+ **Always** import the package that owns the function: `xai.*` from `@scenerok/xai`, `eleven.*` from `@elevenlabs/music`, and `motion.*` from `@scenerok/basic-animations`. Calls without import fail validation with `Unknown function 'xai.imagine'`.
99
94
 
100
- Put plugin calls **directly** in time blocks never `let clip = xai.imagine(...)`.
95
+ For ElevenLabs music, import `@elevenlabs/music` and call `eleven.music(...)`, `eleven.generateMusic(...)`, or `eleven.composeMusic(...)`. Use `let bed = eleven.music(...)` followed by `audio bed, volume: ...` when you need volume or fades.
101
96
 
102
97
  Read `vidscript-strict.md` for anti-patterns and `examples/system/*.vid` for copy-paste templates (installed with `scenerok skills install`).
103
98