mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e01659095d | ||
|
|
2cfe48f6f0 |
+2
-17
@@ -13,6 +13,7 @@
|
|||||||
import { readFileSync, existsSync } from 'node:fs';
|
import { readFileSync, existsSync } from 'node:fs';
|
||||||
import { join, dirname, resolve } from 'node:path';
|
import { join, dirname, resolve } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { printUsage } from './usage.mjs';
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
const SKILL_COMMANDS = new Set(['help', 'install', 'link', 'update', 'check']);
|
const SKILL_COMMANDS = new Set(['help', 'install', 'link', 'update', 'check']);
|
||||||
@@ -33,23 +34,7 @@ async function main() {
|
|||||||
const command = args[0];
|
const command = args[0];
|
||||||
|
|
||||||
if (!command || command === '--help' || command === '-h') {
|
if (!command || command === '--help' || command === '-h') {
|
||||||
console.log(`Usage: impeccable <command> [options]
|
printUsage();
|
||||||
|
|
||||||
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);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { createHash } from 'node:crypto';
|
|||||||
import { tmpdir, homedir } from 'node:os';
|
import { tmpdir, homedir } from 'node:os';
|
||||||
import { unzipSync } from 'fflate';
|
import { unzipSync } from 'fflate';
|
||||||
import { getHookConsent, setHookConsent } from '../../lib/impeccable-config.mjs';
|
import { getHookConsent, setHookConsent } from '../../lib/impeccable-config.mjs';
|
||||||
|
import { printUsage } from '../usage.mjs';
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
const API_BASE = 'https://impeccable.style';
|
const API_BASE = 'https://impeccable.style';
|
||||||
@@ -2363,6 +2364,11 @@ async function downloadFile(url, dest, { fetchImpl = globalThis.fetch } = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function update(flags = []) {
|
async function update(flags = []) {
|
||||||
|
if (flags.includes('--help') || flags.includes('-h')) {
|
||||||
|
printUsage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const yes = flags.includes('-y') || flags.includes('--yes');
|
const yes = flags.includes('-y') || flags.includes('--yes');
|
||||||
const force = flags.includes('--force');
|
const force = flags.includes('--force');
|
||||||
const installHooks = !flags.includes('--no-hooks');
|
const installHooks = !flags.includes('--no-hooks');
|
||||||
|
|||||||
@@ -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`);
|
||||||
|
}
|
||||||
@@ -858,12 +858,50 @@ describe('skills install/update: local universal bundle e2e', () => {
|
|||||||
test('root help advertises top-level skills commands', () => {
|
test('root help advertises top-level skills commands', () => {
|
||||||
const output = run('--help');
|
const output = run('--help');
|
||||||
|
|
||||||
expect(output).toContain('install Install impeccable skills');
|
expect(output).toContain('Start with /impeccable');
|
||||||
expect(output).toContain('update Update skills to the latest version');
|
expect(output).toContain('npx impeccable install');
|
||||||
expect(output).toContain('impeccable skills <command> Legacy namespace; still supported.');
|
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');
|
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', () => {
|
test('top-level install aliases the legacy skills install command', () => {
|
||||||
const tmp = mkdtempSync(join(tmpdir(), 'imp-test-top-level-install-'));
|
const tmp = mkdtempSync(join(tmpdir(), 'imp-test-top-level-install-'));
|
||||||
const home = mkdtempSync(join(tmpdir(), 'imp-home-top-level-install-'));
|
const home = mkdtempSync(join(tmpdir(), 'imp-home-top-level-install-'));
|
||||||
|
|||||||
Reference in New Issue
Block a user