Python Scripts Reference
All scripts use Python 3 stdlib only — no pip dependencies. They follow one of two contract families.
Contract families
Section titled “Contract families”Detector contract
Section titled “Detector contract”Used when a slash command needs to parse the script’s output. The script:
- Writes JSON to stdout
- Returns a
reasonsarray in the JSON - Exits 0 (match/success) or 1 (no match/failure)
- Writes nothing to stderr on success
Generator/Validator contract
Section titled “Generator/Validator contract”Used for pipeline transformers and human-readable validators. The script:
- Writes data/CSS to stdout (or a file)
- Writes error messages to stderr
- Exits 0 (success), 1 (validation failure), or 2 (IO/usage error)
acss-kit scripts
Section titled “acss-kit scripts”acss-kit detection scripts
Section titled “acss-kit detection scripts”The /setup, /kit-add, and /kit-create workflows lean on four small detector scripts. All four follow the Detector contract (JSON to stdout, reasons array, exit 0 on match / 1 on no-match).
detect_stack.py
Section titled “detect_stack.py”Location: plugins/acss-kit/scripts/detect_stack.py
Inspects package.json to confirm the project is a viable React + TypeScript target and surfaces the React major version.
Inputs:
python3 detect_stack.py [--project-root=<path>]Output (stdout):
{ "react": { "present": true, "version": "18.3.1", "major": 18 }, "typescript": { "present": true, "version": "5.4.5" }, "sass": { "present": true, "package": "sass" }, "reasons": ["Detected React 18 + TypeScript 5 + sass"]}Exit codes: 0 = stack viable, 1 = required dep missing.
detect_package_manager.py
Section titled “detect_package_manager.py”Location: plugins/acss-kit/scripts/detect_package_manager.py
Identifies the project’s package manager from the packageManager field, lockfile presence, and (as a fallback) the engines field.
Inputs:
python3 detect_package_manager.py [--project-root=<path>]Output (stdout):
{ "manager": "pnpm", "version": "9.4.0", "source": "packageManager", "addCommand": "pnpm add -D", "reasons": ["Read packageManager field: pnpm@9.4.0"]}source values: "packageManager", "lockfile", "engines", "default".
Exit codes: 0 = detected, 1 = no signal (defaults to npm).
detect_target.py
Section titled “detect_target.py”Location: plugins/acss-kit/scripts/detect_target.py
Resolves the components output directory. Reads .acss-target.json first, then falls back to src/components/fpkit/, then to a heuristic on src/.
Inputs:
python3 detect_target.py [--project-root=<path>]Output (stdout):
{ "source": "configured", "componentsDir": "src/components/fpkit/", "exists": true, "reasons": ["Found .acss-target.json with componentsDir"]}source values: "configured", "default", "heuristic", "none".
Exit codes: 0 = directory resolved, 1 = no usable target.
verify_integration.py
Section titled “verify_integration.py”Location: plugins/acss-kit/scripts/verify_integration.py
Run after /setup (and any time afterwards) to confirm the project still wires acss-kit correctly: ui.tsx is present, theme files exist, theme imports are reachable from the entry stylesheet, and .acss-target.json matches reality.
Inputs:
python3 verify_integration.py [--project-root=<path>] [--strict]Output (stdout):
{ "ok": true, "checks": { "uiTsx": "ok", "themeFiles": "ok", "themeImports": "ok", "acssTarget": "ok" }, "reasons": ["All integration points verified"]}Exit codes: 0 = all checks pass, 1 = one or more checks fail (--strict also fails on warnings).
generate_color_scale.py
Section titled “generate_color_scale.py”Contract: Generator
Location: plugins/acss-kit/scripts/generate_color_scale.py
Generates a 10-step OKLCH color scale (steps 50–900) from a seed hex color. Used by /color-scale.
Inputs (CLI arguments):
python3 generate_color_scale.py <hex-color> [--name=<name>] [--format=css|json|both]| Argument | Required | Description |
|---|---|---|
hex-color | Yes | Seed color as #rrggbb or #rgb |
--name | No | CSS variable prefix, kebab-case (default: scale) |
--format | No | css, json, or both (default: both) |
Output (stdout): JSON block then CSS block, separated by a blank line (when --format=both).
JSON shape:
{ "name": "primary", "seed": "#4f46e5", "seed_oklch": { "L": 0.4823, "C": 0.2108, "H": 264.05 }, "steps": [ { "step": 50, "hex": "#f5f3ff", "oklch": "oklch(0.965 0.0213 264.1)", "css_var": "--color-primary-50", "clamped": false, "reasons": [] }, { "step": 900, "hex": "#1e1b4b", "oklch": "oklch(0.141 0.0812 264.1)", "css_var": "--color-primary-900", "clamped": true, "reasons": ["P3 gamut exceeded at chroma 0.24; clamped to sRGB boundary"] } ]}CSS output uses the var(--x, <fallback>) convention required by acss-kit.
The scale is generated from a single seed color — below is an example of the primary role that /color-scale would expand into a 10-step scale:
--color-primary
—
Exit codes: 0 = success, 1 = invalid hex argument, 2 = IO/usage error
wrap_foundation_layer.py
Section titled “wrap_foundation_layer.py”Contract: Generator
Location: plugins/acss-kit/scripts/wrap_foundation_layer.py
Second step of the foundation.css refresh pipeline. Wraps a compiled Sass output in @layer foundation { }, prepends the canonical file header (including upstream SHA pin and patch list), and appends the P3 prefers-reduced-motion patch.
Run after compiling with Sass:
npx sass --style=expanded --no-source-map \ plugins/acss-kit/assets/foundation/sass/_index.scss:_foundation_raw.csspython3 plugins/acss-kit/scripts/wrap_foundation_layer.py \ _foundation_raw.css plugins/acss-kit/assets/foundation/foundation.cssInputs:
python3 wrap_foundation_layer.py <raw_css_in> <foundation_css_out>Output: Writes <foundation_css_out> with:
- A file header comment declaring the upstream SHA and patches P1–P4
@layer foundation, components, utilities, theme;cascade order declaration- All input CSS wrapped in
@layer foundation { } - P3
prefers-reduced-motionblock appended
Exit codes: 0 = success, 2 = usage/IO error
generate_palette.py
Section titled “generate_palette.py”Contract: Generator
Location: plugins/acss-kit/scripts/generate_palette.py
Converts a hex seed color into a full semantic role palette using OKLCH math and WCAG 2.2 AA validation.
Inputs (CLI arguments):
python3 generate_palette.py <hex-color> [--mode=light|dark|both|brand]| Argument | Required | Description |
|---|---|---|
hex-color | Yes | 6-digit hex (e.g., #4f46e5) |
--mode | No | light (default), dark, both, or brand |
Output (stdout): JSON palette object
{ "mode": "both", "seed": "#4f46e5", "seed_oklch": [0.55, 0.22, 270], "light": { "--color-background": "oklch(99% 0.004 270)", "--color-primary": "oklch(55% 0.22 270)", "--color-danger": "oklch(50% 0.22 27)", "...": "18 roles total" }, "dark": { "--color-background": "oklch(12% 0.008 270)", "--color-primary": "oklch(72% 0.18 270)", "...": "18 roles total" }, "wcag": { "light": { "all_pass": true, "pairs": { "text/background": 14.2 } }, "dark": { "all_pass": true, "pairs": { "text/background": 12.8 } } }}Exit codes: 0 = success, 1 = WCAG failure after all retries, 2 = invalid input
css_to_tokens.py
Section titled “css_to_tokens.py”Contract: Generator
Location: plugins/acss-kit/scripts/css_to_tokens.py
Extracts --color-* declarations from CSS theme files and emits a JSON token object.
Inputs:
python3 css_to_tokens.py <file.css> [file2.css ...]Output (stdout): JSON conforming to assets/theme.schema.json
{ "light": { "--color-background": "oklch(99% 0.004 270)", "...": "..." }, "dark": { "--color-background": "oklch(12% 0.008 270)", "...": "..." }, "brand": {}}Features: Resolves var() references recursively (up to 5 levels deep). Reports unresolvable references to stderr.
Exit codes: 0 = success, 2 = IO/usage error
tokens_to_css.py
Section titled “tokens_to_css.py”Contract: Generator
Location: plugins/acss-kit/scripts/tokens_to_css.py
Converts a JSON palette (from generate_palette.py) to CSS theme files.
Inputs:
python3 tokens_to_css.py <palette.json> --out-light=<path> --out-dark=<path>Output: CSS files written to --out-light and --out-dark paths.
Features:
- Emits
:rootblock for light,[data-theme="dark"]for dark - Adds section comment headers (
/* ── Backgrounds ── */) - Sorts roles in canonical order (backgrounds → borders → text → brand → semantic)
- Handles paired-role updates (primary + primary-hover kept together)
Exit codes: 0 = success, 2 = IO/usage error
validate_theme.py
Section titled “validate_theme.py”Contract: Validator
Location: plugins/acss-kit/scripts/validate_theme.py
Validates WCAG 2.2 AA contrast ratios for all 10 semantic role pairs in a theme CSS file.
Inputs:
python3 validate_theme.py <theme.css># or validate a directory:python3 validate_theme.py src/styles/theme/Output (stdout on failure):
WCAG validation — src/styles/theme/light.css
PASS text / background: 14.2 : 1 PASS primary / background: 5.8 : 1 FAIL danger / background: 2.4 : 1 (minimum 4.5:1)
1 failure. Exit code: 1Exit codes:
- 0 — all pairs pass
- 1 — one or more pairs fail
- 2 — file not found or parse error
oklch_shift.py
Section titled “oklch_shift.py”Contract: Generator
Location: plugins/acss-kit/scripts/oklch_shift.py
Applies OKLCH deltas to a hex color and returns the resulting hex.
Inputs:
python3 oklch_shift.py <hex-color> [--hue=<delta>] [--chroma=<multiplier>] [--lightness=<delta>]| Flag | Type | Description |
|---|---|---|
--hue | float (degrees) | Hue offset (e.g., +20, -15) |
--chroma | float (multiplier) | Chroma multiplier (e.g., 1.2 = 20% more saturated) |
--lightness | float (percentage points) | Lightness offset (e.g., +5, -3) |
Output (stdout): Resulting hex color string
#6040e5Stderr (on gamut clamp):
Warning: significant gamut clamping (>5%) on blue channel. Result may differ from expected.Exit codes: 0 = success, 1 = invalid input, 2 = severe gamut clamping (> 20%)
acss-kit kit-sync scripts
Section titled “acss-kit kit-sync scripts”The /kit-sync and /kit-update commands use four scripts to manage the manifest at .acss-kit/manifest.json. All four follow the Detector contract where applicable (JSON to stdout, reasons array, exit 0/1) or the Generator contract for write operations.
hash_file.py
Section titled “hash_file.py”Contract: Generator
Location: plugins/acss-kit/scripts/hash_file.py
Computes a normalized SHA256 hash of a file or stdin content using the kit-sync normalization rules: LF endings, trailing whitespace stripped per line, trailing blank lines collapsed to one. Used by /kit-sync before recording a file in the manifest and by diff_status.py when comparing on-disk content.
Inputs:
python3 hash_file.py --path <path># or via stdin:cat content | python3 hash_file.py --stdinOutput (stdout):
{ "path": "/abs/path/to/file", "sha256": "<hex>", "normalizedBytes": 1234, "reasons": []}Exit codes: 0 = success, 1 = file not found (--path), 2 = IO/usage error
manifest_read.py
Section titled “manifest_read.py”Contract: Detector
Location: plugins/acss-kit/scripts/manifest_read.py
Reads .acss-kit/manifest.json from a project root and emits its full contents as JSON. Used by both /kit-sync (re-sync detection) and /kit-update (drift baseline).
Inputs:
python3 manifest_read.py [project_root]Output (stdout):
{ "exists": true, "path": "/abs/.acss-kit/manifest.json", "schemaVersion": 1, "pluginVersion": "0.9.0", "targetDir": "src/components/fpkit", "stylesDir": "src/styles", "themeFile": "acss-kit.theme.json", "generatedAt": "2026-05-03T14:22:11Z", "files": { "<rel-path>": { "source": "...", "sha256": "...", "kind": "..." } }, "reasons": []}When the manifest is missing:
{ "exists": false, "path": "/abs/.acss-kit/manifest.json", "files": {}, "reasons": ["Manifest not found. Run /kit-sync first."]}Exit codes: 0 = manifest exists and parsed, 1 = manifest missing or invalid
manifest_write.py
Section titled “manifest_write.py”Contract: Generator
Location: plugins/acss-kit/scripts/manifest_write.py
Atomically writes or merges .acss-kit/manifest.json. Reads a JSON payload from stdin describing entries to upsert, merges with any existing manifest (preserving entries not in the payload), and writes atomically (write-temp + rename). Used at the end of both the /kit-sync bulk install and /kit-update safe-update workflows.
Stdin payload shape:
{ "projectRoot": "/abs/path", "pluginVersion": "0.9.0", "targetDir": "src/components/fpkit", "stylesDir": "src/styles", "themeFile": "acss-kit.theme.json", "files": [ { "path": "src/components/fpkit/button.tsx", "sha256": "<hex>", "kind": "component", "source": "ref:components/button.md#tsx-template", "component": "button" } ], "removePaths": []}Output (stdout):
{ "ok": true, "path": "/abs/.acss-kit/manifest.json", "filesWritten": 5, "filesRemoved": 0, "reasons": []}Exit codes: 0 = success, 1 = malformed payload, 2 = IO error
diff_status.py
Section titled “diff_status.py”Contract: Detector
Location: plugins/acss-kit/scripts/diff_status.py
Computes drift between .acss-kit/manifest.json and the current on-disk state. Classifies every tracked file as clean (matches recorded hash), modified (hash differs), or missing (deleted). Used by /kit-update to determine which files to overwrite, skip, or recreate.
Inputs:
python3 diff_status.py [project_root]Output (stdout):
{ "ok": true, "projectRoot": "/abs/path", "manifestPath": "/abs/.acss-kit/manifest.json", "clean": [{ "path": "...", "kind": "...", "component": "..." }], "modified": [ { "path": "...", "kind": "...", "currentSha": "...", "expectedSha": "..." } ], "missing": [{ "path": "...", "kind": "..." }], "totals": { "clean": 3, "modified": 1, "missing": 0 }, "reasons": []}Exit codes: 0 = diff computed (regardless of drift counts), 1 = manifest missing or unreadable
acss-kit HTML scripts
Section titled “acss-kit HTML scripts”detect_html_target.py
Section titled “detect_html_target.py”Contract: Detector
Location: plugins/acss-kit/scripts/detect_html_target.py
Detects the target directory for static-HTML component output. Unlike detect_target.py (which requires a React project), this script works with any project type — static sites, server-rendered apps, or projects with no package.json. Reads .acss-html-target.json at the project root.
Inputs:
python3 detect_html_target.py [project_root]Output (stdout):
{ "source": "configured", "projectRoot": "/abs/path", "componentsHtmlDir": "components/html", "foundationPresent": true, "reasons": []}source values: "configured" (.acss-html-target.json found), "none" (no config file present; first-run prompt needed).
foundationPresent — whether _stateful.js has been copied to the target directory yet.
Exit codes: 0 = configured (with or without foundation file), 1 = not configured
verify_html_integration.py
Section titled “verify_html_integration.py”Contract: Detector
Location: plugins/acss-kit/scripts/verify_html_integration.py
Checks that every .scss/.css and .js artifact generated by /kit-add-html is referenced by at least one page in the project. .html snippet files are listed but not checked (they are copy-paste fragments, not entrypoints). Reads .acss-html-target.json for componentsHtmlDir.
Inputs:
python3 verify_html_integration.py [project_root]Output (stdout):
{ "ok": true, "projectRoot": "/abs/path", "componentsHtmlDir": "components/html", "checks": [ { "artifact": "button.scss", "kind": "style", "imported": true }, { "artifact": "dialog.js", "kind": "script", "imported": true }, { "artifact": "button.html", "kind": "snippet", "imported": null } ], "reasons": []}When references are missing, ok is false and reasons contains suggested <link>/<script> snippets.
Exit codes: 0 = all artifacts referenced (or only snippets), 1 = missing references
acss-kit setup scripts
Section titled “acss-kit setup scripts”detect_css_entry.py
Section titled “detect_css_entry.py”Contract: Detector
Location: plugins/acss-kit/scripts/detect_css_entry.py
Used by /setup Step 7.5 to find the project’s main CSS/SCSS entry file so theme @import lines can be appended idempotently. Walks a fixed candidate list in priority order and, for each file found, scans for existing imports of the theme/utility artifacts by basename.
Inputs:
python3 detect_css_entry.py [project_root]Output (stdout):
At least one candidate found (exit 0):
{ "source": "detected", "projectRoot": "/abs/path", "candidates": [ { "path": "src/styles/index.scss", "imports": { "light.css": 3, "dark.css": null, "token-bridge.css": null, "utilities.css": null } } ], "reasons": []}In the imports map, a non-null value is the 1-based line number where that basename appears on an import-bearing line. null means the file is not yet imported.
No candidate found (exit 1):
{ "source": "none", "projectRoot": "/abs/path", "candidates": [], "reasons": [ "No CSS or SCSS entry file found at any of the standard locations." ]}Exit codes: 0 = at least one candidate found, 1 = no candidate
acss-utilities scripts
Section titled “acss-utilities scripts”generate_utilities.py
Section titled “generate_utilities.py”Contract: Generator
Location: plugins/acss-utilities/scripts/generate_utilities.py
Generates the full utility CSS bundle from utilities.tokens.json.
Inputs:
python3 generate_utilities.py [--tokens=<path>] [--out-dir=<dir>] [--families=<list>]| Flag | Default | Description |
|---|---|---|
--tokens | assets/utilities.tokens.json | Token source file |
--out-dir | assets/utilities/ | Directory for per-family partials |
--families | All enabled families | Comma-separated override |
Output: Per-family CSS partials + concatenated assets/utilities.css
Exit codes: 0 = success, 1 = budget exceeded, 2 = IO/usage error
validate_utilities.py
Section titled “validate_utilities.py”Contract: Validator
Location: plugins/acss-utilities/scripts/validate_utilities.py
Validates the committed utility bundle for integrity, bridge parity, and idempotency.
Checks:
- Bundle integrity — valid CSS syntax
- Bridge parity — every
:rootalias also in[data-theme="dark"] - Idempotency — regenerated bundle matches committed bundle byte-for-byte
Inputs:
python3 validate_utilities.py [--bundle=<path>] [--bridge=<path>]Exit codes:
- 0 — all checks pass
- 1 — integrity or parity failure
- 2 — IO/usage error
detect_utility_target.py
Section titled “detect_utility_target.py”Contract: Detector
Location: plugins/acss-utilities/scripts/detect_utility_target.py
Detects the target styles directory for utility output.
Inputs:
python3 detect_utility_target.py [--project-root=<path>]Output (stdout): JSON
{ "source": "configured", "utilitiesDir": "src/styles/", "reasons": ["Found .acss-target.json with utilitiesDir: src/styles/"]}source values: "configured" (.acss-target.json found), "default" (src/styles/ heuristic), "none" (no directory found)
Exit codes: 0 = directory found, 1 = no directory found
migrate_classnames.py
Section titled “migrate_classnames.py”Contract: Generator/Validator
Location: plugins/acss-utilities/scripts/migrate_classnames.py
Migrates acss-utilities class names from the 0.1.x colon form (sm:hide, md:p-6) to the 0.2.0 hyphen form (sm-hide, md-p-6) in source files. By default performs a dry run and prints a unified diff per modified file. Pass --write to apply changes in place.
Inputs:
python3 migrate_classnames.py <path> [<path>...] [--write] [--include=<glob>,...] [--exclude=<glob>,...] [--prefixes=sm,md,lg,xl,print]| Flag | Default | Description |
|---|---|---|
<path> | Required | One or more files or directories to scan |
--write | Dry run | Apply changes in place |
--include | All files | Glob patterns to include |
--exclude | None | Glob patterns to exclude |
--prefixes | sm,md,lg,xl,print | Responsive prefixes to migrate |
Output (stdout): Unified diff per modified file (dry run), or file paths written (with --write).
Match strategy: Conservative — rewrites only inside className="...", className={...}, class="...", and @apply directives. JSX template literals, Vue :class arrays, and Svelte class: directives are flagged as limitations and not automatically rewritten.
Idempotency: Running the script twice on the same tree produces zero diffs on the second run.
Exit codes: 0 = no changes needed (or all applied with --write), 1 = dry-run: changes pending, 2 = usage/IO error