{"record":{"id":"67723b35e46f1698","repo":"heygen-com/hyperframes","slug":"exactly-one-of-grades-or-luts-is-required","errorCode":null,"errorMessage":"Exactly one of --grades or --luts is required","messagePattern":"Exactly one of --grades or --luts is required","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/commands/grade-compare.ts","lineNumber":274,"sourceCode":"  return [validateCell(\"original\", {}), ...cells];\n}\n\nexport function parseGradeCompareArgs(args: {\n  for?: unknown;\n  grades?: unknown;\n  luts?: unknown;\n  project?: unknown;\n  out?: unknown;\n  json?: unknown;\n  timeout?: unknown;\n}): ParsedGradeCompareArgs {\n  const frameArg = readOptionalString(args.for);\n  if (!frameArg) throw new Error(\"--for <path> is required\");\n\n  const gradesArg = readOptionalString(args.grades);\n  const lutsArg = readOptionalString(args.luts);\n  if (Boolean(gradesArg) === Boolean(lutsArg)) {\n    throw new Error(\"Exactly one of --grades or --luts is required\");\n  }\n\n  const projectDir = resolve(readOptionalString(args.project) ?? process.cwd());\n  const framePath = resolveFromBase(projectDir, frameArg);\n  const outPath = resolveFromBase(projectDir, readOptionalString(args.out) ?? \"grade-compare.png\");\n\n  return {\n    framePath,\n    projectDir,\n    outPath,\n    source: gradesArg\n      ? { kind: \"grades\", path: resolveFromBase(projectDir, gradesArg) }\n      : { kind: \"luts\", value: lutsArg ?? \"\" },\n    json: args.json === true,\n    timeoutMs: Number.parseInt(readOptionalString(args.timeout) ?? \"\", 10) || 5000,\n  };\n}\n","sourceCodeStart":256,"sourceCodeEnd":292,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/commands/grade-compare.ts#L256-L292","documentation":"Thrown by parseGradeCompareArgs when `--grades` and `--luts` are either both supplied or both absent (`Boolean(gradesArg) === Boolean(lutsArg)` covers both true and both false). The command needs exactly one source of candidate looks — a grades JSON file or a comma-separated LUT list — because mixing them would be ambiguous and supplying neither leaves nothing to render.","triggerScenarios":"Running grade-compare with both `--grades grades.json --luts a.cube`; or with neither flag; or with both empty strings.","commonSituations":"Copy-pasting a command that evolved from one mode to the other; CI templates that conditionally set both; forgetting which mode you intended.","solutions":["Pick one source: use `--grades grades.json` for inline-defined looks, or `--luts a.cube,b.cube` for .cube files.","Remove whichever flag is unwanted from the command line.","If neither is set, add the one that matches your asset type."],"exampleFix":"# before (both)\nhyperframes grade-compare --for f.png --grades g.json --luts a.cube\n# after (pick one)\nhyperframes grade-compare --for f.png --grades g.json","handlingStrategy":"validation","validationCode":"// Ensure exactly one source flag is set\nfunction pickSource(grades?: string, luts?: string): \"grades\" | \"luts\" {\n  const g = Boolean(grades), l = Boolean(luts);\n  if (g === l) throw new Error(\"Pass exactly one of --grades or --luts\");\n  return g ? \"grades\" : \"luts\";\n}","typeGuard":"function exactlyOneSet(a: unknown, b: unknown): boolean {\n  return Boolean(a) !== Boolean(b);\n}","tryCatchPattern":"try {\n  const parsed = parseGradeCompareArgs(args);\n} catch (err) {\n  if (/Exactly one of/.test((err as Error).message)) {\n    console.error(\"Choose either --grades <file> or --luts <list>, not both/neither.\");\n  }\n  throw err;\n}","preventionTips":["Standardize your team on one mode (--grades) and document a single canonical command shape.","In CI, fail fast if BOTH env-supplied values are non-empty.","Use exactlyOneSet in a pre-flight check when templating commands."],"tags":["cli","input-validation","grade-compare","mutually-exclusive"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}