#!/usr/bin/env node /** * Impeccable CLI * * Usage: * npx impeccable detect [file-or-dir-or-url...] * npx impeccable ignores * npx impeccable help|install|update * npx impeccable --help */ import { readFileSync, existsSync } from 'node:fs'; import { join, dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const SKILL_COMMANDS = new Set(['help', 'install', 'link', 'update', 'check']); // Is this a detect target (the `npx impeccable src/` shorthand) or a mistyped // command? Flags, URLs, path-shaped args, and real files/dirs (e.g. an // extension-less `Dockerfile`) are targets; anything else is an unknown command. function looksLikeDetectTarget(arg) { const isFlag = arg.startsWith('-'); const isUrl = /^https?:\/\//i.test(arg); const isPathShaped = arg.includes('/') || arg.includes('\\') || arg.includes('.'); const isExistingPath = existsSync(resolve(arg)); return isFlag || isUrl || isPathShaped || isExistingPath; } async function main() { const args = process.argv.slice(2); const command = args[0]; if (!command || command === '--help' || command === '-h') { console.log(`Usage: impeccable [options] Commands: detect [file-or-dir-or-url...] Scan for UI anti-patterns and design quality issues ignores Manage detector ignore rules, files, and values help List all available skills and commands install Install impeccable skills into your project or global harness link Symlink skills from a local checkout or submodule update Update skills to the latest version check Check if skill updates are available Options: --help Show this help message --version Show version number Compatibility: impeccable skills Legacy namespace; still supported.`); process.exit(0); } if (command === '--version' || command === '-v') { const pkg = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf8')); console.log(pkg.version); process.exit(0); } if (command === 'detect') { process.argv = [process.argv[0], process.argv[1], ...args.slice(1)]; const { detectCli } = await import('../engine/detect-antipatterns.mjs'); await detectCli(); } else if (command === 'ignores' || command === 'ignore') { const { run } = await import('./commands/ignores.mjs'); await run(args.slice(1)); } else if (command === 'skills') { const { run } = await import('./commands/skills.mjs'); await run(args.slice(1)); } else if (SKILL_COMMANDS.has(command)) { const { run } = await import('./commands/skills.mjs'); await run(args); } else if (looksLikeDetectTarget(command)) { // Default: treat as detect arguments (allow `npx impeccable src/` shorthand) process.argv = [process.argv[0], process.argv[1], ...args]; const { detectCli } = await import('../engine/detect-antipatterns.mjs'); await detectCli(); } else if (command === 'init') { // The follow-up mistake from issue #472: `/impeccable init` belongs in an AI // coding agent's chat, and a user who typed it into their shell is likely to // retry it here as `npx impeccable init`. console.error(`"init" is not a CLI command. Type /impeccable init in your AI coding agent's chat (Claude Code, Cursor, Codex, ...), not in this terminal.`); process.exit(1); } else { // An unknown bareword: a mistyped command (or an old cached version run // against newer docs). Fail loudly instead of silently statting it as a path. console.error(`Unknown command: "${command}"\n\nTo see a list of supported commands, run:\n impeccable --help`); process.exit(1); } } main().catch(error => { if (error?.code === 'IMPECCABLE_PROMPT_ABORT') { console.log('\nAborted.'); process.exit(130); } console.error(error?.message || error); process.exit(1); });