diff --git a/.context/retros/2026-03-22-1.json b/.context/retros/2026-03-22-1.json
new file mode 100644
index 0000000..b71bad6
--- /dev/null
+++ b/.context/retros/2026-03-22-1.json
@@ -0,0 +1,60 @@
+{
+ "date": "2026-03-22",
+ "window": "7d",
+ "metrics": {
+ "commits": 23,
+ "contributors": 1,
+ "prs_merged": 0,
+ "insertions": 17007,
+ "deletions": 504,
+ "net_loc": 16503,
+ "test_loc": 415,
+ "test_ratio": 0.024,
+ "active_days": 3,
+ "sessions": 9,
+ "deep_sessions": 1,
+ "avg_session_minutes": 16,
+ "loc_per_session_hour": 5500,
+ "feat_pct": 0.39,
+ "fix_pct": 0.43,
+ "docs_pct": 0.13,
+ "peak_hour": 20,
+ "ai_assisted_commits": 19
+ },
+ "authors": {
+ "shenxianpeng": {
+ "commits": 23,
+ "insertions": 17007,
+ "deletions": 504,
+ "test_ratio": 0.024,
+ "top_area": "website/"
+ }
+ },
+ "version_range": ["initial", "initial"],
+ "streak_days": 3,
+ "tweetable": "Week of Mar 15: 23 commits, 17k LOC, 3 active days, 9 sessions | Streak: 3d | 83% AI-assisted",
+ "backlog": {
+ "total_open": 9,
+ "p0_p1": 0,
+ "p2": 1,
+ "p3": 1,
+ "completed_this_period": 0,
+ "added_this_period": 2
+ },
+ "test_health": {
+ "total_test_files": 10,
+ "tests_added_this_period": 0,
+ "regression_test_commits": 0,
+ "test_files_changed": 0
+ },
+ "skill_usage": {
+ "office-hours": 6,
+ "plan-eng-review": 3,
+ "plan-ceo-review": 2,
+ "qa": 2,
+ "ship": 1,
+ "document-release": 1,
+ "design-review": 1,
+ "retro": 1
+ }
+}
diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml
index 5894d73..40e8d6f 100644
--- a/.github/workflows/deploy-website.yml
+++ b/.github/workflows/deploy-website.yml
@@ -1,8 +1,11 @@
# CAST — Deploy official website to GitHub Pages
#
# Triggers on push to main (website/ or docs/ changes).
-# Builds HTML from Markdown sources, then serves website/ at
-# https://castops.github.io/cast/
+# Builds HTML from Markdown sources, then pushes website/ to the
+# gh-pages branch. PR preview subdirectories (pr-*/) are preserved.
+#
+# Pages source must be set to: Deploy from branch → gh-pages → / (root)
+# Repo Settings → Pages → Source → Deploy from a branch
name: Deploy Website
@@ -15,21 +18,16 @@ on:
workflow_dispatch:
permissions:
- contents: read
- pages: write
- id-token: write
+ contents: write
concurrency:
- group: pages
+ group: pages-main
cancel-in-progress: false
jobs:
deploy:
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v4
@@ -44,14 +42,11 @@ jobs:
- name: Build docs HTML from Markdown
run: python scripts/build_docs.py
- - name: Setup Pages
- uses: actions/configure-pages@v5
-
- - name: Upload artifact
- uses: actions/upload-pages-artifact@v3
+ - name: Deploy to gh-pages branch
+ uses: JamesIves/github-pages-deploy-action@v4
with:
- path: website/
-
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v4
+ folder: website
+ branch: gh-pages
+ clean: true
+ clean-exclude: |
+ pr-*
diff --git a/.github/workflows/devsecops.yml b/.github/workflows/devsecops.yml
index f7f3089..51e0f0f 100644
--- a/.github/workflows/devsecops.yml
+++ b/.github/workflows/devsecops.yml
@@ -80,7 +80,10 @@ jobs:
- name: pip-audit
run: |
pip install pip-audit
- pip-audit
+ pip install -e ".[dev]"
+ # CVE-2026-4539: ReDoS in pygments AdlLexer — no fix released yet
+ # (https://github.com/advisories/GHSA-5239-wwwm-4pmq); re-enable once patched
+ pip-audit --ignore-vuln CVE-2026-4539
# ── 4. Container Security ──────────────────────────────────────────────────
container:
diff --git a/.github/workflows/pr-preview-cleanup.yml b/.github/workflows/pr-preview-cleanup.yml
new file mode 100644
index 0000000..11d8f7a
--- /dev/null
+++ b/.github/workflows/pr-preview-cleanup.yml
@@ -0,0 +1,38 @@
+# CAST — PR Preview Cleanup
+#
+# Removes the pr-{number}/ subdirectory from the gh-pages branch
+# when a pull request is closed (merged or dismissed).
+
+name: PR Preview Cleanup
+
+on:
+ pull_request:
+ types: [closed]
+
+permissions:
+ contents: write
+
+jobs:
+ cleanup:
+ name: Remove PR preview
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: gh-pages
+ fetch-depth: 1
+
+ - name: Remove preview directory for PR #${{ github.event.number }}
+ env:
+ PR_NUM: ${{ github.event.number }}
+ run: |
+ if [ -d "pr-${PR_NUM}" ]; then
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git rm -rf "pr-${PR_NUM}"
+ git commit -m "chore: remove preview for PR #${PR_NUM}"
+ git push
+ echo "Removed pr-${PR_NUM}/"
+ else
+ echo "No preview directory found for PR #${PR_NUM} — nothing to clean up."
+ fi
diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml
new file mode 100644
index 0000000..691b9ab
--- /dev/null
+++ b/.github/workflows/pr-preview.yml
@@ -0,0 +1,83 @@
+# CAST — PR Preview Deployment
+#
+# Deploys a preview of the website to the gh-pages branch under
+# pr-{number}/ on every push to a pull request.
+# Posts a comment with the preview URL (updates existing comment on re-push).
+# Cleaned up automatically when the PR is closed.
+
+name: PR Preview
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+ paths:
+ - 'website/**'
+ - 'docs/**'
+
+permissions:
+ contents: write
+ pull-requests: write
+
+concurrency:
+ group: pr-preview-${{ github.event.number }}
+ cancel-in-progress: true
+
+jobs:
+ preview:
+ name: Deploy PR preview
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.11'
+
+ - name: Install Markdown library
+ run: pip install markdown
+
+ - name: Build docs HTML from Markdown
+ run: python scripts/build_docs.py
+
+ - name: Deploy to gh-pages/pr-${{ github.event.number }}/
+ uses: JamesIves/github-pages-deploy-action@v4
+ with:
+ folder: website
+ branch: gh-pages
+ target-folder: pr-${{ github.event.number }}
+ clean: true
+
+ - name: Post preview URL comment
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const pr = context.issue.number;
+ const owner = context.repo.owner;
+ const repo = context.repo.repo;
+ const base = `https://${owner}.github.io/${repo}/pr-${pr}`;
+ const sha = context.payload.pull_request.head.sha.slice(0, 7);
+
+ const body = [
+ `## 🔍 Preview ready`,
+ ``,
+ `| Page | URL |`,
+ `|------|-----|`,
+ `| EN homepage | [${base}/](${base}/) |`,
+ `| ZH homepage | [${base}/zh/](${base}/zh/) |`,
+ `| Docs (EN) | [${base}/docs/getting-started.html](${base}/docs/getting-started.html) |`,
+ `| Docs (ZH) | [${base}/zh/docs/getting-started.html](${base}/zh/docs/getting-started.html) |`,
+ ``,
+ `> Built from \`${sha}\`. Updates on every push. Removed when PR is closed.`,
+ ].join('\n');
+
+ const comments = await github.rest.issues.listComments({ owner, repo, issue_number: pr });
+ const existing = comments.data.find(
+ c => c.user.login === 'github-actions[bot]' && c.body.includes('Preview ready')
+ );
+
+ if (existing) {
+ await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
+ } else {
+ await github.rest.issues.createComment({ owner, repo, issue_number: pr, body });
+ }
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 25f28b7..e07fec6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,37 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [Unreleased]
+## [0.1.1] — 2026-03-24
+
+### Changed
+
+- **Complete website redesign (EN + ZH)** — redesigned `website/index.html` and
+ `website/zh/index.html` to the Industrial Editorial design system defined in `DESIGN.md`:
+ - Display font: Fraunces 900 (headlines/hero) with Instrument Sans body and IBM Plex Sans
+ Condensed labels
+ - Color palette: warm near-black `#0D0C0B` background, electric chartreuse `#CBFF2E`
+ accent, warm off-white `#EDE9E3` text
+ - 55/45 left-anchored hero grid with diagonal structural line
+ - Operator-console section labels (uppercase, IBM Plex Sans Condensed, chartreuse hairline)
+ - Replaced GitHub-ish blue/gray palette with Industrial Editorial tokens
+- **Website navigation and link fixes:**
+ - Fixed root-relative hrefs (`/`, `/index.html`) → relative paths (`./`) for correct
+ GitHub Pages project-site routing
+ - Fixed broken `../docs/` links → rendered HTML docs at `docs/getting-started.html`
+ and `docs/plugin-guide.html` (built by `scripts/build_docs.py` at deploy time)
+ - Unified GitHub repo references to `castops/cast-cli` in both language variants
+ - Added `target="_blank" rel="noopener noreferrer"` to all external links in both files
+- **Website SEO and meta improvements:**
+ - Added `og:title`, `og:description`, `og:type` Open Graph tags to EN page
+ - Fixed stale `og:title` mismatch with `
` in EN page
+ - Added `` to both pages
+ - Added `` to both pages
+ - Added `hreflang` alternate link tags (`en` / `zh-CN`) to both pages
+ - Fixed `preconnect` hint to add missing `crossorigin` attribute on `fonts.googleapis.com`
+ - Fixed EN nav `color-mix()` background → `rgba()` with `-webkit-backdrop-filter` for
+ Safari/WebView/enterprise browser compatibility
+ - Added mobile responsive nav collapse at 900px breakpoint to ZH page
+
### Added
- **Node.js and Go GitHub Actions templates** — production-ready pipelines for both stacks:
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..74ec2a0
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,15 @@
+# CLAUDE.md — CastOps Project Instructions
+
+## Design System
+Always read `DESIGN.md` before making any visual or UI decisions.
+All font choices, colors, spacing, border-radius, and aesthetic direction are defined there.
+Do not deviate without explicit user approval.
+
+Key rules:
+- **Display font:** Fraunces (serif) — never substitute with Inter, Roboto, or other grotesks without approval
+- **Accent color:** `#CBFF2E` (electric chartreuse) — use sparingly; this is a high-value signal color, not a background fill
+- **Background:** `#0D0C0B` (warm near-black) — not cool gray, not pure black
+- **Aesthetic:** Industrial Editorial — asymmetric, left-anchored, operator-console energy
+- **Never:** purple gradients, 3-column icon grids, centered hero with floating CTA, glassmorphism, decorative blobs
+
+In QA mode, flag any code that doesn't match `DESIGN.md` — including font substitutions, off-palette colors, or layout patterns that conflict with the defined aesthetic.
diff --git a/DESIGN.md b/DESIGN.md
new file mode 100644
index 0000000..912761c
--- /dev/null
+++ b/DESIGN.md
@@ -0,0 +1,113 @@
+# Design System — CastOps
+
+## Product Context
+- **What this is:** An open-source platform engineering suite for the AI-native era, comprising Cast CLI (DevSecOps governance) and Cast Slice (GPU FinOps engine for Kubernetes)
+- **Who it's for:** Platform engineers, DevOps/MLOps engineers, and AI infrastructure teams — technical, opinionated people who run clusters and care deeply about operational efficiency
+- **Space/industry:** DevOps / platform tooling; peers include Pulumi, Railway, Fly.io, Grafana, Warp
+- **Project type:** Marketing/documentation website
+- **License:** Apache 2.0 (open source)
+
+## Aesthetic Direction
+- **Direction:** Industrial Editorial
+- **Decoration level:** Intentional — signal lines, grid fragments, annotated diagrams, structural rule marks
+- **Mood:** An operations room designed by someone who also reads *Monocle*. Not glassy, not neon. Machined surfaces, operator confidence, the energy of a flight operations center that happens to be beautifully typeset. Every design choice signals: "these people run clusters and have taste."
+- **Anti-patterns (never use):** Purple/violet gradients, 3-column icon feature grids, centered-everything hero layouts, generic stock developer illustrations, glassmorphism cards, decorative blobs, gradient buttons as primary CTA, fake dashboards full of meaningless charts
+
+## Typography
+
+- **Display/Hero:** [Fraunces](https://fonts.google.com/specimen/Fraunces) — high-contrast editorial serif; in a zero-serif DevOps category, this creates instant visual differentiation and signals authority before a word is read. Use at 72–100px, -0.03em letter-spacing, weight 900
+- **Body:** [Instrument Sans](https://fonts.google.com/specimen/Instrument+Sans) — technical clarity with more character than default SaaS grotesks; 16–18px, 1.65 line-height
+- **UI/Labels:** Instrument Sans (same as body), 13–14px, weight 600, uppercase with 0.06em tracking for labels
+- **Data/Tables:** [IBM Plex Sans Condensed](https://fonts.google.com/specimen/IBM+Plex+Sans+Condensed) — compact, operational, excellent for dense infrastructure content; must use `font-variant-numeric: tabular-nums` for all numeric values
+- **Code/Terminal:** [Berkeley Mono](https://berkeleygraphics.com/typefaces/berkeley-mono/) — the best monospace typeface of the last decade; has weight and personality. Requires a commercial license. Use [JetBrains Mono](https://fonts.google.com/specimen/JetBrains+Mono) as a free fallback
+- **Loading:** Google Fonts CDN for Fraunces, Instrument Sans, IBM Plex Sans Condensed, JetBrains Mono. Berkeley Mono self-hosted after licensing.
+- **Scale:**
+ | Level | Size | Usage |
+ |-------|------|-------|
+ | display-2xl | 88–100px | Hero headline (Fraunces) |
+ | display-xl | 64–88px | Section openers (Fraunces) |
+ | display-lg | 48–64px | Major subheads (Fraunces) |
+ | display-md | 32–48px | Card titles (Fraunces) |
+ | body-lg | 18px | Marketing prose (Instrument Sans) |
+ | body-md | 16px | Docs body (Instrument Sans) |
+ | body-sm | 14px | Secondary descriptions (Instrument Sans) |
+ | label | 11–12px | Uppercase labels (IBM Plex Sans Condensed, weight 700, 0.1em tracking) |
+ | code | 12.5–14px | Terminal / config blocks (Berkeley Mono / JetBrains Mono) |
+
+## Color
+
+- **Approach:** Restrained — the accent is rare and high-value; most of the site lives in graphite and ash tones
+- **Background:** `#0D0C0B` — near-black with warmth stripped of blue; feels like matte metal, not a generic dark-mode SaaS clone
+- **Surface:** `#171614` — for cards, panels, sidebars, terminal backgrounds
+- **Surface Elevated:** `#1F1D1B` — for dropdowns, tooltips, modal backgrounds
+- **Border/Hairline:** `#2A2723` — just enough to see the edge without a bright line
+- **Primary Text:** `#EDE9E3` — warm off-white; pure white on this background is aggressive and cheap
+- **Secondary Text:** `#B5AFA8` — for descriptions, supporting copy
+- **Muted Text:** `#7A736C` — for metadata, timestamps, labels, placeholder text
+- **Primary Accent:** `#CBFF2E` — electric chartreuse; the color of a GPU under load on a thermal camera. Nothing in DevOps tooling uses this color. Reserve for: CTAs, active states, key metrics, hover highlights, the single brand moment per section
+- **Secondary Accent:** `#E87C3E` — construction/engineering orange; for warnings, cost anomalies, "this matters right now" states
+- **Semantic:**
+ | Role | Hex | Usage |
+ |------|-----|-------|
+ | Success | `#7DFA9B` | Passing checks, healthy nodes, cost savings |
+ | Warning | `#FFBF47` | Idle nodes, policy warnings, approaching limits |
+ | Danger | `#FF6B57` | Failed checks, blocked deployments, critical alerts |
+ | Info | `#63C7FF` | Informational banners, doc notes |
+- **Dark mode:** This IS the dark mode. The base palette is dark.
+- **Light mode:** Invert to warm off-white base (`#F5F3EF`), surface white (`#FFFFFF`), adjust accent to `#6B8B00` (darker chartreuse for contrast on light). Keep the same type system.
+
+## Spacing
+
+- **Base unit:** 8px
+- **Density:** Comfortable-dense — platform engineers can handle information density; sparse layouts signal "made for non-technical stakeholders"
+- **Scale:**
+ | Token | Value | Usage |
+ |-------|-------|-------|
+ | 2xs | 2px | Micro gaps, hairlines |
+ | xs | 4px | Icon-to-label, tight pairs |
+ | sm | 8px | Component internal padding |
+ | md | 16px | Standard padding, card internal |
+ | lg | 24px | Section-internal gaps |
+ | xl | 32px | Between subsections |
+ | 2xl | 48px | Between major sections |
+ | 3xl | 64px | Hero padding, section separation |
+ | 4xl | 96px | Page-level section padding |
+
+## Layout
+
+- **Approach:** Creative-editorial — left-anchored, asymmetric composition; the hero must not look like a document
+- **Grid:** 12-column; max content width 1160px; 40px gutters on desktop, 20px on mobile
+- **Hero composition:** Left 55% carries the headline (high-left anchor, not centered) and CTA row; right 45% carries a live product tableau (terminal + pipeline visualization). A diagonal structural line or clipped panel edge cuts through for motion.
+- **Section rhythm:** Alternate dense content blocks with more open ones. Mix editorial prose blocks with operator-console data modules.
+- **Max content width:** 1160px
+- **Border radius:**
+ | Token | Value | Usage |
+ |-------|-------|-------|
+ | sm | 4px | Buttons, tags, code tokens |
+ | md | 8px | Cards, terminal windows, inputs |
+ | lg | 12px | Modals, dialogs |
+ | full | 9999px | Pills, avatars |
+
+## Motion
+
+- **Approach:** Intentional — only transitions that aid comprehension; no decorative scroll animations
+- **Easing:** enter: `ease-out` / exit: `ease-in` / move: `ease-in-out`
+- **Duration:**
+ | Token | Range | Usage |
+ |-------|-------|-------|
+ | micro | 50–100ms | Hover states, focus rings |
+ | short | 150–250ms | Button feedback, menu open |
+ | medium | 250–400ms | Panel transitions, accordion |
+ | long | 400–700ms | Page transitions, terminal line animation |
+- **Permitted motion:** Terminal line input animation, number counter rollup on stats, status indicator pulse for active nodes
+- **Forbidden motion:** Scroll-triggered entrance animations on marketing copy, parallax backgrounds, looping decorative animations
+
+## Decisions Log
+
+| Date | Decision | Rationale |
+|------|----------|-----------|
+| 2026-03-24 | Initial design system created | Created by /design-consultation. Research covered Railway, Fly.io, Pulumi, Warp, and Grafana. Outside voices: Codex (GPT-5.4) and Claude subagent both independently converged on industrial/editorial direction and chartreuse accent. |
+| 2026-03-24 | Fraunces as display font (Risk #1) | Every competitor uses grotesque sans-serifs. In a zero-serif category, editorial serif = instant visual differentiation and psychological authority before a word is read. |
+| 2026-03-24 | Accent color #CBFF2E — electric chartreuse (Risk #2) | GPU thermal imaging color. Not blue, not purple, not teal. Nothing in DevOps tooling looks like this. Must be used sparingly (rare = valuable). |
+| 2026-03-24 | Information-dense layout (Risk #3) | Target users are platform engineers who handle data density daily. Sparse layouts pre-screen for the wrong audience. |
+| 2026-03-24 | Background warm near-black #0D0C0B instead of cool graphite | Cool gray (#0B0F10) reads as generic dark-mode SaaS. Warm near-black feels like matte metal — more physical, more material. |
diff --git a/TODOS.md b/TODOS.md
index d7fbbb0..629dd89 100644
--- a/TODOS.md
+++ b/TODOS.md
@@ -2,14 +2,6 @@
## Design
-### Create DESIGN.md
-**What:** Run `/design-consultation` to document the dashboard's design system.
-**Why:** `dashboard/template.html` uses GitHub-dark CSS tokens (`#0d1117`, `#58a6ff`, etc.) but this palette is implicit and undocumented. Without a reference, future contributors may introduce inconsistent colors.
-**Pros:** Prevents design drift; gives future contributors a reference for new UI additions (e.g., multi-repo dashboard, dark/light toggle).
-**Cons:** Minor time investment (~15 min).
-**Context:** Design tokens live in the `:root` block of `dashboard/template.html`. The implicit system already has semantic naming (--critical-bg, --pass-bg, --high-bg) — DESIGN.md would formalize and explain these choices.
-**Depends on:** None.
-
## Accessibility
### Verify color contrast ratios meet WCAG AA
@@ -114,3 +106,13 @@
**Context:** 存储方案候选:项目根目录 `.cast-score.json`(与项目绑定)vs `~/.cast/{repo-slug}.json`(全局)。需要在 Phase 2 设计时决定,避免后期迁移。
**Priority:** P2 — Phase 2 后续迭代。
**Depends on:** cast score (Phase 2) 基础实施完成。
+
+## Completed
+
+### Create DESIGN.md
+**What:** Run `/design-consultation` to document the project's design system (Industrial Editorial aesthetic).
+**Completed:** v0.1.1 (2026-03-24) — `DESIGN.md` created with full token set: Fraunces/Instrument Sans typography, `#0D0C0B`/`#CBFF2E` palette, spacing scale, and layout rules.
+
+### Redesign website (EN + ZH) per DESIGN.md
+**What:** Apply the Industrial Editorial design system to `website/index.html` and `website/zh/index.html`.
+**Completed:** v0.1.1 (2026-03-24) — Both pages fully redesigned. Fixed navigation links, SEO meta tags, cross-browser compat, and mobile responsive nav.
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 0000000..fad7e57
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,344 @@
+version = 1
+revision = 3
+requires-python = ">=3.9"
+resolution-markers = [
+ "python_full_version >= '3.10'",
+ "python_full_version < '3.10'",
+]
+
+[[package]]
+name = "annotated-doc"
+version = "0.0.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
+]
+
+[[package]]
+name = "castops"
+source = { editable = "." }
+dependencies = [
+ { name = "rich" },
+ { name = "typer", version = "0.23.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "typer", version = "0.24.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+
+[package.optional-dependencies]
+dev = [
+ { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "pytest", marker = "extra == 'dev'", specifier = ">=8" },
+ { name = "rich", specifier = ">=13" },
+ { name = "typer", specifier = ">=0.12" },
+]
+provides-extras = ["dev"]
+
+[[package]]
+name = "click"
+version = "8.1.8"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" },
+]
+
+[[package]]
+name = "click"
+version = "8.3.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
+]
+
+[[package]]
+name = "markdown-it-py"
+version = "3.0.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "mdurl", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
+]
+
+[[package]]
+name = "markdown-it-py"
+version = "4.0.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+dependencies = [
+ { name = "mdurl", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
+]
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "26.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "8.4.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.10'" },
+ { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "packaging", marker = "python_full_version < '3.10'" },
+ { name = "pluggy", marker = "python_full_version < '3.10'" },
+ { name = "pygments", marker = "python_full_version < '3.10'" },
+ { name = "tomli", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "9.0.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
+ { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" },
+ { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "packaging", marker = "python_full_version >= '3.10'" },
+ { name = "pluggy", marker = "python_full_version >= '3.10'" },
+ { name = "pygments", marker = "python_full_version >= '3.10'" },
+ { name = "tomli", marker = "python_full_version == '3.10.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
+]
+
+[[package]]
+name = "rich"
+version = "14.3.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" },
+]
+
+[[package]]
+name = "shellingham"
+version = "1.5.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" },
+ { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" },
+ { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" },
+ { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" },
+ { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" },
+ { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" },
+ { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" },
+ { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" },
+ { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" },
+ { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" },
+ { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" },
+ { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" },
+ { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" },
+ { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" },
+ { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" },
+]
+
+[[package]]
+name = "typer"
+version = "0.23.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "annotated-doc", marker = "python_full_version < '3.10'" },
+ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "rich", marker = "python_full_version < '3.10'" },
+ { name = "shellingham", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d3/ae/93d16574e66dfe4c2284ffdaca4b0320ade32858cb2cc586c8dd79f127c5/typer-0.23.2.tar.gz", hash = "sha256:a99706a08e54f1aef8bb6a8611503808188a4092808e86addff1828a208af0de", size = 120162, upload-time = "2026-02-16T18:52:40.354Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/14/2c/dee705c427875402200fe779eb8a3c00ccb349471172c41178336e9599cc/typer-0.23.2-py3-none-any.whl", hash = "sha256:e9c8dc380f82450b3c851a9b9d5a0edf95d1d6456ae70c517d8b06a50c7a9978", size = 56834, upload-time = "2026-02-16T18:52:39.308Z" },
+]
+
+[[package]]
+name = "typer"
+version = "0.24.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+dependencies = [
+ { name = "annotated-doc", marker = "python_full_version >= '3.10'" },
+ { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "rich", marker = "python_full_version >= '3.10'" },
+ { name = "shellingham", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]
diff --git a/website/index.html b/website/index.html
index b8f60c9..aeb4ace 100644
--- a/website/index.html
+++ b/website/index.html
@@ -3,777 +3,1117 @@
- CAST — Production-Grade DevSecOps Pipeline
-
-
+ CAST — DevSecOps pipeline governance, one command
+
+
+
+
+
+
+
+
+
-
-