pixifact-cli 0.1.3 → 0.1.4

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
@@ -1,44 +1,44 @@
1
1
  # Pixifact CLI
2
2
 
3
- Command line tools for Pixifact Scene automation.
3
+ Pixifact Scene 自动化命令行工具。
4
4
 
5
- The CLI is Bun-first in this release.
5
+ 本版本 CLI 是 Bun-first 工具。
6
6
 
7
- ## Install
7
+ ## 安装
8
8
 
9
9
  ```bash
10
10
  bun add -d pixifact-cli
11
11
  ```
12
12
 
13
- ## Commands
13
+ ## 命令
14
14
 
15
- Inspect a Scene:
15
+ 查看 Scene:
16
16
 
17
17
  ```bash
18
18
  pixifact scene inspect --project-root . --scene src/scenes/Hud.scene
19
19
  ```
20
20
 
21
- Validate one Scene:
21
+ 校验单个 Scene:
22
22
 
23
23
  ```bash
24
24
  pixifact scene validate --project-root . --scene src/scenes/Hud.scene
25
25
  ```
26
26
 
27
- Validate every compiler Scene:
27
+ 校验所有 compiler Scene:
28
28
 
29
29
  ```bash
30
30
  pixifact scene validate --project-root . --all
31
31
  ```
32
32
 
33
- Compile generated Scene runtime files:
33
+ 编译生成 Scene runtime 文件:
34
34
 
35
35
  ```bash
36
36
  pixifact compile-scenes --project-root .
37
37
  ```
38
38
 
39
- ## Agent Workflow
39
+ ## Agent 工作流
40
40
 
41
- Codex, Claude Code, and other coding agents should edit `.scene` files directly, then run:
41
+ Codex、Claude Code 和其他 coding agent 应直接编辑 `.scene` 文件,然后运行:
42
42
 
43
43
  ```bash
44
44
  pixifact scene validate --project-root . --all
@@ -46,9 +46,9 @@ pixifact compile-scenes --project-root .
46
46
  bun run build
47
47
  ```
48
48
 
49
- Do not edit generated files under `.pixifact/generated/**`.
49
+ 不要编辑 `.pixifact/generated/**` 下的生成文件。
50
50
 
51
- ## Requirements
51
+ ## 环境要求
52
52
 
53
53
  - Bun
54
- - `pixifact` installed in the target project
54
+ - 目标项目已安装 `pixifact`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pixifact-cli",
3
- "version": "0.1.3",
4
- "description": "CLI for Pixifact Scene automation.",
3
+ "version": "0.1.4",
4
+ "description": "Pixifact Scene 自动化 CLI。",
5
5
  "packageManager": "bun@1.3.13",
6
6
  "type": "module",
7
7
  "bin": {
@@ -12,7 +12,7 @@
12
12
  ],
13
13
  "repository": {
14
14
  "type": "git",
15
- "url": "git+https://github.com/hxg2050/pixifact.git",
15
+ "url": "https://github.com/hxg2050/pixifact",
16
16
  "directory": "packages/pixifact-cli"
17
17
  },
18
18
  "keywords": [
@@ -31,6 +31,6 @@
31
31
  "start": "bun src/pixifact-cli.ts"
32
32
  },
33
33
  "dependencies": {
34
- "pixifact": "^0.1.3"
34
+ "pixifact": "^0.1.4"
35
35
  }
36
36
  }
package/src/automation.ts CHANGED
@@ -2,8 +2,10 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import {
4
4
  createSceneRevision,
5
+ builtinSceneAssetIds,
6
+ builtinSceneNameFromAssetId,
5
7
  defaultSceneSourceRoots,
6
- extractSceneScriptInterface,
8
+ extractSceneScriptInterfaces,
7
9
  findMissingScenePartReferences,
8
10
  inspectSceneTemplate,
9
11
  isIgnoredSceneSourceDirectory,
@@ -12,32 +14,22 @@ import {
12
14
  parseSceneTemplate,
13
15
  resolveSceneReference,
14
16
  sceneLocalName,
17
+ toPosixPath,
15
18
  validateSceneContent,
16
19
  } from 'pixifact/compiler';
20
+ import { readBuiltinSceneScriptSourcesSync } from 'pixifact/compiler-node';
17
21
  import {
18
- container,
19
- scene,
20
- SceneDocument,
21
22
  parsePixifactProjectConfig,
22
23
  pixifactProjectConfigFileName,
23
24
  summarizePixifactProjectConfig,
24
25
  } from 'pixifact';
25
- import type { SceneSpec, NodeSpec } from 'pixifact';
26
- import type { SceneProjectState } from 'pixifact';
27
- import type { SceneTemplate, SceneTemplateInterface, SceneValidationDiagnostic } from 'pixifact/compiler';
26
+ import type { SceneScriptInterface, SceneTemplate, SceneTemplateInterface, SceneTemplateNode, SceneValidationDiagnostic } from 'pixifact/compiler';
28
27
 
29
28
  interface ProjectFileSummary {
30
29
  path: string;
31
30
  kind: 'folder' | 'scene' | 'file';
32
31
  }
33
32
 
34
- interface EditableDocument {
35
- projectRoot: string;
36
- target: string;
37
- document: SceneDocument;
38
- isProjectFile: boolean;
39
- }
40
-
41
33
  interface ToolInput {
42
34
  projectRoot?: unknown;
43
35
  scenePath?: unknown;
@@ -48,34 +40,13 @@ interface ToolInput {
48
40
  type CompilerSceneValidationSuccess = ReturnType<typeof validateCompilerSceneFile> & { ok: true };
49
41
  type CompilerSceneValidationFailure = ReturnType<typeof validateCompilerSceneFile> & { ok: false };
50
42
 
51
- type DetailedNode = NodeSpec & {
43
+ type DetailedCompilerNode = SceneTemplateNode & {
52
44
  locator: string;
53
45
  depth: number;
54
46
  parent?: string;
55
47
  childCount: number;
56
48
  }
57
49
 
58
- interface NodeSummary {
59
- id?: string;
60
- key?: string;
61
- role?: string;
62
- name?: string;
63
- kind: NodeSpec['kind'];
64
- locator: string;
65
- depth: number;
66
- transform?: NodeSpec['transform'];
67
- components: Array<{
68
- id?: string;
69
- type: string;
70
- propKeys: string[];
71
- }>;
72
- childCount: number;
73
- children: NodeSummary[];
74
- }
75
-
76
- const projectStateType = 'pixifact.aiEditorProject';
77
- const sceneType = 'scene';
78
-
79
50
  function isRecord(value: unknown): value is Record<string, unknown> {
80
51
  return typeof value === 'object' && value !== null && !Array.isArray(value);
81
52
  }
@@ -128,58 +99,24 @@ function writeTextFile(filePath: string, value: string) {
128
99
  fs.writeFileSync(filePath, value, 'utf8');
129
100
  }
130
101
 
131
- function writeNewJsonFile(filePath: string, value: unknown) {
102
+ function writeNewTextFile(filePath: string, value: string, errorMessage: string) {
132
103
  try {
133
- fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, {
104
+ fs.writeFileSync(filePath, value, {
134
105
  encoding: 'utf8',
135
106
  flag: 'wx',
136
107
  });
137
108
  } catch (error) {
138
109
  if (isRecord(error) && error.code === 'EEXIST') {
139
- throw new Error('Scene file already exists.');
110
+ throw new Error(errorMessage);
140
111
  }
141
112
  throw error;
142
113
  }
143
114
  }
144
115
 
145
- function isProjectState(value: unknown): value is SceneProjectState {
146
- return isRecord(value)
147
- && value.version === 1
148
- && value.type === projectStateType
149
- && isRecord(value.scene)
150
- && (value.scene as { type?: unknown }).type === sceneType;
151
- }
152
-
153
- function isScene(value: unknown): value is SceneSpec {
154
- return isRecord(value)
155
- && value.version === 1
156
- && value.type === sceneType
157
- && isRecord(value.root);
158
- }
159
-
160
- function loadEditableDocument(projectRoot: unknown, scenePath: unknown): EditableDocument {
161
- const { root, target } = resolveProjectPath(projectRoot, scenePath);
162
- const content = readJsonFile(target);
163
- if (isProjectState(content)) {
164
- const document = new SceneDocument(content.scene);
165
- document.loadState(content);
166
- return {
167
- projectRoot: root,
168
- target,
169
- document,
170
- isProjectFile: true,
171
- };
172
- }
173
- if (!isScene(content)) {
174
- throw new Error('Target file must be a Pixifact Scene or AI editor project JSON.');
116
+ function assertNewFile(filePath: string, errorMessage: string) {
117
+ if (fs.existsSync(filePath)) {
118
+ throw new Error(errorMessage);
175
119
  }
176
- const document = new SceneDocument(content);
177
- return {
178
- projectRoot: root,
179
- target,
180
- document,
181
- isProjectFile: false,
182
- };
183
120
  }
184
121
 
185
122
  function loadCompilerScene(projectRoot: unknown, scenePath: unknown) {
@@ -217,7 +154,12 @@ function collectAssetFiles(root: string, directory: string, assets: Set<string>)
217
154
  }
218
155
 
219
156
  function collectCompilerSceneInterfaces(root: string, skippedScene?: string) {
220
- const interfaces: Record<string, SceneTemplateInterface> = {};
157
+ const sceneSources: {
158
+ scenePath: string;
159
+ template: SceneTemplate;
160
+ scriptPath: string;
161
+ scriptSource: string;
162
+ }[] = [];
221
163
 
222
164
  for (const scenePath of collectCompilerScenePaths(root)) {
223
165
  if (scenePath === skippedScene) {
@@ -225,11 +167,49 @@ function collectCompilerSceneInterfaces(root: string, skippedScene?: string) {
225
167
  }
226
168
  try {
227
169
  const template = parseSceneTemplate(readTextFile(path.join(root, scenePath)));
228
- const pair = readCompilerScenePairContract(root, scenePath, template);
170
+ const scriptPath = pairedSceneScriptPath(scenePath);
171
+ const absoluteScriptPath = path.join(root, scriptPath);
172
+ if (!fs.existsSync(absoluteScriptPath)) {
173
+ continue;
174
+ }
175
+ sceneSources.push({
176
+ scenePath,
177
+ template,
178
+ scriptPath,
179
+ scriptSource: readTextFile(absoluteScriptPath),
180
+ });
181
+ } catch {
182
+ continue;
183
+ }
184
+ }
185
+
186
+ const builtinSources = readBuiltinSceneScriptSourcesSync();
187
+ const descriptors = extractSceneScriptInterfaces([
188
+ ...builtinSceneAssetIds().map((scene) => {
189
+ const name = builtinSceneNameFromAssetId(scene);
190
+ return {
191
+ scene,
192
+ fileName: `${name}.ts`,
193
+ source: builtinSources[name],
194
+ };
195
+ }),
196
+ ...sceneSources.map((source) => ({
197
+ scene: source.scenePath,
198
+ fileName: source.scriptPath,
199
+ source: source.scriptSource,
200
+ })),
201
+ ]);
202
+ const interfaces: Record<string, SceneTemplateInterface> = Object.fromEntries(
203
+ builtinSceneAssetIds().map((scene) => [scene, descriptors[scene].interface]),
204
+ );
205
+
206
+ for (const source of sceneSources) {
207
+ try {
208
+ const pair = readCompilerScenePairContract(root, source.scenePath, source.template, descriptors[source.scenePath]);
229
209
  if (pair.diagnostics.length > 0 || !pair.sceneInterface) {
230
210
  continue;
231
211
  }
232
- interfaces[scenePath] = pair.sceneInterface;
212
+ interfaces[source.scenePath] = pair.sceneInterface;
233
213
  } catch {
234
214
  continue;
235
215
  }
@@ -299,6 +279,7 @@ function readCompilerScenePairContract(
299
279
  root: string,
300
280
  scenePath: string,
301
281
  template: SceneTemplate,
282
+ descriptor?: SceneScriptInterface,
302
283
  ): { diagnostics: SceneValidationDiagnostic[]; sceneInterface?: SceneTemplateInterface } {
303
284
  const expectedName = sceneLocalName(scenePath);
304
285
  if (template.name !== expectedName) {
@@ -315,20 +296,29 @@ function readCompilerScenePairContract(
315
296
  };
316
297
  }
317
298
 
318
- let descriptor: ReturnType<typeof extractSceneScriptInterface>;
299
+ let sceneDescriptor = descriptor;
319
300
  try {
320
- descriptor = extractSceneScriptInterface(readTextFile(absoluteScriptPath), absoluteScriptPath, { scene: scenePath });
301
+ sceneDescriptor ??= extractSceneScriptInterfaces([{
302
+ scene: scenePath,
303
+ fileName: absoluteScriptPath,
304
+ source: readTextFile(absoluteScriptPath),
305
+ }])[scenePath];
321
306
  } catch (error) {
322
307
  return {
323
308
  diagnostics: [compilerSceneScriptContractDiagnostic(error)],
324
309
  };
325
310
  }
326
- if (descriptor.className !== template.name) {
311
+ if (!sceneDescriptor) {
327
312
  return {
328
- diagnostics: [compilerSceneClassDiagnostic(template.name, descriptor.className)],
313
+ diagnostics: [compilerSceneScriptContractDiagnostic(new Error('No @scene decorator found.'))],
329
314
  };
330
315
  }
331
- const missingParts = findMissingScenePartReferences(template, descriptor.parts);
316
+ if (sceneDescriptor.className !== template.name) {
317
+ return {
318
+ diagnostics: [compilerSceneClassDiagnostic(template.name, sceneDescriptor.className)],
319
+ };
320
+ }
321
+ const missingParts = findMissingScenePartReferences(template, sceneDescriptor.parts);
332
322
  if (missingParts.length > 0) {
333
323
  return {
334
324
  diagnostics: missingParts.map((part) => compilerScenePartDiagnostic(part.property, part.id)),
@@ -337,7 +327,7 @@ function readCompilerScenePairContract(
337
327
 
338
328
  return {
339
329
  diagnostics: [],
340
- sceneInterface: descriptor.interface,
330
+ sceneInterface: sceneDescriptor.interface,
341
331
  };
342
332
  }
343
333
 
@@ -452,71 +442,6 @@ function validateAllCompilerScenes(root: string) {
452
442
  };
453
443
  }
454
444
 
455
- function nodeLocator(node: NodeSpec) {
456
- return node.id ?? node.key ?? node.name ?? '';
457
- }
458
-
459
- function summarizeNode(node: NodeSpec, depth = 0): NodeSummary {
460
- const children = node.kind === 'container' ? node.children ?? [] : [];
461
- return {
462
- id: node.id,
463
- key: node.key,
464
- role: node.role,
465
- name: node.name,
466
- kind: node.kind,
467
- locator: nodeLocator(node),
468
- depth,
469
- transform: node.transform,
470
- components: (node.components ?? []).map((component) => ({
471
- id: component.id,
472
- type: component.type,
473
- propKeys: Object.keys(component.props ?? {}),
474
- })),
475
- childCount: children.length,
476
- children: children.map((child) => summarizeNode(child, depth + 1)),
477
- };
478
- }
479
-
480
- function collectNodes(node: NodeSpec, depth = 0, parent?: string): Array<{
481
- id?: string;
482
- key?: string;
483
- role?: string;
484
- name?: string;
485
- kind: NodeSpec['kind'];
486
- locator: string;
487
- depth: number;
488
- parent?: string;
489
- componentCount: number;
490
- childCount: number;
491
- }> {
492
- return [
493
- {
494
- id: node.id,
495
- key: node.key,
496
- role: node.role,
497
- name: node.name,
498
- kind: node.kind,
499
- locator: nodeLocator(node),
500
- depth,
501
- parent,
502
- componentCount: node.components?.length ?? 0,
503
- childCount: node.kind === 'container' ? node.children?.length ?? 0 : 0,
504
- },
505
- ...(node.kind === 'container' ? (node.children ?? []).flatMap((child) => collectNodes(child, depth + 1, nodeLocator(node))) : []),
506
- ];
507
- }
508
-
509
- function summarizeScene(scene: SceneSpec) {
510
- const nodes = collectNodes(scene.root);
511
- return {
512
- name: scene.name,
513
- version: scene.version,
514
- nodeCount: nodes.length,
515
- componentCount: nodes.reduce((sum, node) => sum + node.componentCount, 0),
516
- root: summarizeNode(scene.root),
517
- };
518
- }
519
-
520
445
  function walkFiles(root: string, directory: string, depth: number, maxDepth: number, results: ProjectFileSummary[]) {
521
446
  if (depth > maxDepth) {
522
447
  return;
@@ -548,6 +473,69 @@ function walkFiles(root: string, directory: string, depth: number, maxDepth: num
548
473
  }
549
474
  }
550
475
 
476
+ function compilerNodeChildren(node: SceneTemplateNode): SceneTemplateNode[] {
477
+ if (node.kind === 'pixi') {
478
+ return node.children;
479
+ }
480
+ if (node.kind === 'sceneInstance') {
481
+ return Object.values(node.slots).flat();
482
+ }
483
+ return [];
484
+ }
485
+
486
+ function compilerNodeSegment(index: number, node: SceneTemplateNode) {
487
+ if (node.kind === 'slotOutlet') {
488
+ return `${index}:slot:${node.name}`;
489
+ }
490
+ return `${index}:${node.id ?? node.kind}`;
491
+ }
492
+
493
+ function collectDetailedCompilerNodes(
494
+ nodes: readonly SceneTemplateNode[],
495
+ depth = 0,
496
+ parent?: string,
497
+ ): DetailedCompilerNode[] {
498
+ return nodes.flatMap((node, index) => {
499
+ const locator = parent ? `${parent}/${compilerNodeSegment(index, node)}` : compilerNodeSegment(index, node);
500
+ const children = node.kind === 'pixi'
501
+ ? collectDetailedCompilerNodes(node.children, depth + 1, locator)
502
+ : node.kind === 'sceneInstance'
503
+ ? Object.entries(node.slots).flatMap(([slot, slotChildren]) => (
504
+ collectDetailedCompilerNodes(slotChildren, depth + 1, `${locator}/slot:${slot}`)
505
+ ))
506
+ : [];
507
+ return [
508
+ {
509
+ ...structuredClone(node),
510
+ locator,
511
+ depth,
512
+ parent,
513
+ childCount: compilerNodeChildren(node).length,
514
+ },
515
+ ...children,
516
+ ];
517
+ });
518
+ }
519
+
520
+ function createBlankCompilerSceneSource(sceneName: string) {
521
+ return [
522
+ `<Scene name="${sceneName}" width="960" height="540">`,
523
+ '</Scene>',
524
+ '',
525
+ ].join('\n');
526
+ }
527
+
528
+ function createBlankCompilerSceneScript(sceneName: string) {
529
+ return [
530
+ "import { Group } from 'pixifact/runtime';",
531
+ "import { scene } from 'pixifact/compiler';",
532
+ '',
533
+ '@scene()',
534
+ `export class ${sceneName} extends Group {}`,
535
+ '',
536
+ ].join('\n');
537
+ }
538
+
551
539
  export function createPixifactAutomation() {
552
540
  return {
553
541
  getProjectSummary(input: unknown) {
@@ -565,12 +553,15 @@ export function createPixifactAutomation() {
565
553
 
566
554
  getScene(input: unknown) {
567
555
  const args = assertRecord(input, 'input') as ToolInput;
568
- const editable = loadEditableDocument(args.projectRoot, args.scenePath);
556
+ const loaded = loadCompilerScene(args.projectRoot, args.scenePath);
557
+ const template = parseSceneTemplate(loaded.content);
569
558
  return {
570
- scenePath: path.relative(editable.projectRoot, editable.target),
571
- sourceType: editable.isProjectFile ? 'project' : 'scene',
572
- scene: editable.document.scene,
573
- summary: summarizeScene(editable.document.scene),
559
+ ok: true,
560
+ sourceType: 'compiler-scene',
561
+ scenePath: loaded.scenePath,
562
+ revision: createSceneRevision(loaded.content),
563
+ template,
564
+ summary: inspectSceneTemplate(template),
574
565
  };
575
566
  },
576
567
 
@@ -578,20 +569,26 @@ export function createPixifactAutomation() {
578
569
  const args = assertRecord(input, 'input') as ToolInput;
579
570
  const { root, target } = resolveProjectPath(args.projectRoot, args.scenePath);
580
571
  const sceneName = assertString(args.name, 'name');
581
- const nextScene = scene(sceneName, container('Root', {
582
- id: 'root',
583
- key: 'root',
584
- width: 320,
585
- height: 180,
586
- children: [],
587
- }));
588
-
589
- writeNewJsonFile(target, nextScene);
572
+ const scenePath = normalizeSceneAssetId(path.relative(root, target));
573
+ const sourceRootFailure = compilerSceneSourceRootFailure(scenePath);
574
+ if (sourceRootFailure) {
575
+ return sourceRootFailure;
576
+ }
577
+ const scriptPath = path.resolve(root, pairedSceneScriptPath(scenePath));
578
+ const source = createBlankCompilerSceneSource(sceneName);
579
+ const template = parseSceneTemplate(source);
580
+
581
+ assertNewFile(target, 'Scene file already exists.');
582
+ assertNewFile(scriptPath, 'Scene script already exists.');
583
+ writeNewTextFile(target, source, 'Scene file already exists.');
584
+ writeNewTextFile(scriptPath, createBlankCompilerSceneScript(sceneName), 'Scene script already exists.');
590
585
  return {
591
586
  ok: true,
592
- scenePath: path.relative(root, target),
593
- scene: nextScene,
594
- summary: summarizeScene(nextScene),
587
+ scenePath,
588
+ scriptPath: toPosixPath(path.relative(root, scriptPath)),
589
+ revision: createSceneRevision(source),
590
+ template,
591
+ summary: inspectSceneTemplate(template),
595
592
  };
596
593
  },
597
594
 
@@ -599,11 +596,16 @@ export function createPixifactAutomation() {
599
596
  const args = assertRecord(input, 'input') as ToolInput;
600
597
  const loaded = loadCompilerScene(args.projectRoot, args.scenePath);
601
598
  const template = parseSceneTemplate(loaded.content);
599
+ const sceneInterface = collectCompilerSceneInterfaces(loaded.root)[loaded.scenePath];
600
+ if (!sceneInterface) {
601
+ throw new Error(`Scene "${loaded.scenePath}" requires a readable paired @scene contract.`);
602
+ }
602
603
  return {
603
604
  ok: true,
604
605
  scenePath: loaded.scenePath,
605
606
  revision: createSceneRevision(loaded.content),
606
607
  summary: inspectSceneTemplate(template),
608
+ interface: sceneInterface,
607
609
  };
608
610
  },
609
611
 
@@ -621,9 +623,10 @@ export function createPixifactAutomation() {
621
623
 
622
624
  inspectNode(input: unknown) {
623
625
  const args = assertRecord(input, 'input') as ToolInput;
624
- const editable = loadEditableDocument(args.projectRoot, args.scenePath);
626
+ const loaded = loadCompilerScene(args.projectRoot, args.scenePath);
627
+ const template = parseSceneTemplate(loaded.content);
625
628
  const nodeId = assertString(args.node, 'node');
626
- const node = collectDetailedNodes(editable.document.scene.root).find((item) => item.locator === nodeId);
629
+ const node = collectDetailedCompilerNodes(template.children).find((item) => item.locator === nodeId);
627
630
  if (!node) {
628
631
  throw new Error(`Node "${nodeId}" was not found.`);
629
632
  }
@@ -631,16 +634,3 @@ export function createPixifactAutomation() {
631
634
  },
632
635
  };
633
636
  }
634
-
635
- function collectDetailedNodes(node: NodeSpec, depth = 0, parent?: string): DetailedNode[] {
636
- return [
637
- {
638
- ...structuredClone(node),
639
- locator: nodeLocator(node),
640
- depth,
641
- parent,
642
- childCount: node.kind === 'container' ? node.children?.length ?? 0 : 0,
643
- },
644
- ...(node.kind === 'container' ? (node.children ?? []).flatMap((child) => collectDetailedNodes(child, depth + 1, nodeLocator(node))) : []),
645
- ];
646
- }
@@ -220,7 +220,7 @@ async function executeFileCommand(positionals: string[], flags: Record<string, s
220
220
  }
221
221
 
222
222
  if (area === 'scene' && action === 'get') {
223
- return automation.getScene({
223
+ return automation.inspectCompilerScene({
224
224
  projectRoot: requireFlag(flags, 'project-root'),
225
225
  scenePath: requireFlag(flags, 'scene'),
226
226
  });