Skip to content

Python Scripts Reference

All scripts use Python 3 stdlib only — no pip dependencies. They follow one of two contract families.

Used when a slash command needs to parse the script’s output. The script:

  • Writes JSON to stdout
  • Returns a reasons array in the JSON
  • Exits 0 (match/success) or 1 (no match/failure)
  • Writes nothing to stderr on success

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)

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).

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:

Terminal window
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.

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:

Terminal window
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).

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:

Terminal window
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.

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:

Terminal window
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).


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):

Terminal window
python3 generate_color_scale.py <hex-color> [--name=<name>] [--format=css|json|both]
ArgumentRequiredDescription
hex-colorYesSeed color as #rrggbb or #rgb
--nameNoCSS variable prefix, kebab-case (default: scale)
--formatNocss, 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


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:

Terminal window
npx sass --style=expanded --no-source-map \
plugins/acss-kit/assets/foundation/sass/_index.scss:_foundation_raw.css
python3 plugins/acss-kit/scripts/wrap_foundation_layer.py \
_foundation_raw.css plugins/acss-kit/assets/foundation/foundation.css

Inputs:

Terminal window
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-motion block appended

Exit codes: 0 = success, 2 = usage/IO error


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):

Terminal window
python3 generate_palette.py <hex-color> [--mode=light|dark|both|brand]
ArgumentRequiredDescription
hex-colorYes6-digit hex (e.g., #4f46e5)
--modeNolight (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


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:

Terminal window
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


Contract: Generator
Location: plugins/acss-kit/scripts/tokens_to_css.py

Converts a JSON palette (from generate_palette.py) to CSS theme files.

Inputs:

Terminal window
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 :root block 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


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:

Terminal window
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: 1

Exit codes:

  • 0 — all pairs pass
  • 1 — one or more pairs fail
  • 2 — file not found or parse error

Contract: Generator
Location: plugins/acss-kit/scripts/oklch_shift.py

Applies OKLCH deltas to a hex color and returns the resulting hex.

Inputs:

Terminal window
python3 oklch_shift.py <hex-color> [--hue=<delta>] [--chroma=<multiplier>] [--lightness=<delta>]
FlagTypeDescription
--huefloat (degrees)Hue offset (e.g., +20, -15)
--chromafloat (multiplier)Chroma multiplier (e.g., 1.2 = 20% more saturated)
--lightnessfloat (percentage points)Lightness offset (e.g., +5, -3)

Output (stdout): Resulting hex color string

#6040e5

Stderr (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%)


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.

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:

Terminal window
python3 hash_file.py --path <path>
# or via stdin:
cat content | python3 hash_file.py --stdin

Output (stdout):

{
"path": "/abs/path/to/file",
"sha256": "<hex>",
"normalizedBytes": 1234,
"reasons": []
}

Exit codes: 0 = success, 1 = file not found (--path), 2 = IO/usage error


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:

Terminal window
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


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


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:

Terminal window
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


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:

Terminal window
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


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:

Terminal window
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


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:

Terminal window
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


Contract: Generator
Location: plugins/acss-utilities/scripts/generate_utilities.py

Generates the full utility CSS bundle from utilities.tokens.json.

Inputs:

Terminal window
python3 generate_utilities.py [--tokens=<path>] [--out-dir=<dir>] [--families=<list>]
FlagDefaultDescription
--tokensassets/utilities.tokens.jsonToken source file
--out-dirassets/utilities/Directory for per-family partials
--familiesAll enabled familiesComma-separated override

Output: Per-family CSS partials + concatenated assets/utilities.css

Exit codes: 0 = success, 1 = budget exceeded, 2 = IO/usage error


Contract: Validator
Location: plugins/acss-utilities/scripts/validate_utilities.py

Validates the committed utility bundle for integrity, bridge parity, and idempotency.

Checks:

  1. Bundle integrity — valid CSS syntax
  2. Bridge parity — every :root alias also in [data-theme="dark"]
  3. Idempotency — regenerated bundle matches committed bundle byte-for-byte

Inputs:

Terminal window
python3 validate_utilities.py [--bundle=<path>] [--bridge=<path>]

Exit codes:

  • 0 — all checks pass
  • 1 — integrity or parity failure
  • 2 — IO/usage error

Contract: Detector
Location: plugins/acss-utilities/scripts/detect_utility_target.py

Detects the target styles directory for utility output.

Inputs:

Terminal window
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


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:

Terminal window
python3 migrate_classnames.py <path> [<path>...] [--write] [--include=<glob>,...] [--exclude=<glob>,...] [--prefixes=sm,md,lg,xl,print]
FlagDefaultDescription
<path>RequiredOne or more files or directories to scan
--writeDry runApply changes in place
--includeAll filesGlob patterns to include
--excludeNoneGlob patterns to exclude
--prefixessm,md,lg,xl,printResponsive 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