Compare commits

...
Author SHA1 Message Date
Abdul WahabandCursor e01659095d Fix: print the docs map on CLI --help (#699)
`impeccable --help` and `update --help` now share one catalog aligned to the docs site, and still return without downloading or writing files.

AI assistance: prepared with Cursor Grok under maintainer direction.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 12:43:52 +05:00
Abdul WahabandCursor 2cfe48f6f0 Fix: treat update --help as read-only (#699)
Print update usage and return before any download, prompt, or skill write.

AI assistance: prepared with Cursor Grok under maintainer direction.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 10:54:43 +05:00
4 changed files with 211 additions and 20 deletions
+2 -17
View File
@@ -13,6 +13,7 @@
import { readFileSync, existsSync } from 'node:fs';
import { join, dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { printUsage } from './usage.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const SKILL_COMMANDS = new Set(['help', 'install', 'link', 'update', 'check']);
@@ -33,23 +34,7 @@ async function main() {
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.`);
printUsage();
process.exit(0);
}
+6
View File
@@ -19,6 +19,7 @@ import { createHash } from 'node:crypto';
import { tmpdir, homedir } from 'node:os';
import { unzipSync } from 'fflate';
import { getHookConsent, setHookConsent } from '../../lib/impeccable-config.mjs';
import { printUsage } from '../usage.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const API_BASE = 'https://impeccable.style';
@@ -2363,6 +2364,11 @@ async function downloadFile(url, dest, { fetchImpl = globalThis.fetch } = {}) {
}
async function update(flags = []) {
if (flags.includes('--help') || flags.includes('-h')) {
printUsage();
return;
}
const yes = flags.includes('-y') || flags.includes('--yes');
const force = flags.includes('--force');
const installHooks = !flags.includes('--no-hooks');
+162
View File
@@ -0,0 +1,162 @@
export function printUsage() {
console.log(`Usage: impeccable <command> [options]
Terminal: npx impeccable ... install, update, detect, ignores
Agent: /impeccable ... every design command
Docs: https://impeccable.style/docs
\`impeccable polish\` in a shell is not polish. Design commands run in the agent.
────────────────────────────────────────────────────────
Start with /impeccable
────────────────────────────────────────────────────────
1. npx impeccable install From the project root, then reload the agent
2. /impeccable init PRODUCT.md (and DESIGN.md when code exists)
3. /impeccable polish the pricing page
────────────────────────────────────────────────────────
Learn
────────────────────────────────────────────────────────
Getting started
npx impeccable install Skill + hook into this project or user home
npx impeccable update Refresh an existing install
npx impeccable check See if a newer skill bundle exists
npx impeccable link --source=.impeccable
Symlink a git checkout / submodule
--providers=claude,cursor,codex,github,gemini,grok,hermes,kiro,
opencode,pi,qoder,trae,trae-cn,rovo-dev,vibe,
veto,antigravity
--scope=project|global --project | --user
-y, --yes Skip prompts
--force Replace hook manifests / existing links
--no-hooks Skills only; do not install or repair hooks
impeccable skills <command> Legacy namespace; still supported
/impeccable pin audit | unpin audit Standalone /audit shortcut (any command)
Reload the agent after install. Then trust the hook in the harness.
Iterate on UI with Live Mode
/impeccable live Pick an element, three variants, accept
into source. Vite, Next, SvelteKit,
Astro, Nuxt. Next includes monorepos.
From a monorepo root, pick the app first (or --target <app>).
Live state lives in <that-app>/.impeccable/live/
Critique with the visual overlay
/impeccable critique UX review in the agent
Chrome extension Same 61 rules as an overlay on any page
────────────────────────────────────────────────────────
Core concepts
────────────────────────────────────────────────────────
Design Context
PRODUCT.md Audience, purpose, platform
DESIGN.md Visual system
.impeccable/design.json Generated sidecar
.impeccable/surfaces/*.md Per-page / per-route briefs
Platform web | ios | android | adaptive
Mode (per surface, not per repo) Persuade | Operate | Read | Experience
More than one app
Found via package.json workspaces, pnpm-workspace.yaml, lerna.json,
or "projectRoots": ["apps/*"] in .impeccable/config.json
Child PRODUCT.md / DESIGN.md wins; missing files inherit the repo root
per file. A nested git repo does not inherit.
--target <app|file|route> on context, live, and doctor.
In a non-monorepo repo, --target still selects a nested product.
From the repo root with no --target: pick an app, then rerun there.
Config and ignores
npx impeccable ignores list
npx impeccable ignores add-rule <id>
npx impeccable ignores add-file <glob>
npx impeccable ignores add-value <rule> <value>
npx impeccable ignores remove-rule | remove-file | remove-value
npx impeccable ignores clear
--shared .impeccable/config.json (default, commit this)
--local .impeccable/config.local.json (gitignored)
--all remove/clear both
--file <glob> --reason <text>
In-file: impeccable-disable | -line | -next-line
projectRoots in config.json (local.json can add private roots; !glob hides)
Keep .gitignore .impeccable rules unanchored so apps/web/.impeccable matches
.impeccable/live/config.json is shared; do commit it
New work
/impeccable Describe a new surface in plain English
Worlds, direction, then build. Replacement looks go through new-work,
not polish-on-the-old-one.
────────────────────────────────────────────────────────
Automation
────────────────────────────────────────────────────────
Detector CLI
npx impeccable detect [file|dir|url...]
--json --quiet --scope type|layout --viewport WxH
--no-config --no-inline-ignores --no-design-system --no-advisory
Workspace files use that app's DESIGN.md, else the repo root's.
Exit codes are CI-safe. Advisory findings never fail the gate.
Design hooks
/impeccable hooks status | on | off
/impeccable hooks ignore-rule | ignore-file | ignore-value
install/update writes the manifest for Claude Code, Copilot, Codex,
Cursor, Grok Build. The harness still has to trust it.
Doctor
/impeccable doctor Context, DESIGN drift, ignores vs live
rules, hook path, workspace table
--json --fix --target <path>
Flags projectRoots globs that match nothing.
────────────────────────────────────────────────────────
Commands (agent: /impeccable <command> [target])
────────────────────────────────────────────────────────
Create
impeccable Next-step menu, or describe the work in plain English
shape Plan UX/UI before code
Evaluate
audit Technical quality, P0-P3
critique UX review, scoring, personas, detector
Refine
animate Purposeful motion
bolder Safe design, more impact
colorize Strategic color
delight Small memorable moments
layout Spacing, rhythm, composition
overdrive Shaders, physics, 60fps, cinematic
quieter Too loud, same intent
typeset Type hierarchy and fonts
Simplify
adapt Screens, devices, platforms
clarify UX copy, labels, errors
distill Strip to essence
Harden
harden Errors, i18n, overflow, edge cases
onboard First-run, empty states, activation
optimize UI performance
polish Last quality pass
System
document DESIGN.md from existing UI
extract Tokens and components into the system
init PRODUCT.md
live Browser variants into source
pin / unpin Standalone /audit (and friends)
teach Same as init
craft Deprecated new-work alias`);
}
+41 -3
View File
@@ -858,12 +858,50 @@ describe('skills install/update: local universal bundle e2e', () => {
test('root help advertises top-level skills commands', () => {
const output = run('--help');
expect(output).toContain('install Install impeccable skills');
expect(output).toContain('update Update skills to the latest version');
expect(output).toContain('impeccable skills <command> Legacy namespace; still supported.');
expect(output).toContain('Start with /impeccable');
expect(output).toContain('npx impeccable install');
expect(output).toContain('npx impeccable update');
expect(output).toContain('impeccable skills <command> Legacy namespace; still supported');
expect(output).toContain('Detector CLI');
expect(output).toContain('Design Context');
expect(output).not.toContain('Full session path');
expect(output).not.toContain('Useful command pairs');
expect(output).not.toContain('skills install Install impeccable skills');
});
test('update --help is read-only and never downloads (#699)', () => {
const missingBundle = join(tmpdir(), 'imp-missing-bundle-699-does-not-exist');
const envBase = { ...process.env, IMPECCABLE_BUNDLE_PATH: missingBundle };
const tmp = mkdtempSync(join(tmpdir(), 'imp-test-update-help-699-'));
const home = mkdtempSync(join(tmpdir(), 'imp-home-update-help-699-'));
createFakeSkills(tmp, ['impeccable'], ['.claude']);
const skillPath = join(tmp, '.claude', 'skills', 'impeccable', 'SKILL.md');
const beforeContent = readFileSync(skillPath, 'utf8');
for (const args of ['update --help', 'update -h', 'skills update --help']) {
const output = run(args, { cwd: tmp, env: { ...envBase, HOME: home } });
expect(output).toContain('Usage: impeccable <command> [options]');
expect(output).toContain('Start with /impeccable');
expect(output).not.toContain('Checking for updates');
expect(output).not.toContain('Updating the');
}
expect(readFileSync(skillPath, 'utf8')).toBe(beforeContent);
rmSync(tmp, { recursive: true, force: true });
rmSync(home, { recursive: true, force: true });
const emptyTmp = mkdtempSync(join(tmpdir(), 'imp-test-update-help-empty-699-'));
const emptyHome = mkdtempSync(join(tmpdir(), 'imp-home-update-help-empty-699-'));
const output = run('update --help', { cwd: emptyTmp, env: { ...envBase, HOME: emptyHome } });
expect(output).toContain('Usage: impeccable <command> [options]');
expect(output).not.toContain('Run `npx impeccable install` to install first.');
expect(output).not.toContain('Checking for updates');
rmSync(emptyTmp, { recursive: true, force: true });
rmSync(emptyHome, { recursive: true, force: true });
});
test('top-level install aliases the legacy skills install command', () => {
const tmp = mkdtempSync(join(tmpdir(), 'imp-test-top-level-install-'));
const home = mkdtempSync(join(tmpdir(), 'imp-home-top-level-install-'));