CLI: turn the impeccable npm package into a platform-binary shim

cli/engine, cli/lib, and cli/bin/commands are gone; their behavior lives in
the engine binary. cli/bin/cli.js now resolves the binary from IMPECCABLE_BIN,
the @impeccable/cli-<os>-<arch> optional dependency (templates under
cli/platform-packages/, published by the engine release), the
~/.impeccable/bin/<version>/ cache, or a checksum-verified download, and
execs it. package.json drops the engine dependencies and the library
exports; puppeteer moves to devDependencies for the icon scripts.
README.npm.md describes the shim.

Prepared with AI assistance (Claude Code).
This commit is contained in:
Paul Bakaus
2026-08-31 19:57:42 -07:00
parent 11a1ea64a6
commit 6dd16d21ef
34 changed files with 197 additions and 27746 deletions
+65 -95
View File
@@ -1,102 +1,72 @@
#!/usr/bin/env node
// `impeccable` npm shim: finds the platform binary and execs it with argv.
// Order: $IMPECCABLE_BIN, the @impeccable/cli-<os>-<arch> optional dependency,
// the version-pinned user cache (~/.impeccable/bin/<version>/), then a
// download into that cache from the public release channel.
import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import fs from 'node:fs';
import { createRequire } from 'node:module';
import os from 'node:os';
import path from 'node:path';
/**
* Impeccable CLI
*
* Usage:
* npx impeccable detect [file-or-dir-or-url...]
* npx impeccable ignores <list|add-file|add-value|remove-...>
* npx impeccable help|install|update
* npx impeccable --help
*/
const require = createRequire(import.meta.url);
const pkg = require('../../package.json');
const OS = { darwin: 'darwin', linux: 'linux', win32: 'windows' }[process.platform] || process.platform;
const ARCH = { arm64: 'arm64', x64: 'x64' }[process.arch] || process.arch;
const TARGET = `${OS}-${ARCH}`;
const EXE = OS === 'windows' ? 'impeccable.exe' : 'impeccable';
const PLATFORM_PKG = `@impeccable/cli-${TARGET}`;
// The engine version travels as the pinned optionalDependency range.
const VERSION = String(pkg.optionalDependencies?.[PLATFORM_PKG] || Object.values(pkg.optionalDependencies || {})[0] || '').replace(/^[^\d]*/, '');
const CACHE_ROOT = process.env.IMPECCABLE_HOME || path.join(os.homedir(), '.impeccable');
const CACHED = path.join(CACHE_ROOT, 'bin', VERSION, EXE);
const BASE = (process.env.IMPECCABLE_DOWNLOAD_BASE || 'https://github.com/renaissance-geek-inc/impeccable-dist/releases/download').replace(/\/$/, '');
const URL = `${BASE}/v${VERSION}/impeccable-${TARGET}${OS === 'windows' ? '.exe' : ''}`;
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;
function exists(p) { try { return !!p && fs.statSync(p).isFile(); } catch { return false; } }
function fromPackage() {
try { return path.join(path.dirname(require.resolve(`${PLATFORM_PKG}/package.json`)), 'bin', EXE); } catch { return null; }
}
async function download() {
if (!VERSION) return null;
const res = await fetch(URL, { redirect: 'follow' });
if (!res.ok) return null;
const buf = Buffer.from(await res.arrayBuffer());
const sum = await fetch(`${URL}.sha256`, { redirect: 'follow' }).then(r => (r.ok ? r.text() : ''), () => '');
const expected = sum.trim().split(/\s+/)[0];
if (expected && createHash('sha256').update(buf).digest('hex') !== expected) {
throw new Error(`checksum mismatch downloading ${URL}`);
}
fs.mkdirSync(path.dirname(CACHED), { recursive: true });
const tmp = `${CACHED}.part.${process.pid}`;
fs.writeFileSync(tmp, buf, { mode: 0o755 });
fs.renameSync(tmp, CACHED);
return CACHED;
}
async function locate() {
const envBin = process.env.IMPECCABLE_BIN;
if (exists(envBin)) return envBin;
const fromPkg = fromPackage();
if (exists(fromPkg)) return fromPkg;
if (exists(CACHED)) return CACHED;
return download().catch((err) => { process.stderr.write(`impeccable: ${err.message}\n`); return null; });
}
async function main() {
const args = process.argv.slice(2);
const command = args[0];
if (!command || command === '--help' || command === '-h') {
console.log(`Usage: impeccable <command> [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 <command> 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);
}
const bin = await locate();
if (!bin) {
process.stderr.write(
`impeccable: no binary for ${TARGET}. Install ${PLATFORM_PKG}@${VERSION}, set IMPECCABLE_BIN, `
+ `or download impeccable-${TARGET} v${VERSION} from ${BASE} into ${CACHED}.\n`,
);
process.exit(127);
}
main().catch(error => {
if (error?.code === 'IMPECCABLE_PROMPT_ABORT') {
console.log('\nAborted.');
process.exit(130);
}
console.error(error?.message || error);
process.exit(1);
const result = spawnSync(bin, process.argv.slice(2), {
stdio: 'inherit',
env: { IMPECCABLE_SELF: 'npx impeccable', ...process.env },
});
if (result.error) {
process.stderr.write(`impeccable: failed to run ${bin}: ${result.error.message}\n`);
process.exit(127);
}
process.exit(result.status === null ? 1 : result.status);