{"record":{"id":"859bc8836324a43b","repo":"heygen-com/hyperframes","slug":"need-2-paths-to-compare","errorCode":null,"errorMessage":"need 2+ paths to compare","messagePattern":"need 2\\+ paths to compare","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/commands/compare.ts","lineNumber":132,"sourceCode":"function defaultCompareCols(cellCount: number): number {\n  return Math.max(1, Math.min(MAX_COLUMNS, Math.ceil(Math.sqrt(cellCount))));\n}\n\nexport function parseCompareArgs(\n  args: {\n    _?: readonly unknown[];\n    labels?: unknown;\n    out?: unknown;\n    at?: unknown;\n    cols?: unknown;\n    json?: unknown;\n    timeout?: unknown;\n  },\n  cwd = process.cwd(),\n): ParsedCompareArgs {\n  const paths = parsePathArgs(args);\n  if (paths.length < 2) {\n    throw new Error(\"need 2+ paths to compare\");\n  }\n\n  const labels = parseLabels(args.labels, paths.length);\n  const variants = paths.map((input, index) => ({\n    label: labels?.[index] ?? defaultLabelForPath(input),\n    inputPath: resolveFromBase(cwd, input),\n    displayPath: displayPathFromInput(cwd, input),\n  }));\n\n  return {\n    variants,\n    outPath: resolveFromBase(cwd, readOptionalString(args.out) ?? \"compare.png\"),\n    atSeconds: parseAtSeconds(args.at),\n    cols: parseColumns(args.cols),\n    json: args.json === true,\n    timeoutMs:\n      Number.parseInt(readOptionalString(args.timeout) ?? \"\", 10) ||\n      DEFAULT_RENDER_READY_TIMEOUT_MS,","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/commands/compare.ts#L114-L150","documentation":"Thrown by parseCompareArgs() when fewer than 2 positional composition paths survive parsePathArgs. A comparison sheet is meaningless with one cell, so the command refuses rather than rendering a single-frame image. parsePathArgs keeps only string positionals, trims them, and drops empties, so the count can shrink below what was typed.","triggerScenarios":"Running `hyperframes compare` with zero positionals; with one positional (`compare ./a`); or with positionals that are non-string/empty-after-trim (e.g. a shell glob that expanded to nothing, or quoted empty strings).","commonSituations":"Forgetting the second variant path; a glob like `./variants/*` that matched nothing because the directory is empty or the cwd is wrong; quoting mistakes that collapse args.","solutions":["Pass two or more composition paths (directories with index.html or .html files)","If using a glob, verify it expands: run `echo ./variants/*` first","Quote each path separately rather than as one combined argument"],"exampleFix":"// before\nhyperframes compare ./variants/a\n// after\nhyperframes compare ./variants/a ./variants/b","handlingStrategy":"validation","validationCode":"const paths = (args._ ?? []).filter((v): v is string => typeof v === 'string').map(v => v.trim()).filter(Boolean);\nif (paths.length < 2) {\n  throw new Error(`need 2+ paths to compare (got ${paths.length})`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Expand globs in your shell and echo them before running compare","Quote each composition path as its own argument","Reject empty-string positionals upstream so they don't silently shrink the count"],"tags":["cli-args","validation","compare","positional"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}