{"record":{"id":"6f0d78eb5fc70cca","repo":"JuliusBrussee/caveman","slug":"project-name-must-contain-a-letter-or-number","errorCode":null,"errorMessage":"project name must contain a letter or number","messagePattern":"project name must contain a letter or number","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/create-caveman-agent/src/index.ts","lineNumber":307,"sourceCode":"`,\n  };\n}\n\nfunction credentialName(provider: Provider): string {\n  if (provider === \"anthropic\") return \"ANTHROPIC_API_KEY\";\n  if (provider === \"openai\") return \"OPENAI_API_KEY\";\n  return \"GEMINI_API_KEY (or GOOGLE_API_KEY)\";\n}\n\nfunction parseProvider(value: string): Provider {\n  const normalized = value.trim().toLowerCase();\n  if (normalized === \"anthropic\" || normalized === \"openai\" || normalized === \"google\") return normalized;\n  throw new Error(`unsupported provider ${JSON.stringify(value)}`);\n}\n\nfunction safeName(value: string): string {\n  const normalized = value.toLowerCase().replace(/[^a-z0-9_-]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n  if (!normalized) throw new Error(\"project name must contain a letter or number\");\n  return normalized.slice(0, 96);\n}\n\nasync function assertAbsent(path: string): Promise<void> {\n  try {\n    await stat(path);\n    throw new Error(`target already exists: ${path}`);\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n  }\n}\n\nmain().catch((error) => {\n  const message = error instanceof Error ? error.message : String(error);\n  process.stderr.write(`create-caveman-agent: ${message}\\n`);\n  process.exitCode = 1;\n});\n","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/create-caveman-agent/src/index.ts#L289-L325","documentation":"safeName lowercases the project name, collapses every run of characters outside [a-z0-9_-] into '-', trims leading/trailing dashes, and then requires a non-empty result. A name made entirely of symbols/spaces/unicode (e.g. '---', '日本語', '!!!') normalizes to the empty string, which cannot serve as a package/directory name, so it throws.","triggerScenarios":"Passing a target like `--`, `...`, `©©`, or any name whose lowercase form contains no ASCII letters, digits, underscore, or hyphen.","commonSituations":"Non-Latin project names, accidental shell expansions producing punctuation, paste of emoji/decorative names, or a name of only dots that collapses to nothing.","solutions":["Choose a name containing at least one ASCII letter or digit (other characters become separators).","For non-English names, add a Latin prefix/suffix, e.g. `日本語-app` → normalizes to `-app` → trimmed to `app`; prefer `nihongo-app`.","Keep the final name under the 96-char cap applied after normalization."],"exampleFix":"# before\nnpm create @caveman-ai/agent@latest \"🚀!!!\"\n# after\nnpm create @caveman-ai/agent@latest rocket-app","handlingStrategy":"validation","validationCode":"function normalizesToNonEmpty(name: string): boolean {\n  const n = name.toLowerCase().replace(/[^a-z0-9_-]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n  return n.length > 0;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Choose project names with at least one ASCII letter or digit.","Remember punctuation collapses to hyphens — symbols-only names are invalid.","Keep normalized names within 96 characters."],"tags":["cli","validation","naming"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}