mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-13 06:36:26 +03:00
* CLI: --version reports the npm package version npx impeccable --version printed 3.6.0 after the 4.0.0 release because the shim handed every argument to the engine, whose baked-in CLI_VERSION still said 3.6.0. The shim now answers --version and -v from its own package.json, as docs/CLI-CONTRACT.md specifies, without locating or downloading a binary; a test covers both flags. The engine's CLI_VERSION moves to 4.0.0 for the next engine release, with the cli-version golden re-recorded and the delta noted. Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY * CLI shim: --version wins whenever it leads, trailing arguments or not Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY --------- Co-authored-by: Claude Code <noreply@anthropic.com>
137 lines
6.1 KiB
Rust
137 lines
6.1 KiB
Rust
//! `impeccable` binary: verb router.
|
|
//!
|
|
//! Every skill script and CLI subcommand is a verb here. Verb crates expose
|
|
//! `run(args: &[String], io: &mut Io) -> i32` (exit code) and never call
|
|
//! `std::process::exit` themselves, so this file is the single place exit codes
|
|
//! and stream flushing are decided (contract: docs/CLI-CONTRACT.md in the
|
|
//! public repo). Verb names are the JS script basenames; a few carry aliases
|
|
//! (`signals` for context-signals, `hooks` for hook-admin).
|
|
|
|
use std::io::Write;
|
|
|
|
use impeccable_common::Io;
|
|
|
|
mod font_render;
|
|
|
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
|
|
fn main() {
|
|
let args: Vec<String> = std::env::args().skip(1).collect();
|
|
let mut io = Io::stdio();
|
|
let code = run(&args, &mut io);
|
|
let _ = io.stdout.flush();
|
|
let _ = io.stderr.flush();
|
|
std::process::exit(code);
|
|
}
|
|
|
|
fn run(args: &[String], io: &mut Io) -> i32 {
|
|
// cli/bin/cli.js dispatch: help / version / detect / ignores / skills verbs
|
|
let Some(verb) = args.first().map(String::as_str) else {
|
|
io.out(impeccable_detect::ROOT_USAGE);
|
|
return 0;
|
|
};
|
|
let rest = &args[1..];
|
|
match verb {
|
|
"--help" | "-h" => {
|
|
io.out(impeccable_detect::ROOT_USAGE);
|
|
0
|
|
}
|
|
"--version" | "-v" => {
|
|
io.out(&format!("{CLI_VERSION}\n"));
|
|
0
|
|
}
|
|
// Launcher handshake: a cheap discriminator so the launchers can tell
|
|
// this engine apart from the retired 3.x npm CLI (which answers any
|
|
// unknown verb with `Unknown command`, exit 1) before exec'ing a
|
|
// candidate found on PATH or in the unversioned user cache. Kept out
|
|
// of --help on purpose; not part of the user-facing contract.
|
|
"engine-probe" => {
|
|
io.out(&format!("impeccable-engine {VERSION}\n"));
|
|
0
|
|
}
|
|
"detect" => impeccable_detect::run_detect(rest, io, &engines()),
|
|
"ignores" | "ignore" => impeccable_detect::run_ignores(rest, io),
|
|
"skills" => impeccable_skills::run(rest, io),
|
|
"help" | "install" | "link" | "update" | "check" => impeccable_skills::run(args, io),
|
|
// skill scripts
|
|
"context" => impeccable_context::run_context(rest, io),
|
|
"pin" => impeccable_context::run_pin(rest, io),
|
|
"detect-csp" => impeccable_context::run_detect_csp(rest, io),
|
|
"palette" => impeccable_context::run_palette(rest, io),
|
|
"surface-brief" => impeccable_context::run_surface_brief(rest, io),
|
|
"critique-storage" => impeccable_context::run_critique_storage(rest, io),
|
|
"embed-prompt" => impeccable_context::run_embed_prompt(rest, io),
|
|
"signals" | "context-signals" => impeccable_context::run_signals(rest, io),
|
|
"doctor" => impeccable_context::run_doctor(rest, io),
|
|
"concept-seed" => impeccable_context::run_concept_seed(rest, io),
|
|
"generate-image" => impeccable_context::run_generate_image(rest, io),
|
|
"serve-question" => impeccable_context::run_serve_question(rest, io),
|
|
// comp-fidelity verbs (crates/comp-verbs over crates/comp)
|
|
"comp-spec" => impeccable_comp_verbs::run_comp_spec(rest, io),
|
|
"comp-diff" => impeccable_comp_verbs::run_comp_diff(rest, io),
|
|
"font-match" => {
|
|
let mut renderer = font_render::CdpFontRenderer::from_process_env();
|
|
impeccable_comp_verbs::run_font_match(rest, io, &mut renderer)
|
|
}
|
|
"build-phase" => {
|
|
// Inject the organic-clip-path CSS scanner (a rule that lives in the
|
|
// closed `core` crate) so comp-verbs stays core-free.
|
|
let organic = |html: &str| -> Vec<(Option<String>, String)> {
|
|
impeccable_core::checks::css_scan::scan_css_text_for_organic_clip_path(html)
|
|
.into_iter()
|
|
.map(|f| (f.selector, f.snippet))
|
|
.collect()
|
|
};
|
|
impeccable_comp_verbs::run_build_phase(rest, io, &organic)
|
|
}
|
|
"hook" => impeccable_hook::run_hook(rest, io, engines().html),
|
|
"hook-before-edit" => impeccable_hook::run_hook_before_edit(rest, io, engines().html),
|
|
"hooks" | "hook-admin" => impeccable_hook::run_hook_admin(rest, io),
|
|
v if v.starts_with("live") => impeccable_live::run(v, rest, io),
|
|
// `npx impeccable src/` shorthand: a path-shaped, flag, URL, or existing
|
|
// first arg is a detect target (cli.js looksLikeDetectTarget).
|
|
v if impeccable_detect::looks_like_detect_target(v, &io.cwd.to_string_lossy()) => {
|
|
impeccable_detect::run_detect(args, io, &engines())
|
|
}
|
|
"init" => {
|
|
io.err(impeccable_detect::INIT_MESSAGE);
|
|
1
|
|
}
|
|
other => {
|
|
io.err(&format!(
|
|
"Unknown command: \"{other}\"\n\nTo see a list of supported commands, run:\n impeccable --help\n"
|
|
));
|
|
1
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The npm `impeccable` package version `cli.js --version` prints (its
|
|
/// `package.json`), tracked separately from the crate version.
|
|
pub const CLI_VERSION: &str = "4.0.0";
|
|
|
|
/// The engines wired into `impeccable detect`: the static HTML engine
|
|
/// (crates/html). The browser engine (crates/browser) plugs in here once it
|
|
/// lands; until then URL scans report the puppeteer message.
|
|
fn engines() -> impeccable_detect::Engines<'static> {
|
|
static HTML: impeccable_html::StaticHtmlEngine = impeccable_html::StaticHtmlEngine {
|
|
// The shipped binary carries the built-in rules only.
|
|
static_rule_pack: None,
|
|
};
|
|
impeccable_detect::Engines {
|
|
html: &HTML,
|
|
url: Some(url_engine()),
|
|
}
|
|
}
|
|
|
|
// --- browser engine (crates/browser) -------------------------------------
|
|
/// The URL engine, built once from the process environment (browser
|
|
/// discovery reads `IMPECCABLE_BROWSER` / `PUPPETEER_EXECUTABLE_PATH` /
|
|
/// `CHROME_PATH`, sandbox flags read `CI`).
|
|
fn url_engine() -> &'static impeccable_browser::BrowserEngine {
|
|
static ENGINE: std::sync::OnceLock<impeccable_browser::BrowserEngine> =
|
|
std::sync::OnceLock::new();
|
|
ENGINE.get_or_init(impeccable_browser::BrowserEngine::from_process_env)
|
|
}
|
|
// -------------------------------------------------------------------------
|