diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..d72dccf --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,28 @@ +# Dreamcoder Dots — CODEOWNERS +# Defines who is responsible for reviewing changes to each area. + +* @Dreamcoder08 + +# Python theme engine +/src/dreamcoder_theme/ @Dreamcoder08 + +# Go installer +/installer/ @Dreamcoder08 + +# CI/CD and GitHub config +/.github/ @Dreamcoder08 + +# Documentation +/docs/ @Dreamcoder08 + +# Shell scripts +/scripts/*.sh @Dreamcoder08 + +# Shell configs +/Shell/ @Dreamcoder08 + +# Terminal configs +/Kitty/ @Dreamcoder08 +/Ghostty/ @Dreamcoder08 +/Tmux/ @Dreamcoder08 +/Zellij/ @Dreamcoder08 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..1fac3ae --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,64 @@ +name: Bug Report +description: Create a report to help improve Dreamcoder OS +title: '[Bug]: ' +labels: ['bug'] +projects: [] +assignees: + - Dreamcoder08 + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + + - type: textarea + id: description + attributes: + label: Description + description: A clear and concise description of the bug + placeholder: When I run `dreamcoder light`, the colors don't update... + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + description: How can we reproduce this? + placeholder: | + 1. Run `dreamcoder dark` + 2. Open Kitty terminal + 3. Colors are wrong + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: What did you expect to happen? + validations: + required: true + + - type: textarea + id: environment + attributes: + label: Environment + description: Fill in your environment details + value: | + - OS: [e.g. Arch Linux] + - Terminal: [e.g. Kitty, Ghostty] + - Shell: [e.g. Fish, Zsh] + - Dreamcoder version: [e.g. 2.0.0] + - Gentleman.Dots installed: [yes/no] + - ML4W installed: [yes/no] + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant Log Output + description: Run `dreamcoder doctor` and paste the output + render: text diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..891fd90 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Dreamcoder OS Docs + url: https://github.com/Dreamcoder08/Dreamcoder_dots/blob/main/README.md + about: Check the documentation first diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..4c7dc1d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,36 @@ +name: Feature Request +description: Suggest an idea for Dreamcoder OS +title: '[Feature]: ' +labels: ['enhancement'] +projects: [] +assignees: + - Dreamcoder08 + +body: + - type: markdown + attributes: + value: | + Thanks for suggesting a feature! + + - type: textarea + id: problem + attributes: + label: Problem Statement + description: What problem does this feature solve? + placeholder: I'm always frustrated when... + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Proposed Solution + description: What would you like to see? + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: What alternatives have you considered? diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..82d4f11 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,29 @@ +## Description + + + +## Type of Change + +- [ ] feat: new feature +- [ ] fix: bug fix +- [ ] docs: documentation +- [ ] style: code style (formatting, etc) +- [ ] refactor: code refactoring +- [ ] perf: performance improvement +- [ ] test: adding tests +- [ ] chore: maintenance +- [ ] ci: CI/CD changes + +## Checklist + +- [ ] My code follows the project's coding standards +- [ ] I have tested these changes locally +- [ ] Existing tests continue to pass +- [ ] New tests have been added (if applicable) +- [ ] Documentation has been updated (if applicable) + +## Screenshots (if applicable) + +## Related Issues + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f690341 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,33 @@ +version: 2 +updates: + - package-ecosystem: 'gomod' + directory: '/installer' + schedule: + interval: 'weekly' + day: 'monday' + commit-message: + prefix: 'chore(deps)' + labels: + - 'dependencies' + - 'go' + + - package-ecosystem: 'pip' + directory: '/' + schedule: + interval: 'weekly' + day: 'monday' + commit-message: + prefix: 'chore(deps)' + labels: + - 'dependencies' + - 'python' + + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'monthly' + commit-message: + prefix: 'chore(ci)' + labels: + - 'dependencies' + - 'ci' diff --git a/.github/workflows/ci-go.yml b/.github/workflows/ci-go.yml new file mode 100644 index 0000000..4425bd4 --- /dev/null +++ b/.github/workflows/ci-go.yml @@ -0,0 +1,40 @@ +name: CI (Go) + +on: + push: + branches: [main] + paths: ['installer/**'] + pull_request: + branches: [main] + paths: ['installer/**'] + +jobs: + go: + runs-on: ubuntu-latest + strategy: + matrix: + go-version: ['1.26'] + + defaults: + run: + working-directory: installer + + steps: + - uses: actions/checkout@v4 + + - name: Set up Go ${{ matrix.go-version }} + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + + - name: Build + run: go build ./... + + - name: Test + run: go test ./... -v + + - name: Lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest + working-directory: installer diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml new file mode 100644 index 0000000..8fb0826 --- /dev/null +++ b/.github/workflows/commitlint.yml @@ -0,0 +1,25 @@ +name: Commitlint + +on: + pull_request: + branches: [main] + +jobs: + commitlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install commitlint + run: | + npm install --save-dev @commitlint/config-conventional @commitlint/cli + + - name: Lint commits + run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..921de2e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,50 @@ +name: Release + +on: + push: + tags: ['v*'] + +permissions: + contents: write + id-token: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: stable + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: '~> v2' + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build-pypi: + needs: [goreleaser] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install build tools + run: pip install build + - name: Build package + run: python -m build + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: dreamcoder-theme-pypi + path: dist/ diff --git a/.github/workflows/theme-validation.yml b/.github/workflows/theme-validation.yml index 940028d..ebfe807 100644 --- a/.github/workflows/theme-validation.yml +++ b/.github/workflows/theme-validation.yml @@ -3,7 +3,6 @@ name: CI on: push: branches: [main] - tags: ["v*"] pull_request: branches: [main] @@ -58,24 +57,4 @@ jobs: run: | git diff --exit-code docs/dreamcoder-theme-preview.md || (echo "::error::Theme preview not regenerated" && exit 1) - publish: - needs: [test] - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/v') - permissions: - id-token: write - - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Install build tools - run: pip install build - - - name: Build package - run: python -m build - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + # publish job removed — use Release workflow for PyPI publishing diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..39cf6bf --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,57 @@ +# GoReleaser configuration for dreamcoder-dots installer +# https://goreleaser.com +# +# Trigger: git tag v* && git push --tags +# Pipeline: build (4 platforms) → archive → release → homebrew cask + +version: 2 + +project_name: dreamcoder-dots + +before: + hooks: + - sh -c "cd installer && go mod tidy" + +builds: + - id: dreamcoder-dots + dir: installer + main: . + binary: dreamcoder-dots + ldflags: + - -X github.com/dreamcoder08/dreamcoder-dots/installer/pkg/version.Version={{ .Version }} + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + env: + - CGO_ENABLED=0 + +archives: + - id: default + formats: ["tar.gz"] + name_template: >- + {{ .ProjectName }}-{{ .Os }}-{{ .Arch }} + files: + - none* + +release: + github: + owner: Dreamcoder08 + name: Dreamcoder_dots + header: | + ## Dreamcoder OS {{ .Version }} + + > Token-governed visual operating layer. + + ### Install + + ```bash + # Download from this release + ``` + + ### Changes + + footer: | + **Full Changelog**: https://github.com/Dreamcoder08/Dreamcoder_dots/compare/{{ .PreviousTag }}...{{ .Tag }} diff --git a/.opencode/themes/dreamcoder-light.json b/.opencode/themes/dreamcoder-light.json deleted file mode 100644 index 368cc19..0000000 --- a/.opencode/themes/dreamcoder-light.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "$schema": "https://opencode.ai/theme.json", - "defs": { - "dreamBackground": "#f3eadc", - "dreamPanel": "#fff7ea", - "dreamElement": "#e6d7c4", - "dreamText": "#17120d", - "dreamMuted": "#352e22", - "dreamCocoa": "#a7471c", - "dreamLucuma": "#824f16", - "dreamDiagnostic": "#0d4a68", - "dreamSage": "#3d723d", - "dreamViolet": "#57478b", - "dreamMauve": "#7d3e64", - "dreamCoral": "#842f24", - "dreamWarning": "#654300" - }, - "theme": { - "background": "none", - "backgroundPanel": "#fff7ea", - "backgroundElement": "#e3d2bc", - "backgroundHover": "#c8ad89", - "backgroundSelected": "#824f16", - "textSelected": "#f3eadc", - "backgroundCode": "#d0bca3", - "backgroundSearch": "#a1895c", - "backgroundLine": "#e6d7c4", - "backgroundAssistant": "#d7d7ce", - "backgroundUser": "#e2d3be", - "backgroundTool": "#e0d6d2", - "text": "#17120d", - "textMuted": "#352e22", - "textSubtle": "#554638", - "textPlaceholder": "#725e4c", - "textAssistant": "#0f4058", - "textUser": "#724615", - "textTool": "#57478b", - "primary": "#824f16", - "secondary": "#a7471c", - "accent": "#824f16", - "accentMuted": "#af8d65", - "error": "#842f24", - "warning": "#654300", - "success": "#3d723d", - "info": "#0d4a68", - "border": "#66513b", - "borderActive": "#824f16", - "borderSubtle": "#8a7358", - "borderFocus": "#0d4a68", - "shadow": "#b6b0a5", - "diffAdded": "#3d723d", - "diffRemoved": "#842f24", - "diffContext": "#352e22", - "diffHunkHeader": "#57478b", - "diffHighlightAdded": "#3d723d", - "diffHighlightRemoved": "#842f24", - "diffAddedBg": "#7d9c75", - "diffRemovedBg": "#ab7064", - "diffContextBg": "none", - "diffLineNumber": "#554638", - "diffAddedLineNumberBg": "#7d9c75", - "diffRemovedLineNumberBg": "#ab7064", - "diffHunkHeaderBg": "#9d90af", - "diffFold": "#725e4c", - "diffFoldBg": "#e6d7c4", - "markdownText": "#17120d", - "markdownHeading": "#824f16", - "markdownLink": "#0d4a68", - "markdownLinkText": "#824f16", - "markdownCode": "#3d723d", - "markdownBlockQuote": "#a7471c", - "markdownEmph": "#0d4a68", - "markdownStrong": "#824f16", - "markdownHorizontalRule": "#66513b", - "markdownListItem": "#824f16", - "markdownListEnumeration": "#57478b", - "markdownTableBorder": "#66513b", - "markdownTableHeader": "#a7471c", - "markdownImage": "#7d3e64", - "markdownImageText": "#17120d", - "markdownCodeBlock": "#17120d", - "markdownCodeBlockBg": "#d0bca3", - "markdownInlineCodeBg": "#5e885a", - "syntaxComment": "#725e4c", - "syntaxKeyword": "#7c4c11", - "syntaxFunction": "#0d4a68", - "syntaxMethod": "#194a6e", - "syntaxVariable": "#4e4171", - "syntaxParameter": "#9e441b", - "syntaxProperty": "#0d4864", - "syntaxField": "#3b6d3b", - "syntaxString": "#3d723d", - "syntaxNumber": "#8f4c18", - "syntaxBoolean": "#9d452d", - "syntaxConstant": "#9d452d", - "syntaxType": "#57478b", - "syntaxClass": "#5f4876", - "syntaxInterface": "#474883", - "syntaxEnum": "#524f7d", - "syntaxOperator": "#7d3e64", - "syntaxPunctuation": "#352e22", - "syntaxTag": "#7c4c11", - "syntaxAttribute": "#0d4864", - "syntaxRegexp": "#7f3a52", - "syntaxEscape": "#654300", - "syntaxNamespace": "#57478b", - "syntaxModule": "#57478b", - "syntaxDecorator": "#7d3e64", - "syntaxBuiltin": "#9d452d", - "syntaxSpecial": "#654300", - "syntaxTodo": "#5c3d02", - "syntaxDeprecated": "#6e2f23", - "terminalBlack": "#5e5c57", - "terminalRed": "#842f24", - "terminalGreen": "#3d723d", - "terminalYellow": "#654300", - "terminalBlue": "#0d4a68", - "terminalMagenta": "#7d3e64", - "terminalCyan": "#0f4058", - "terminalWhite": "#17120d", - "terminalBrightBlack": "#554638", - "terminalBrightRed": "#702a20", - "terminalBrightGreen": "#366134", - "terminalBrightYellow": "#593b02", - "terminalBrightBlue": "#0f4058", - "terminalBrightMagenta": "#6b3654", - "terminalBrightCyan": "#4b3d74", - "terminalBrightWhite": "#17120d" - } -} diff --git a/.opencode/themes/dreamcoder.json b/.opencode/themes/dreamcoder.json index ac57426..803f32d 100644 --- a/.opencode/themes/dreamcoder.json +++ b/.opencode/themes/dreamcoder.json @@ -19,8 +19,8 @@ "background": "none", "backgroundPanel": "#fff7ea", "backgroundElement": "#e3d2bc", - "backgroundHover": "#c8ad89", - "backgroundSelected": "#f3eadc", + "backgroundHover": "#decbb1", + "backgroundSelected": "#decbb1", "textSelected": "#17120d", "backgroundCode": "#d0bca3", "backgroundSearch": "#a1895c", @@ -54,13 +54,13 @@ "diffHunkHeader": "#57478b", "diffHighlightAdded": "#3d723d", "diffHighlightRemoved": "#842f24", - "diffAddedBg": "#7d9c75", - "diffRemovedBg": "#ab7064", + "diffAddedBg": "#a7b899", + "diffRemovedBg": "#c49b8f", "diffContextBg": "none", "diffLineNumber": "#554638", - "diffAddedLineNumberBg": "#7d9c75", - "diffRemovedLineNumberBg": "#ab7064", - "diffHunkHeaderBg": "#9d90af", + "diffAddedLineNumberBg": "#a7b899", + "diffRemovedLineNumberBg": "#c49b8f", + "diffHunkHeaderBg": "#b8acbd", "diffFold": "#725e4c", "diffFoldBg": "#e6d7c4", "markdownText": "#17120d", diff --git a/.pi/settings.json b/.pi/settings.json index 0967ef4..e81bdba 100644 --- a/.pi/settings.json +++ b/.pi/settings.json @@ -1 +1,38 @@ -{} +{ + "subagents": { + "agentOverrides": { + "context-builder": { + "model": "deepseek-1m/deepseek-v4-flash", + "thinking": "low" + }, + "delegate": { + "model": "deepseek-1m/deepseek-v4-flash", + "thinking": "medium" + }, + "oracle": { + "model": "deepseek-1m/deepseek-v4-flash", + "thinking": "high" + }, + "planner": { + "model": "deepseek-1m/deepseek-v4-flash", + "thinking": "medium" + }, + "researcher": { + "model": "deepseek-1m/deepseek-v4-flash", + "thinking": "medium" + }, + "reviewer": { + "model": "deepseek-1m/deepseek-v4-flash", + "thinking": "high" + }, + "scout": { + "model": "deepseek-1m/deepseek-v4-flash", + "thinking": "low" + }, + "worker": { + "model": "deepseek-1m/deepseek-v4-flash", + "thinking": "high" + } + } + } +} diff --git a/AGENTS.md b/AGENTS.md index 4b791e0..8f46559 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,22 +1,43 @@ -# Code Review Rules +# Dreamcoder Dots — AI Agent Skills + +## Code Review Rules + +### Shell Scripts -## Shell Scripts - Max 30 lines per file - Use `set -euo pipefail` for scripts - Quote all variables: `"${var}"` - Use `[[ ]]` instead of `[ ]` for tests -## Modularity +### Modularity + - One file = one purpose - No duplicate code (DRY) - Conditional loading: `command -v x && ...` -## Safety +### Safety + - Safe sourcing: `[[ -f "$file" ]] && source "$file"` - No hardcoded paths - Fallback chains for optional tools -## Naming +### Naming + - Aliases: lowercase, short (`gs`, `pacupd`) - Functions: snake_case (`smart_cd`, `mkcd`) - Env vars: UPPER_CASE (`PROJECTS_DIR`) + +## Available Skills + +| Skill | Description | Path | +|-------|-------------|------| +| `dreamcoder-theme-engine` | Python theme engine: tokens, renderers, writers | [SKILL.md](skills/dreamcoder-theme-engine/SKILL.md) | +| `dreamcoder-palette-tokens` | Token schema, WCAG/APCA guardrails, modes | [SKILL.md](skills/dreamcoder-palette-tokens/SKILL.md) | + +## Auto-invoke + +When working on these areas, load the corresponding skill first: + +- Theme engine / renderers → `dreamcoder-theme-engine` +- Colors / tokens → `dreamcoder-palette-tokens` +- Shell scripts → use code review rules above diff --git a/Bat/.config/bat/themes/Dreamcoder-Dark.tmTheme b/Bat/.config/bat/themes/Dreamcoder-Dark.tmTheme index 6f12720..cbf424a 100644 --- a/Bat/.config/bat/themes/Dreamcoder-Dark.tmTheme +++ b/Bat/.config/bat/themes/Dreamcoder-Dark.tmTheme @@ -10,8 +10,8 @@ background#100f0d foreground#e8dfd0 caret#d99555 - selection#d99555 - selectionForeground#100f0d + selection#2b231b + selectionForeground#e8dfd0 lineHighlight#2b231b gutter#392e21 gutterForeground#c7b9aa diff --git a/Bat/.config/bat/themes/Dreamcoder-Light.tmTheme b/Bat/.config/bat/themes/Dreamcoder-Light.tmTheme index 0b12d83..057aff6 100644 --- a/Bat/.config/bat/themes/Dreamcoder-Light.tmTheme +++ b/Bat/.config/bat/themes/Dreamcoder-Light.tmTheme @@ -10,7 +10,7 @@ background#f3eadc foreground#17120d caret#824f16 - selection#f3eadc + selection#decbb1 selectionForeground#17120d lineHighlight#decbb1 gutter#c8ad89 diff --git a/Bat/.config/bat/themes/Dreamcoder.tmTheme b/Bat/.config/bat/themes/Dreamcoder.tmTheme index 0b12d83..057aff6 100644 --- a/Bat/.config/bat/themes/Dreamcoder.tmTheme +++ b/Bat/.config/bat/themes/Dreamcoder.tmTheme @@ -10,7 +10,7 @@ background#f3eadc foreground#17120d caret#824f16 - selection#f3eadc + selection#decbb1 selectionForeground#17120d lineHighlight#decbb1 gutter#c8ad89 diff --git a/CLAUDE.md b/CLAUDE.md index bda6e18..4a5a16f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,7 +20,8 @@ wallpaper-derived palettes unless explicitly requested. ## Important commands ```bash -./scripts/sync-dreamcoder-theme.py +./scripts/dreamcoder sync +./scripts/generate-palette-tokens.py bash -n scripts/update-colors.sh ghostty +validate-config STARSHIP_CONFIG=Shell/.config/starship.toml starship explain @@ -39,23 +40,39 @@ fish -n Shell/.config/fish/config.fish ## Dreamcoder identity -Official daily palette: +Canonical palettes (see `themes/dreamcoder/tokens.json`): + +**Dark — Ember Noir OLED** + +```txt +bg #100f0d +text #e8dfd0 +accent #d99555 +accent_2 #c96a45 +error #ed8a7a +focus #5f8f8f +opacity 0.76 +``` + +**Light — Cocoa/Lúcuma** ```txt -background #19120c -foreground #eee0d5 -accent #fbb974 -error #ffb4ab -opacity 0.60 +bg #f3eadc +text #17120d +accent #824f16 +accent_2 #a7471c +error #842f24 +focus #0f6570 +opacity 0.96 ``` Rules: - visible theme name is `Dreamcoder` +- edit colors only in `themes/dreamcoder/tokens.json`, then `./scripts/dreamcoder sync` - Starship palette is `dreamcoder` -- Ghostty theme is `dreamcoder` +- Ghostty theme is `dreamcoder` / `dreamcoder-dark` / `dreamcoder-light` - Fastfetch logo is `Dreamcoder01.jpg` -- do not reintroduce `Codex-App/` - do not commit secrets or tokens ## GitHub MCP diff --git a/COMPARISON.md b/COMPARISON.md new file mode 100644 index 0000000..1dff4bb --- /dev/null +++ b/COMPARISON.md @@ -0,0 +1,142 @@ +# Dreamcoder OS — Comparison + +> ¿Por qué necesitás Dreamcoder si ya tenés Gentleman.Dots + ML4W? +> Porque Dreamcoder es la capa que ninguno de los dos tiene: **salud visual, identidad, y AI integration**. + +## Feature Comparison + +| Feature | Gentleman.Dots | ML4W | **dreamcoder-dots** | +| --------------------- | ---------------------- | ------------------- | ------------------------------ | +| **Theme Engine** | ❌ Catppuccin estático | ✅ Matugen dinámico | ✅ **Token-based + WCAG/APCA** | +| **Dark/Light/Dusk** | ❌ Solo dark | ✅ Light/Dark | ✅ **+ Dusk transition mode** | +| **Accesibilidad** | ❌ | ❌ | ✅ **WCAG 4.5:1 + APCA 75** | +| **AI Session Prompt** | ❌ | ❌ | ✅ **Bleeding edge en 2026** | +| **Neovim Plugins** | ✅ 29 (LazyVim) | ❌ | 🔶 Dreamcoder colorscheme | +| **Hyprland Config** | ❌ | ✅ Completo | 🔶 Color overlay | +| **Shell Configs** | ✅ Fish/Zsh/Nushell | ✅ Fish/Bash | 🔶 Aliases + 9 functions | +| **Ghostty Shaders** | ✅ 45+ GLSL | ❌ | 🔶 Usa los de Gentleman | +| **Go TUI Installer** | ✅ Bubbletea | ✅ bash | ✅ Bubbletea + Vim Trainer | +| **Starship Prompt** | ❌ Básico | ❌ Básico | ✅ **23 módulos + AI state** | +| **Python Theme Lib** | ❌ | ❌ | ✅ **Publicada en PyPI** | +| **Homebrew Formula** | ✅ | ❌ | ✅ | +| **Vim Trainer** | ✅ RPG-style | ❌ | ✅ RPG-style | +| **Auto Theme Switch** | ❌ | ✅ Manual | ✅ **Systemd timer** | +| **CI/CD** | ❌ | ❌ | ✅ GitHub Actions | + +> ✅ = lo provee · 🔶 = dreamcoder overlay · ❌ = no lo provee + +--- + +## Lo que se queda de Gentleman.Dots + +Después de instalar Dreamcoder, TODO lo de Gentleman sigue funcionando exactamente igual: + +| Componente | Qué se queda | Cómo lo usa Dreamcoder | +| -------------------- | --------------------------------- | --------------------------------------------------- | +| **Neovim** | init.lua + 29 plugins | Agrega `colorscheme dreamcoder` al init.lua | +| **Ghostty shaders** | 45+ GLSL | Usa `dreamcoder-cursor-pulse.glsl` por defecto | +| **Tmux** | `~/.tmux.conf` con TPM + kanagawa | `apply-theme-mode.sh` sobreescribe colores kanagawa | +| **Zellij** | `config.kdl` con plugins | Cambia `theme "dreamcoder-{mode}"` | +| **Fish/Zsh/Nushell** | Configs base | Agrega `conf.d/dreamcoder-*.fish` | +| **Vim Trainer** | RPG completo | Sin cambios | +| **Starship** | Config base (si existe) | Reemplaza con 23 módulos | + +## Lo que se queda de ML4W + +| Componente | Qué se queda | Cómo lo usa Dreamcoder | +| --------------- | -------------------------------------- | ------------------------------------------------- | +| **Hyprland** | hyprland.conf + animaciones + keybinds | Agrega `colors.{conf,lua}` con variables de color | +| **Waybar** | `config.jsonc` + `style.css` | Genera `colors.css` con `@import` | +| **Rofi** | `config.rasi` | Genera `colors.rasi` con `@import` | +| **Dunst** | `dunstrc` | Genera `dreamcoder-dunst.conf` con `[include]` | +| **GTK 3.0/4.0** | `settings.ini` | Switchea `prefer-dark-theme` en runtime | +| **Btop** | `btop.conf` | Agrega `dreamcoder.theme` | +| **Matugen** | Pipeline de colores | Dreamcoder corre matugen en cada mode switch | + +--- + +## Lo que Dreamcoder AGREGA + +### Token Engine with WCAG/APCA + +```json +{ + "guardrails": { + "minimum_text_contrast": 4.5, + "minimum_apca_body": 75, + "avoid_pure_black_white": true + } +} +``` + +Ningún otro dotfiles hace esto. Cada color está validado contra WCAG 4.5:1 y APCA. + +### 3 Modos de Color + +| Modo | Nombre | BG | Accent | Uso | +| -------- | --------------- | --------- | --------- | ----------- | +| 🌙 Dark | Ember Noir OLED | `#100f0d` | `#d99555` | 18:00-07:00 | +| ☀️ Light | Cocoa/Lúcuma | `#f3eadc` | `#824f16` | 07:00-16:00 | +| 🌆 Dusk | Transición | `#ebe4d6` | `#8a5520` | 16:00-18:00 | + +### AI Session State en el Prompt + +Cuando Claude Code o OpenCode están activos, el prompt muestra: + +``` +⎔ claude-4 42K +``` + +(solo visible cuando hay una sesión activa) + +### 9 Shell Functions + +| Función | Para qué | +| ------------ | --------------------------------- | +| `extract` | Descomprime CUALQUIER formato | +| `sysupdate` | Actualiza pacman + brew + flatpak | +| `ports` | Muestra puertos en escucha | +| `killport` | Mata proceso en un puerto | +| `dots` | Cd al repo dreamcoder | +| `cheat` | TLDR para help rápido | +| `http` | HTTPie wrapper | +| `logs` | Journalctl wrapper | +| `tm-session` | Tmux session picker | + +### Modern CLI Aliases + +```bash +alias ll='eza -la --icons' # mejor ls +alias cat='bat --paging=never' # mejor cat +alias find='fd' # mejor find +alias grep='rg' # mejor grep +alias cd='z' # zoxide smart cd +alias ps='procs' # mejor ps +alias top='btm' # mejor htop +alias du='dua' # mejor du +alias df='duf' # mejor df +alias sed='sd' # mejor sed +alias help='tldr' # mejor man +``` + +Todos con graceful fallback si la herramienta no está instalada. + +--- + +## Resumen Visual + +``` +Sin Dreamcoder: +┌─────────────────────────────────────────────┐ +│ Gentleman.Dots: Catppuccin │ +│ ML4W: Matugen colors │ +│ Sin WCAG, sin AI prompt, sin dusk mode │ +└─────────────────────────────────────────────┘ + +Con Dreamcoder: +┌─────────────────────────────────────────────┐ +│ Gentleman.Dots: Neovim, shaders, tmux │ +│ ML4W: Hyprland, waybar, rofi, dunst │ +│ Dreamcoder: tokens WCAG/APCA + AI prompt │ +└─────────────────────────────────────────────┘ +``` diff --git a/Codex-App/Dreamcoder-Dark.codex-theme.json b/Codex-App/Dreamcoder-Dark.codex-theme.json index 12cc139..0557a29 100644 --- a/Codex-App/Dreamcoder-Dark.codex-theme.json +++ b/Codex-App/Dreamcoder-Dark.codex-theme.json @@ -19,9 +19,9 @@ "background": "#100f0d", "backgroundPanel": "#201b16", "backgroundElement": "#2b231b", - "backgroundHover": "#392e21", - "backgroundSelected": "#d99555", - "textSelected": "#100f0d", + "backgroundHover": "#2b231b", + "backgroundSelected": "#2b231b", + "textSelected": "#e8dfd0", "backgroundCode": "#231d18", "backgroundSearch": "#8d7141", "backgroundLine": "#181512", @@ -54,13 +54,13 @@ "diffHunkHeader": "#d4b4e6", "diffHighlightAdded": "#4db35f", "diffHighlightRemoved": "#ed8a7a", - "diffAddedBg": "#67a468", - "diffRemovedBg": "#cf8979", + "diffAddedBg": "#395437", + "diffRemovedBg": "#68473f", "diffContextBg": "#100f0d", "diffLineNumber": "#938274", - "diffAddedLineNumberBg": "#67a468", - "diffRemovedLineNumberBg": "#cf8979", - "diffHunkHeaderBg": "#b8a0b4", + "diffAddedLineNumberBg": "#395437", + "diffRemovedLineNumberBg": "#68473f", + "diffHunkHeaderBg": "#544b51", "diffFold": "#b8a99a", "diffFoldBg": "#181512", "markdownText": "#e8dfd0", diff --git a/Codex-App/Dreamcoder-Light.codex-theme.json b/Codex-App/Dreamcoder-Light.codex-theme.json index f79d62f..032fcab 100644 --- a/Codex-App/Dreamcoder-Light.codex-theme.json +++ b/Codex-App/Dreamcoder-Light.codex-theme.json @@ -19,8 +19,8 @@ "background": "#f3eadc", "backgroundPanel": "#fff7ea", "backgroundElement": "#e3d2bc", - "backgroundHover": "#c8ad89", - "backgroundSelected": "#f3eadc", + "backgroundHover": "#decbb1", + "backgroundSelected": "#decbb1", "textSelected": "#17120d", "backgroundCode": "#d0bca3", "backgroundSearch": "#a1895c", @@ -54,13 +54,13 @@ "diffHunkHeader": "#57478b", "diffHighlightAdded": "#3d723d", "diffHighlightRemoved": "#842f24", - "diffAddedBg": "#7d9c75", - "diffRemovedBg": "#ab7064", + "diffAddedBg": "#a7b899", + "diffRemovedBg": "#c49b8f", "diffContextBg": "#f3eadc", "diffLineNumber": "#554638", - "diffAddedLineNumberBg": "#7d9c75", - "diffRemovedLineNumberBg": "#ab7064", - "diffHunkHeaderBg": "#9d90af", + "diffAddedLineNumberBg": "#a7b899", + "diffRemovedLineNumberBg": "#c49b8f", + "diffHunkHeaderBg": "#b8acbd", "diffFold": "#725e4c", "diffFoldBg": "#e6d7c4", "markdownText": "#17120d", diff --git a/Codex-App/Dreamcoder.codex-theme.json b/Codex-App/Dreamcoder.codex-theme.json index f79d62f..032fcab 100644 --- a/Codex-App/Dreamcoder.codex-theme.json +++ b/Codex-App/Dreamcoder.codex-theme.json @@ -19,8 +19,8 @@ "background": "#f3eadc", "backgroundPanel": "#fff7ea", "backgroundElement": "#e3d2bc", - "backgroundHover": "#c8ad89", - "backgroundSelected": "#f3eadc", + "backgroundHover": "#decbb1", + "backgroundSelected": "#decbb1", "textSelected": "#17120d", "backgroundCode": "#d0bca3", "backgroundSearch": "#a1895c", @@ -54,13 +54,13 @@ "diffHunkHeader": "#57478b", "diffHighlightAdded": "#3d723d", "diffHighlightRemoved": "#842f24", - "diffAddedBg": "#7d9c75", - "diffRemovedBg": "#ab7064", + "diffAddedBg": "#a7b899", + "diffRemovedBg": "#c49b8f", "diffContextBg": "#f3eadc", "diffLineNumber": "#554638", - "diffAddedLineNumberBg": "#7d9c75", - "diffRemovedLineNumberBg": "#ab7064", - "diffHunkHeaderBg": "#9d90af", + "diffAddedLineNumberBg": "#a7b899", + "diffRemovedLineNumberBg": "#c49b8f", + "diffHunkHeaderBg": "#b8acbd", "diffFold": "#725e4c", "diffFoldBg": "#e6d7c4", "markdownText": "#17120d", diff --git a/Codex-CLI/Dreamcoder-Dark.tmTheme b/Codex-CLI/Dreamcoder-Dark.tmTheme index 6f12720..cbf424a 100644 --- a/Codex-CLI/Dreamcoder-Dark.tmTheme +++ b/Codex-CLI/Dreamcoder-Dark.tmTheme @@ -10,8 +10,8 @@ background#100f0d foreground#e8dfd0 caret#d99555 - selection#d99555 - selectionForeground#100f0d + selection#2b231b + selectionForeground#e8dfd0 lineHighlight#2b231b gutter#392e21 gutterForeground#c7b9aa diff --git a/Codex-CLI/Dreamcoder-Light.tmTheme b/Codex-CLI/Dreamcoder-Light.tmTheme index 0b12d83..057aff6 100644 --- a/Codex-CLI/Dreamcoder-Light.tmTheme +++ b/Codex-CLI/Dreamcoder-Light.tmTheme @@ -10,7 +10,7 @@ background#f3eadc foreground#17120d caret#824f16 - selection#f3eadc + selection#decbb1 selectionForeground#17120d lineHighlight#decbb1 gutter#c8ad89 diff --git a/Codex-CLI/Dreamcoder.tmTheme b/Codex-CLI/Dreamcoder.tmTheme index 0b12d83..057aff6 100644 --- a/Codex-CLI/Dreamcoder.tmTheme +++ b/Codex-CLI/Dreamcoder.tmTheme @@ -10,7 +10,7 @@ background#f3eadc foreground#17120d caret#824f16 - selection#f3eadc + selection#decbb1 selectionForeground#17120d lineHighlight#decbb1 gutter#c8ad89 diff --git a/Ghostty/.config/ghostty/shaders/dreamcoder-cursor-pulse.glsl b/Ghostty/.config/ghostty/shaders/dreamcoder-cursor-pulse.glsl index 53f7324..b431a0c 100644 --- a/Ghostty/.config/ghostty/shaders/dreamcoder-cursor-pulse.glsl +++ b/Ghostty/.config/ghostty/shaders/dreamcoder-cursor-pulse.glsl @@ -1,47 +1,57 @@ -// Dreamcoder Motion cursor pulse for Ghostty. -// Lightweight shader: only reacts around the terminal cursor and fades quickly. - -float sdfRectangle(in vec2 p, in vec2 center, in vec2 halfSize) { - vec2 d = abs(p - center) - halfSize; - return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0); -} - -vec2 normalizePosition(vec2 value, float isPosition) { - return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y; -} - -float antialias(float distance) { - return 1.0 - smoothstep(0.0, normalizePosition(vec2(2.0), 0.0).x, distance); -} - -void mainImage(out vec4 fragColor, in vec2 fragCoord) { - vec2 uv = fragCoord.xy / iResolution.xy; - fragColor = texture(iChannel0, uv); - - vec4 cursor = vec4( - normalizePosition(iCurrentCursor.xy, 1.0), - normalizePosition(iCurrentCursor.zw, 0.0) - ); - - vec2 cursorCenter = vec2( - cursor.x + cursor.z * 0.5, - cursor.y - cursor.w * 0.5 - ); - vec2 point = normalizePosition(fragCoord, 1.0); - - float elapsed = iTime - iTimeCursorChange; - float pulse = 0.5 + 0.5 * sin(iTime * 5.2); - float settle = 1.0 - smoothstep(0.18, 0.42, elapsed); - - float cursorSdf = sdfRectangle(point, cursorCenter, cursor.zw * 0.5); - float glowSdf = sdfRectangle(point, cursorCenter, cursor.zw * (0.85 + pulse * 0.35)); - - vec3 accent = iCurrentCursorColor.rgb; - vec3 warmAccent = mix(accent, vec3(1.0, 0.66, 0.34), 0.22); - - float core = antialias(cursorSdf); - float glow = (1.0 - smoothstep(0.0, 0.028, glowSdf)) * (0.16 + pulse * 0.10) * settle; - - fragColor.rgb = mix(fragColor.rgb, warmAccent, glow); - fragColor.rgb = mix(fragColor.rgb, accent, core * 0.08); +// Dreamcoder Cursor Pulse — custom GLSL shader for Ghostty +// Pulses the cursor with the dreamcoder accent color (#d99555 dark, #824f16 light) +// and adds a subtle warm glow trail. +// +// Install: set `custom-shader = shaders/dreamcoder-cursor-pulse.glsl` in ghostty config + +#version 330 + +// --- Uniforms (provided by Ghostty) --- +uniform float u_time; +uniform vec2 u_resolution; +uniform vec2 u_cursor_position; +uniform vec2 u_cursor_size; +uniform bool u_cursor_visible; + +// --- Dreamcoder palette --- +// Dark mode accent: #d99555 → vec3(0.851, 0.584, 0.333) +// Light mode accent: #824f16 → vec3(0.510, 0.310, 0.086) +// We use dark mode by default; Ghostty can pass the theme color. + +vec3 accent = vec3(0.851, 0.584, 0.333); // #d99555 +vec3 glow = vec3(0.851, 0.584, 0.333); // warm gold glow + +// --- Pixel input from previous stage --- +in vec2 f_uv; +out vec4 fragColor; +uniform sampler2D f_texture; + +void main() { + vec4 color = texture(f_texture, f_uv); + + if (u_cursor_visible) { + // Calculate distance from cursor + vec2 cursor_uv = u_cursor_position / u_resolution; + vec2 cursor_size_uv = u_cursor_size / u_resolution; + vec2 delta = abs(f_uv - cursor_uv) / cursor_size_uv; + + // Check if we're near the cursor area + if (delta.x < 1.5 && delta.y < 1.5) { + // Smooth pulse based on time + float pulse = 0.5 + 0.5 * sin(u_time * 3.0); + float dist = length(delta); + + // Warm glow behind cursor + float glow_intensity = 0.15 * (1.0 - smoothstep(0.0, 1.5, dist)) * pulse; + color.rgb += glow * glow_intensity; + + // Subtle accent tint on cursor itself + if (dist < 0.8) { + float tint = 0.08 * (1.0 - smoothstep(0.0, 0.8, dist)) * pulse; + color.rgb = mix(color.rgb, accent, tint); + } + } + } + + fragColor = color; } diff --git a/Ghostty/.config/ghostty/themes/dreamcoder b/Ghostty/.config/ghostty/themes/dreamcoder index 1c71725..b20ae7c 100644 --- a/Ghostty/.config/ghostty/themes/dreamcoder +++ b/Ghostty/.config/ghostty/themes/dreamcoder @@ -2,7 +2,7 @@ background = #f3eadc foreground = #17120d cursor-color = #824f16 -cursor-text = #f3eadc +cursor-text = #fff7ea selection-background = #decbb1 selection-foreground = #17120d background-opacity = 0.96 @@ -18,10 +18,10 @@ palette = 5=#7d3e64 palette = 6=#57478b palette = 7=#352e22 palette = 8=#554638 -palette = 9=#815850 -palette = 10=#4f6f51 +palette = 9=#702a20 +palette = 10=#366134 palette = 11=#654300 -palette = 12=#3a686e -palette = 13=#6c5ba2 -palette = 14=#4b6f6f +palette = 12=#0f4058 +palette = 13=#4b3d74 +palette = 14=#10565e palette = 15=#17120d diff --git a/Ghostty/.config/ghostty/themes/dreamcoder-dark b/Ghostty/.config/ghostty/themes/dreamcoder-dark index 3fb62cc..d189dae 100644 --- a/Ghostty/.config/ghostty/themes/dreamcoder-dark +++ b/Ghostty/.config/ghostty/themes/dreamcoder-dark @@ -18,10 +18,10 @@ palette = 5=#e29cb4 palette = 6=#d4b4e6 palette = 7=#c7b9aa palette = 8=#938274 -palette = 9=#e9a092 -palette = 10=#75a579 +palette = 9=#ec9989 +palette = 10=#69bb73 palette = 11=#e8b866 -palette = 12=#579ba4 -palette = 13=#846fc5 -palette = 14=#6fa5a5 +palette = 12=#78a2cb +palette = 13=#d8bce2 +palette = 14=#789d9b palette = 15=#e8dfd0 diff --git a/Ghostty/.config/ghostty/themes/dreamcoder-light b/Ghostty/.config/ghostty/themes/dreamcoder-light index 1c71725..b20ae7c 100644 --- a/Ghostty/.config/ghostty/themes/dreamcoder-light +++ b/Ghostty/.config/ghostty/themes/dreamcoder-light @@ -2,7 +2,7 @@ background = #f3eadc foreground = #17120d cursor-color = #824f16 -cursor-text = #f3eadc +cursor-text = #fff7ea selection-background = #decbb1 selection-foreground = #17120d background-opacity = 0.96 @@ -18,10 +18,10 @@ palette = 5=#7d3e64 palette = 6=#57478b palette = 7=#352e22 palette = 8=#554638 -palette = 9=#815850 -palette = 10=#4f6f51 +palette = 9=#702a20 +palette = 10=#366134 palette = 11=#654300 -palette = 12=#3a686e -palette = 13=#6c5ba2 -palette = 14=#4b6f6f +palette = 12=#0f4058 +palette = 13=#4b3d74 +palette = 14=#10565e palette = 15=#17120d diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..8710793 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,177 @@ +# Dreamcoder OS — Installation Guide + +> Instalación completa en 3 pasos: Gentleman.Dots → ML4W → Dreamcoder. + +## Prerequisitos + +- **Arch Linux** (o derivado) +- `git`, `stow`, `python3` instalados +- Conexión a internet + +--- + +## Paso 1: Gentleman.Dots + +Gentleman.Dots provee la base para Neovim, shells, terminales, y multiplexers. + +### Opción A: Homebrew (recomendado) + +```bash +brew install Gentleman-Programming/tap/gentleman-dots +gentleman-dots +``` + +### Opción B: Descarga directa + +```bash +curl -fsSL https://github.com/Gentleman-Programming/Gentleman.Dots/releases/latest/download/gentleman-installer-linux-amd64 -o gentleman.dots +chmod +x gentleman.dots +./gentleman.dots +``` + +### Opción C: Manual + +```bash +git clone https://github.com/Gentleman-Programming/Gentleman.Dots.git +cd Gentleman.Dots +./install.sh +``` + +Seguí el installer TUI para seleccionar: + +- Shell: Fish (recomendado), Zsh, o Nushell +- Terminal: Ghostty (recomendado) o Kitty +- Multiplexer: Tmux o Zellij +- Editor: Neovim + +> Más info: [Gentleman.Dots](https://github.com/Gentleman-Programming/Gentleman.Dots) + +--- + +## Paso 2: ML4W OS + +ML4W provee la base completa de escritorio Hyprland. + +```bash +bash <(curl -s https://ml4w.com/os/stable) +``` + +Esto instala: + +- **Hyprland**: animaciones, keybinds, monitores, layouts, ventanas, workspaces +- **Waybar**: barra de estado con módulos +- **Rofi**: lanzador de apps +- **Dunst**: notificaciones +- **GTK 3.0/4.0**: tema y config +- **Btop**: monitor de sistema +- **Matugen**: generación dinámica de colores + +> Más info: [ML4W OS](https://ml4w.com/os/) + +--- + +## Paso 3: Dreamcoder + +Dreamcoder aplica su capa visual sobre Gentleman + ML4W. + +### Clonar + +```bash +git clone git@github.com:Dreamcoder08/Dreamcoder_dots.git ~/Documents/PROYECTOS/dreamcoder-dots +cd ~/Documents/PROYECTOS/dreamcoder-dots +``` + +### Instalar + +```bash +./scripts/dreamcoder install +``` + +Esto: + +1. Crea backups de configs existentes +2. Stowea los módulos (Shell, Kitty, Ghostty, Fastfetch, Warp, Bat, Systemd) +3. Instala los hooks de tema para cada app +4. Activa el timer systemd para auto-theme-switching +5. Corre el primer sync de temas + +### Verificar + +```bash +./scripts/dreamcoder doctor # Health check completo +./scripts/dreamcoder status # System status +``` + +--- + +## Post-Instalación + +### Tmux + +Si usás Tmux con TPM: + +```bash +tmux +prefix + I # Instalar plugins +``` + +### Neovim + +Abrí Neovim. LazyVim instala los plugins automáticamente: + +```bash +nvim +:Lazy sync +``` + +### Auto Theme Switching + +El timer systemd se activa automáticamente. Para verificar: + +```bash +systemctl --user status dreamcoder-theme-auto.timer +``` + +El timer cambia entre modos según el horario: + +- **07:00-16:00** → Light +- **16:00-18:00** → Dusk +- **18:00-07:00** → Dark + +--- + +## Troubleshooting + +### "Theme files not found" + +Asegurate de haber instalado Gentleman.Dots y ML4W primero. Dreamcoder es un overlay, no un reemplazo. + +### "Error: stow not found" + +```bash +sudo pacman -S stow +``` + +### "Colors not updating" + +```bash +dreamcoder dark # Force dark mode +dreamcoder light # Force light mode +``` + +### "Neovim colorscheme not loading" + +Agregá esto a `~/.config/nvim/init.lua`: + +```lua +vim.cmd.colorscheme("dreamcoder") +``` + +--- + +## Referencias + +- [Gentleman.Dots](https://github.com/Gentleman-Programming/Gentleman.Dots) +- [ML4W OS](https://ml4w.com/os/) +- [Dreamcoder SDD Plans](docs/superpowers/plans/) +- [Architecture Docs](docs/architecture/) diff --git a/Kitty/.config/kitty/colors-dreamcoder-dark.conf b/Kitty/.config/kitty/colors-dreamcoder-dark.conf index 05b4b52..f4ea373 100644 --- a/Kitty/.config/kitty/colors-dreamcoder-dark.conf +++ b/Kitty/.config/kitty/colors-dreamcoder-dark.conf @@ -7,7 +7,7 @@ foreground #e8dfd0 background #100f0d selection_foreground #e8dfd0 selection_background #2b231b -url_color #5f95ca +url_color #d99555 cursor #d99555 cursor_text_color #100f0d @@ -34,19 +34,19 @@ color5 #e29cb4 color6 #d4b4e6 color7 #c7b9aa color8 #938274 -color9 #e9a092 -color10 #75a579 +color9 #ec9989 +color10 #69bb73 color11 #e8b866 -color12 #579ba4 -color13 #846fc5 -color14 #6fa5a5 +color12 #78a2cb +color13 #d8bce2 +color14 #789d9b color15 #e8dfd0 color16 #c96a45 color17 #ed8a7a mark1_foreground #100f0d mark1_background #d99555 -mark2_foreground #100f0d +mark2_foreground #e8dfd0 mark2_background #5f95ca -mark3_foreground #100f0d +mark3_foreground #e8dfd0 mark3_background #e29cb4 diff --git a/Kitty/.config/kitty/colors-dreamcoder-light.conf b/Kitty/.config/kitty/colors-dreamcoder-light.conf index e00731e..be98492 100644 --- a/Kitty/.config/kitty/colors-dreamcoder-light.conf +++ b/Kitty/.config/kitty/colors-dreamcoder-light.conf @@ -7,15 +7,15 @@ foreground #17120d background #f3eadc selection_foreground #17120d selection_background #decbb1 -url_color #0d4a68 +url_color #824f16 cursor #824f16 -cursor_text_color #f3eadc +cursor_text_color #fff7ea cursor_shape block cursor_blink_interval 0.5 cursor_stop_blinking_after 15.0 -active_tab_foreground #f3eadc +active_tab_foreground #fff7ea active_tab_background #824f16 inactive_tab_foreground #352e22 inactive_tab_background #f3eadc @@ -34,19 +34,19 @@ color5 #7d3e64 color6 #57478b color7 #352e22 color8 #554638 -color9 #815850 -color10 #4f6f51 +color9 #702a20 +color10 #366134 color11 #654300 -color12 #3a686e -color13 #6c5ba2 -color14 #4b6f6f +color12 #0f4058 +color13 #4b3d74 +color14 #10565e color15 #17120d color16 #a7471c color17 #842f24 -mark1_foreground #f3eadc +mark1_foreground #fff7ea mark1_background #824f16 -mark2_foreground #f3eadc +mark2_foreground #17120d mark2_background #0d4a68 -mark3_foreground #f3eadc +mark3_foreground #17120d mark3_background #7d3e64 diff --git a/Kitty/.config/kitty/colors-dreamcoder.conf b/Kitty/.config/kitty/colors-dreamcoder.conf index e00731e..be98492 100644 --- a/Kitty/.config/kitty/colors-dreamcoder.conf +++ b/Kitty/.config/kitty/colors-dreamcoder.conf @@ -7,15 +7,15 @@ foreground #17120d background #f3eadc selection_foreground #17120d selection_background #decbb1 -url_color #0d4a68 +url_color #824f16 cursor #824f16 -cursor_text_color #f3eadc +cursor_text_color #fff7ea cursor_shape block cursor_blink_interval 0.5 cursor_stop_blinking_after 15.0 -active_tab_foreground #f3eadc +active_tab_foreground #fff7ea active_tab_background #824f16 inactive_tab_foreground #352e22 inactive_tab_background #f3eadc @@ -34,19 +34,19 @@ color5 #7d3e64 color6 #57478b color7 #352e22 color8 #554638 -color9 #815850 -color10 #4f6f51 +color9 #702a20 +color10 #366134 color11 #654300 -color12 #3a686e -color13 #6c5ba2 -color14 #4b6f6f +color12 #0f4058 +color13 #4b3d74 +color14 #10565e color15 #17120d color16 #a7471c color17 #842f24 -mark1_foreground #f3eadc +mark1_foreground #fff7ea mark1_background #824f16 -mark2_foreground #f3eadc +mark2_foreground #17120d mark2_background #0d4a68 -mark3_foreground #f3eadc +mark3_foreground #17120d mark3_background #7d3e64 diff --git a/Nvim/.config/nvim/colors/dreamcoder-dark.lua b/Nvim/.config/nvim/colors/dreamcoder-dark.lua index 98c130f..93d6f40 100644 --- a/Nvim/.config/nvim/colors/dreamcoder-dark.lua +++ b/Nvim/.config/nvim/colors/dreamcoder-dark.lua @@ -708,17 +708,17 @@ end }) vim.api.nvim_set_hl(0, "@markup.link", { - fg = "#5f95ca", + fg = "#d99555", underline = true }) vim.api.nvim_set_hl(0, "@markup.link.label", { - fg = "#5f95ca", + fg = "#d99555", underline = true }) vim.api.nvim_set_hl(0, "@markup.link.url", { - fg = "#938274", + fg = "#c96a45", underline = true }) diff --git a/Nvim/.config/nvim/colors/dreamcoder-light.lua b/Nvim/.config/nvim/colors/dreamcoder-light.lua index 8e2f66b..d5f3d43 100644 --- a/Nvim/.config/nvim/colors/dreamcoder-light.lua +++ b/Nvim/.config/nvim/colors/dreamcoder-light.lua @@ -708,17 +708,17 @@ end }) vim.api.nvim_set_hl(0, "@markup.link", { - fg = "#0d4a68", + fg = "#824f16", underline = true }) vim.api.nvim_set_hl(0, "@markup.link.label", { - fg = "#0d4a68", + fg = "#824f16", underline = true }) vim.api.nvim_set_hl(0, "@markup.link.url", { - fg = "#554638", + fg = "#a7471c", underline = true }) diff --git a/Pi/.pi/agent/scripts/pi-theme.sh b/Pi/.pi/agent/scripts/pi-theme.sh new file mode 100755 index 0000000..51c7754 --- /dev/null +++ b/Pi/.pi/agent/scripts/pi-theme.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +DD="${DOTS_DIR:-$HOME/Documents/PROYECTOS/dreamcoder-dots}" +LN="$HOME/.pi/agent/themes/dreamcoder.json" + +detect() { + if command -v gsettings &>/dev/null; then + s="$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null || true)" + [[ "$s" == *prefer-dark* ]] && { + echo dark + return + } + [[ "$s" == *default* || "$s" == *prefer-light* ]] && { + echo light + return + } + fi + if [[ -f "$HOME/.theme-mode" ]]; then + m="$(<"$HOME/.theme-mode")" + [[ "$m" == dark || "$m" == light ]] && { + echo "$m" + return + } + fi + if [[ -n "${COLORFGBG:-}" ]]; then + bg="${COLORFGBG##*;}" + [[ "$bg" -lt 8 ]] && echo light || echo dark + return + fi + echo light +} + +mode="$(detect)" +src="$DD/Pi/.pi/agent/themes/dreamcoder-$mode.json" +[[ -f "$src" ]] || { + echo "✗ $src not found" >&2 + exit 1 +} +mkdir -p "$(dirname "$LN")" +ln -sf "$src" "$LN" +echo "→ pi-theme: $mode" diff --git a/README.md b/README.md index 14ceaed..a9d444e 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,40 @@ # Dreamcoder OS +> Visual layer premium para **Gentleman.Dots + ML4W**. +> Café/Lúcuma. Ember Noir. Contraste saludable. Identidad. + [![CI](https://github.com/Gentleman-Programming/dreamcoder-dots/actions/workflows/theme-validation.yml/badge.svg)](https://github.com/Gentleman-Programming/dreamcoder-dots/actions/workflows/theme-validation.yml) -[![Coverage](https://img.shields.io/badge/coverage-%3E40%25-brightgreen)]() +[![WCAG 4.5:1](https://img.shields.io/badge/WCAG-4.5%3A1-brightgreen)]() +[![APCA](https://img.shields.io/badge/APCA-75-brightgreen)]() [![PyPI](https://img.shields.io/pypi/v/dreamcoder-theme)](https://pypi.org/project/dreamcoder-theme/) -[![Python](https://img.shields.io/pypi/pyversions/dreamcoder-theme)](https://pypi.org/project/dreamcoder-theme/) -[![License](https://img.shields.io/pypi/l/dreamcoder-theme)](https://github.com/Gentleman-Programming/dreamcoder-dots/blob/main/LICENSE) +[![License](https://img.shields.io/pypi/l/dreamcoder-theme)](./LICENSE) -> Personal Arch Linux dotfiles — a visual layer on top of ML4W/Gentleman Dots focused on readability, eye comfort, and a premium coding experience. +--- -Personal Arch Linux dotfiles for the Dreamcoder identity: a visual layer on top of -ML4W/Gentleman Dots focused on readability, eye comfort, and a premium coding experience. +## Quick Start — 3-Step Install -```mermaid -flowchart LR - subgraph Tokens["Design Tokens"] - PT["palette_tokens.py
dark / light / dusk"] - end - subgraph Render["Renderers (28+ targets)"] - R["renderers.py
hub → leaf modules"] - end - subgraph Write["Writers"] - W["writers.py
write_if_changed()"] - end - subgraph Output["Theme Files"] - O["Kitty, Ghostty, Nvim,
Tmux, Starship, Hyprland,
Codex, OpenCode, Pi, ..."] - end - Tokens --> Render --> Write --> Output +### 1. Install Gentleman.Dots + +```bash +brew install Gentleman-Programming/tap/gentleman-dots +gentleman-dots ``` ---- +→ O descargá desde [Gentleman.Dots](https://github.com/Gentleman-Programming/Gentleman.Dots) -## Installation +**Te da**: Neovim (29 plugins LazyVim), Ghostty shaders (45+), Tmux/Zellij, Vim Trainer, Fish/Zsh/Nushell -### Theme Engine (Python package) +### 2. Install ML4W OS ```bash -pip install dreamcoder-theme +bash <(curl -s https://ml4w.com/os/stable) ``` -### Full Dotfiles +→ [Documentación ML4W](https://ml4w.com/os/) + +**Te da**: Hyprland completo (animaciones, keybinds, monitores), Waybar, Rofi, Dunst, GTK, Btop + +### 3. Install Dreamcoder ```bash git clone git@github.com:Dreamcoder08/Dreamcoder_dots.git ~/Documents/PROYECTOS/dreamcoder-dots @@ -46,121 +42,127 @@ cd ~/Documents/PROYECTOS/dreamcoder-dots ./scripts/dreamcoder install ``` ---- +**Aplica**: Dreamcoder dark/light/dusk sobre toda la base de Gentleman + ML4W -## Usage +**Agrega**: Starship prompt con AI session state, 9 funciones shell, aliases modernos, auto-theme-switching -### CLI - -```bash -# Render all themes to your config dirs -dreamcoder-theme sync - -# Check health of installed themes -dreamcoder-theme doctor - -# Inspect active theme paths -dreamcoder-theme paths -``` +--- -### Library +## ¿Por qué Dreamcoder? -```python -from dreamcoder_theme.palette import adaptive_palette -from dreamcoder_theme.renderers import kitty_content, ghostty_content +| Feature | Gentleman.Dots | ML4W | **dreamcoder-dots** | +| --------------------- | ------------------- | ------------ | ------------------------------ | +| **Theme Engine** | ❌ Catppuccin | ✅ Matugen | ✅ **Token-based + WCAG/APCA** | +| **Light/Dark/Dusk** | ❌ | ✅ | ✅ **+ Dusk transition** | +| **AI Session Prompt** | ❌ | ❌ | ✅ **Bleeding edge** | +| **Accesibilidad** | ❌ | ❌ | ✅ **WCAG 4.5:1 + APCA 75** | +| **Neovim** | ✅ 29 plugins | ❌ | 🔶 Dreamcoder colorscheme | +| **Hyprland** | ❌ | ✅ Completo | 🔶 Color overlay | +| **Shell Configs** | ✅ Fish/Zsh/Nushell | ✅ Fish/Bash | 🔶 Aliases + functions | +| **Ghostty Shaders** | ✅ 45+ GLSL | ❌ | 🔶 Usa los de Gentleman | +| **Installer** | ✅ Go TUI | ✅ bash | ✅ **Go TUI + Vim Trainer** | +| **Prompt** | ❌ Básico | ❌ Básico | ✅ **Starship 23 módulos** | -# Generate adaptive colors from a wallpaper -active = adaptive_palette(variants["dark"], wallpaper="/path/to/wallpaper.jpg") +> ✅ = lo tiene completo · 🔶 = dreamcoder aporta overlay · ❌ = no lo tiene -# Render to specific formats -kitty_conf = kitty_content(active) -ghostty_conf = ghostty_content(active) -``` +--- -### Script commands +## Lo que dreamcoder NO reemplaza -| Command | Description | -|---------|-------------| -| `./scripts/dreamcoder install` | First install / full reapply | -| `./scripts/dreamcoder repair` | After ML4W or Gentleman updates | -| `./scripts/dreamcoder doctor` | Inspect current health/status | -| `./scripts/dreamcoder dark` | Force dark mode | -| `./scripts/dreamcoder light` | Force light mode | -| `./scripts/dreamcoder preview` | Regenerate docs preview | -| `./scripts/set-wallpaper.sh ` | Set wallpaper and refresh | +Dreamcoder es una **capa visual**, no un reemplazo. Mantenés todo lo que ya te dan Gentleman y ML4W: ---- +**De Gentleman.Dots se queda:** -## Architecture +- Neovim con 29 plugins (avante, copilot, blink, fzf-lua, oil, DAP...) +- Ghostty shaders (45+ GLSL effects) +- Tmux/Zellij con TPM y plugins +- Vim Mastery Trainer +- Fish/Zsh/Nushell base config -The theme generation pipeline follows a four-layer flow: +**De ML4W se queda:** -1. **Input Layer** — `palette_tokens.py` defines static design tokens (dark/light/dusk variants) -2. **Transform Layer** — `palette.py` loads variants and applies adaptive palette from wallpaper -3. **Render Layer** — `renderers.py` imports functions from `renderers_*.py` modules that convert tokens to target-specific formats -4. **Write Layer** — `writers.py` writes files via `write_if_changed()` and updates app configurations +- Hyprland completo (animaciones, keybinds, monitores, layouts) +- Waybar, Rofi, Dunst configs +- GTK 3.0/4.0 settings +- Matugen color generation pipeline +- Btop, Chromium/Edge configs -Each renderer is a pure function: receives a color dict and returns a string. No side effects until the write layer. +**Dreamcoder aporta:** -``` -dreamcoder-dots/ -├── src/ -│ ├── dreamcoder_theme/ -│ │ ├── palette_tokens.py # Design tokens -│ │ ├── palette.py # Adaptive palette -│ │ ├── renderers.py # Render hub -│ │ ├── renderers_kitty.py # Kitty renderer -│ │ ├── renderers_ghostty.py # Ghostty renderer -│ │ └── ... -│ └── ... -├── scripts/ # Install/repair/utility scripts -├── docs/ # Architecture docs and previews -└── configs/ # Default configuration files -``` +- Tokens de color validados con WCAG 4.5:1 + APCA +- 3 modos: Ember Noir (dark), Cocoa/Lúcuma (light), Dusk (transición) +- Starship prompt con 23 módulos y AI session state +- 9 funciones shell (extract, sysupdate, killport, etc.) +- Aliases modernos con graceful fallback (eza, bat, fd, rg, zoxide) +- Auto-theme-switching por horario (systemd timer) +- Python library para generación de temas (PyPI) --- -## Tech Stack - -| Layer | Tech | Purpose | -|-------|------|---------| -| Language | Python 3 | Theme engine and CLI | -| Rendering Targets | 28+ targets | Kitty, Ghostty, Nvim, Tmux, Hyprland, and more | -| Design Tokens | Custom palette system | Dark/light/dusk variants | -| Testing | pytest + ruff | Type-safe theme generation | +## Uso ---- +### CLI -## Philosophy +```bash +dreamcoder dark # → Ember Noir OLED +dreamcoder light # → Dreamcoder Light +dreamcoder status # → System status overview +dreamcoder doctor # → Health check +``` -Dreamcoder is not a neon rice. It is a workbench: +### Theme Engine (Python) -- **health first**: no pure black/white primary backgrounds, strong contrast, low glare; -- **daily comfort**: larger terminal type, calmer prompt density, automatic day/night mode; -- **identity second**: Cocoa/Lúcuma warmth, diagnostic cyan, restrained editorial colors; -- **ML4W-compatible**: Dreamcoder owns colors and hooks, ML4W/Gentleman can keep layout behavior. +```bash +pip install dreamcoder-theme +dreamcoder-theme sync # Render all themes +dreamcoder-theme doctor # Check health +``` --- -## Project Status +## Arquitectura -**Status:** Active -**Version:** 1.0 +```mermaid +flowchart LR + subgraph Tokens["Design Tokens"] + PT["palette_tokens.py
dark / light / dusk"] + end + subgraph Render["Renderers (28+ targets)"] + R["renderers.py
hub → leaf modules"] + end + subgraph Write["Writers"] + W["writers.py
write_if_changed()"] + end + subgraph Output["Theme Files"] + O["Kitty, Ghostty, Nvim,
Tmux, Starship, Hyprland,
Codex, OpenCode, Pi, ..."] + end + Tokens --> Render --> Write --> Output +``` --- -## Contributing +## Filosofía + +Dreamcoder no es un rice neon. Es un banco de trabajo: -See [CONTRIBUTING.md](CONTRIBUTING.md). Full documentation at [docs/README.md](docs/README.md). +- **Salud primero**: sin blanco/negro puro, contraste fuerte, bajo brillo +- **Confort diario**: tipografía más grande, densidad de prompt calmada, modo día/noche automático +- **Identidad segundo**: calidez Cocoa/Lúcuma, cyan diagnóstico, colores editoriales --- -## License +## Projecto -MIT — see [LICENSE](LICENSE). +| Aspecto | Detalle | +| -------------- | ---------------------------------- | +| **Estado** | Active | +| **Versión** | 1.0 | +| **Licencia** | MIT | +| **Docs** | [docs/README.md](docs/README.md) | +| **Contribuir** | [CONTRIBUTING.md](CONTRIBUTING.md) | --- ## SDD -This project sits within the [Dreamcoder08](https://github.com/Dreamcoder08) ecosystem. Documentation is maintained in the [SDD Maestro](../arkelythex/sdd/ecosystem-readme-sdd/00-README.md). +Este proyecto usa Spec-Driven Development. Los planes están en [docs/superpowers/plans/](docs/superpowers/plans/). diff --git a/Shell/.config/fish/conf.d/15-dreamcoder-shortcuts.fish b/Shell/.config/fish/conf.d/15-dreamcoder-shortcuts.fish index 66f25d5..33f0457 100644 --- a/Shell/.config/fish/conf.d/15-dreamcoder-shortcuts.fish +++ b/Shell/.config/fish/conf.d/15-dreamcoder-shortcuts.fish @@ -46,7 +46,79 @@ if status is-interactive echo "🎨 Shell theme reloaded: $DREAMCODER_THEME_MODE mode" end - function mkcd --description 'Create a directory and enter it' - mkdir -p -- $argv[1]; and cd -- $argv[1] + ## ─── Git shortcuts ──────────────────────────────────── + alias g='git' + alias gs='git status' + alias gp='git push' + alias gl='git log --oneline --graph --all' + alias gd='git diff' + alias gc='git commit' + alias gco='git checkout' + alias gb='git branch' + alias ga='git add' + alias gpl='git pull' + alias gst='git stash' + alias glg='git log --oneline --graph --all --decorate' + alias gundo='git reset --soft HEAD~1' + alias gcleanup='git branch --merged | grep -v "\*\|main\|master" | xargs -r git branch -d' + alias gconflicts='git diff --name-only --diff-filter=U' + alias groot='git rev-parse --show-toplevel' + + ## ─── Modern CLI replacements ────────────────────────── + # eza (better ls) + if command -q eza + alias ll='eza -la --icons --group-directories-first' + alias la='eza -a --icons --group-directories-first' + alias lt='eza -T --icons --group-directories-first' + alias l1='eza -1 --icons' + else + alias ll='ls -lahF' + alias la='ls -A' + alias lt='tree -C 2>/dev/null; or ls -R' + end + + # bat (better cat) + if command -q bat + alias cat='bat --paging=never' + end + + # fd (better find) + if command -q fd + alias find='fd' + end + + # rg (better grep) + if command -q rg + alias grep='rg' + end + + # zoxide (better cd) + if command -q zoxide + alias cd='z' + alias cdi='zi' + end + + # Modern replacements (graceful fallback) + if command -q procs + alias ps='procs' + end + if command -q btm + alias top='btm' end + if command -q dua + alias du='dua' + end + if command -q duf + alias df='duf' + end + if command -q sd + alias sed='sd' + end + + ## ─── Quick navigation ───────────────────────────────── + alias home='cd ~' + alias ..='cd ..' + alias ...='cd ../..' + alias ....='cd ../../..' + alias docs='cd $DREAMCODER_DOTS_DIR/docs' end diff --git a/Shell/.config/fish/conf.d/25-dreamcoder-ai-env.fish b/Shell/.config/fish/conf.d/25-dreamcoder-ai-env.fish new file mode 100644 index 0000000..194c3c1 --- /dev/null +++ b/Shell/.config/fish/conf.d/25-dreamcoder-ai-env.fish @@ -0,0 +1,46 @@ +# Dreamcoder AI Session State +# Detects active AI coding sessions and writes state for Starship prompt. +# Sources: Claude Code (~/.claude/sessions/), OpenCode (~/.opencode/state) +# +# Starship reads ~/.cache/dreamcoder/ai-session.state via custom.ai_session module. +# This script should be called by fish_prompt or via a systemd path unit. + +set -l CACHE_DIR "$HOME/.cache/dreamcoder" +mkdir -p "$CACHE_DIR" +set -l STATE_FILE "$CACHE_DIR/ai-session.state" + +# Reset state +echo -n >"$STATE_FILE" + +# Check Claude Code sessions +set -l CLAUDE_SESSIONS "$HOME/.claude/sessions" +if test -d "$CLAUDE_SESSIONS" + # Find most recent active session + set -l latest (command ls -t "$CLAUDE_SESSIONS" 2>/dev/null | head -1) + if test -n "$latest" + # Read session metadata for context info + set -l meta_file "$CLAUDE_SESSIONS/$latest/meta.json" + if test -f "$meta_file" + set -l model (jq -r '.model // "claude"' "$meta_file" 2>/dev/null) + set -l tokens (jq -r '.total_tokens // ""' "$meta_file" 2>/dev/null) + if test -n "$model" + if test -n "$tokens" + printf '%s %sK' "$model" (math "$tokens / 1000" 2>/dev/null) >"$STATE_FILE" + else + printf '%s' "$model" >"$STATE_FILE" + end + end + end + end +end + +# Check OpenCode sessions (falls through if Claude didn't write state) +if not test -s "$STATE_FILE" + set -l OPENCODE_STATE "$HOME/.opencode/state" + if test -f "$OPENCODE_STATE" + set -l model (jq -r '.model // "opencode"' "$OPENCODE_STATE" 2>/dev/null) + if test -n "$model" -a "$model" != "null" + printf '%s' "$model" >"$STATE_FILE" + end + end +end diff --git a/Shell/.config/fish/config.fish b/Shell/.config/fish/config.fish index a8e89b4..4e0400c 100644 --- a/Shell/.config/fish/config.fish +++ b/Shell/.config/fish/config.fish @@ -37,3 +37,9 @@ for dir in $PATH end end set -gx PATH $clean_path + +# Zen Browser - Profile aliases +alias zp="zen-browser -P Personal" +alias zd="zen-browser -P Dev" +alias zf="zen-browser -P Founder" +fish_add_path /home/dreamcoder08/.local/share/npm-global/bin diff --git a/Shell/.config/fish/functions/cheat.fish b/Shell/.config/fish/functions/cheat.fish new file mode 100644 index 0000000..7925dbd --- /dev/null +++ b/Shell/.config/fish/functions/cheat.fish @@ -0,0 +1,12 @@ +function cheat --description 'Quick command cheat sheet (tldr wrapper)' + if test (count $argv) -eq 0 + echo "Usage: cheat " + echo "Shows simplified man pages via tldr or man." + return 1 + end + if command -q tldr + tldr $argv + else + man $argv 2>/dev/null; or echo "cheat: install tldr for better results (brew install tldr)" + end +end diff --git a/Shell/.config/fish/functions/dots.fish b/Shell/.config/fish/functions/dots.fish new file mode 100644 index 0000000..652487f --- /dev/null +++ b/Shell/.config/fish/functions/dots.fish @@ -0,0 +1,12 @@ +function dots --description 'cd to dreamcoder-dots repo' + set -l dir $DREAMCODER_DOTS_DIR + if test -z "$dir" + set dir "$HOME/Documents/PROYECTOS/dreamcoder-dots" + end + if test -d "$dir" + cd "$dir" + else + echo "dots: dreamcoder-dots not found at $dir" >&2 + return 1 + end +end diff --git a/Shell/.config/fish/functions/extract.fish b/Shell/.config/fish/functions/extract.fish new file mode 100644 index 0000000..857c2d1 --- /dev/null +++ b/Shell/.config/fish/functions/extract.fish @@ -0,0 +1,40 @@ +function extract --description 'Extract any archive type' + if test (count $argv) -eq 0 + echo "Usage: extract [output_dir]" + return 1 + end + set -l file $argv[1] + set -l dir (basename $file .(echo $file | sed 's/.*\.//')) + if test (count $argv) -ge 2 + set dir $argv[2] + end + switch $file + case '*.tar.gz' '*.tgz' + tar -xzf $file -C (dirname $file) 2>/dev/null; or tar -xzf $file + case '*.tar.bz2' '*.tbz2' + tar -xjf $file + case '*.tar.xz' '*.txz' + tar -xJf $file + case '*.tar.zst' + tar --zstd -xf $file + case '*.tar' + tar -xf $file + case '*.gz' + gunzip -k $file + case '*.bz2' + bunzip2 -k $file + case '*.xz' + unxz -k $file + case '*.zip' + unzip $file -d $dir + case '*.rar' + unrar x $file $dir + case '*.7z' + 7z x $file -o$dir + case '*.zst' + zstd -d $file -o $dir + case '*' + echo "extract: unknown archive type: $file" + return 1 + end +end diff --git a/Shell/.config/fish/functions/http.fish b/Shell/.config/fish/functions/http.fish new file mode 100644 index 0000000..6b33179 --- /dev/null +++ b/Shell/.config/fish/functions/http.fish @@ -0,0 +1,10 @@ +function http --description 'HTTP request with pretty output (httpie wrapper)' + if command -q http + http $argv + else if command -q curl + curl -sI $argv[1] 2>/dev/null; or curl $argv + else + echo "http: install httpie for best results (brew install httpie)" + return 1 + end +end diff --git a/Shell/.config/fish/functions/id-dev.fish b/Shell/.config/fish/functions/id-dev.fish new file mode 100644 index 0000000..fbad85e --- /dev/null +++ b/Shell/.config/fish/functions/id-dev.fish @@ -0,0 +1,3 @@ +function id-dev --description "Switch to Dev identity" + identity dev +end diff --git a/Shell/.config/fish/functions/id-founder.fish b/Shell/.config/fish/functions/id-founder.fish new file mode 100644 index 0000000..ed14e4f --- /dev/null +++ b/Shell/.config/fish/functions/id-founder.fish @@ -0,0 +1,3 @@ +function id-founder --description "Switch to Founder identity" + identity founder +end diff --git a/Shell/.config/fish/functions/id-personal.fish b/Shell/.config/fish/functions/id-personal.fish new file mode 100644 index 0000000..39a07fe --- /dev/null +++ b/Shell/.config/fish/functions/id-personal.fish @@ -0,0 +1,3 @@ +function id-personal --description "Switch to Personal identity" + identity personal +end diff --git a/Shell/.config/fish/functions/id-research.fish b/Shell/.config/fish/functions/id-research.fish new file mode 100644 index 0000000..6430a6d --- /dev/null +++ b/Shell/.config/fish/functions/id-research.fish @@ -0,0 +1,3 @@ +function id-research --description "Switch to Research identity" + identity research +end diff --git a/Shell/.config/fish/functions/identity.fish b/Shell/.config/fish/functions/identity.fish new file mode 100644 index 0000000..91dda56 --- /dev/null +++ b/Shell/.config/fish/functions/identity.fish @@ -0,0 +1,47 @@ +function identity --description "Switch to an identity workspace: personal, founder, dev, research" + switch $argv[1] + case personal + set -l dir ~/Personal + if test -d $dir + cd $dir + end + zen-browser -P Personal & + disown + echo "🟢 Identity: Personal — ~/Personal/" + + case founder + set -l dir ~/Founder + if test -d $dir + cd $dir + end + zen-browser -P Founder & + disown + echo "🚀 Identity: Founder — ~/Founder/" + + case dev + set -l dir ~/Dev + if test -d $dir + cd $dir + end + zen-browser -P Dev & + disown + echo "💻 Identity: Dev — ~/Dev/" + + case research + set -l dir ~/Research + if test -d $dir + cd $dir + end + zen-browser -P Research & + disown + echo "🔬 Identity: Research — ~/Research/" + + case '*' + echo "Usage: identity " + echo "Available identities:" + echo " personal 🟢 ~/Personal/ — Vida privada" + echo " founder 🚀 ~/Founder/ — Marca y presencia pública" + echo " dev 💻 ~/Dev/ — Infraestructura técnica" + echo " research 🔬 ~/Research/ — Exploración y experimental" + end +end diff --git a/Shell/.config/fish/functions/killport.fish b/Shell/.config/fish/functions/killport.fish new file mode 100644 index 0000000..90a19e3 --- /dev/null +++ b/Shell/.config/fish/functions/killport.fish @@ -0,0 +1,20 @@ +function killport --description 'Kill process running on a specific port' + if test (count $argv) -ne 1 + echo "Usage: killport " + return 1 + end + set -l port $argv[1] + set -l pid (lsof -ti :$port 2>/dev/null) + if test -z "$pid" + echo "killport: no process found on port $port" + return 1 + end + echo "Killing PID $pid on port $port..." + kill -15 $pid 2>/dev/null + sleep 0.5 + if kill -0 $pid 2>/dev/null + echo "Process didn't stop, sending SIGKILL..." + kill -9 $pid 2>/dev/null + end + echo "✓ Port $port freed" +end diff --git a/Shell/.config/fish/functions/logs.fish b/Shell/.config/fish/functions/logs.fish new file mode 100644 index 0000000..4ea26e9 --- /dev/null +++ b/Shell/.config/fish/functions/logs.fish @@ -0,0 +1,22 @@ +function logs --description 'Tail system logs with filters' + if test (count $argv) -eq 0 + echo "Usage: logs [service] [options]" + echo "Examples:" + echo " logs Watch all system logs" + echo " logs sshd Watch sshd service" + echo " logs --since 1h Last hour of logs" + return 1 + end + if command -q journalctl + if test (count $argv) -ge 1 + journalctl -fu $argv + else + journalctl -f + end + else if test -f /var/log/syslog + tail -f /var/log/syslog + else + echo "logs: journalctl not available" >&2 + return 1 + end +end diff --git a/Shell/.config/fish/functions/ports.fish b/Shell/.config/fish/functions/ports.fish new file mode 100644 index 0000000..1fb665c --- /dev/null +++ b/Shell/.config/fish/functions/ports.fish @@ -0,0 +1,12 @@ +function ports --description 'Show listening ports and their processes' + if command -q ss + ss -tlnp 2>/dev/null; or ss -tlnp + else if command -q netstat + netstat -tlnp 2>/dev/null + else if command -q lsof + lsof -i -P -n | grep LISTEN + else + echo "ports: install ss (iproute2), netstat, or lsof" >&2 + return 1 + end +end diff --git a/Shell/.config/fish/functions/sysupdate.fish b/Shell/.config/fish/functions/sysupdate.fish new file mode 100644 index 0000000..08c4c84 --- /dev/null +++ b/Shell/.config/fish/functions/sysupdate.fish @@ -0,0 +1,41 @@ +function sysupdate --description 'Update all system packages' + set -l updated 0 + set -l failed 0 + + # Arch Linux + if command -q paru + echo ":: Updating Arch (paru)..." + paru -Syu --noconfirm; and set updated (math $updated + 1); or set failed (math $failed + 1) + else if command -q yay + echo ":: Updating Arch (yay)..." + yay -Syu --noconfirm; and set updated (math $updated + 1); or set failed (math $failed + 1) + else if command -q pacman + echo ":: Updating Arch (pacman)..." + sudo pacman -Syu --noconfirm; and set updated (math $updated + 1); or set failed (math $failed + 1) + end + + # Homebrew + if command -q brew + echo ":: Updating Homebrew..." + brew update && brew upgrade; and set updated (math $updated + 1); or set failed (math $failed + 1) + end + + # Flatpak + if command -q flatpak + echo ":: Updating Flatpaks..." + flatpak update -y; and set updated (math $updated + 1); or set failed (math $failed + 1) + end + + # NPM global + if command -q npm + echo ":: Updating npm global packages..." + npm update -g 2>/dev/null; and set updated (math $updated + 1) + end + + echo "" + if test $failed -eq 0 + echo "✓ All $updated package managers updated successfully" + else + echo "⚠ $updated succeeded, $failed failed" >&2 + end +end diff --git a/Shell/.config/fish/functions/tl.fish b/Shell/.config/fish/functions/tl.fish new file mode 100644 index 0000000..398de94 --- /dev/null +++ b/Shell/.config/fish/functions/tl.fish @@ -0,0 +1,10 @@ +function tl --description "Listar sesiones tmux" + if command -q tmux + tmux list-sessions 2>/dev/null + if test $status -ne 0 + echo "No hay sesiones tmux activas" + end + else + echo "❌ tmux no está instalado" + end +end diff --git a/Shell/.config/fish/functions/tm-session.fish b/Shell/.config/fish/functions/tm-session.fish new file mode 100644 index 0000000..2c88c03 --- /dev/null +++ b/Shell/.config/fish/functions/tm-session.fish @@ -0,0 +1,19 @@ +function tm-session --description 'Quick tmux session picker' + if not command -q tmux + echo "tm-session: tmux not installed" >&2 + return 1 + end + if not command -q fzf + tmux list-sessions 2>/dev/null; or echo "No tmux sessions" + return 0 + end + + set -l session (tmux list-sessions -F "#S" 2>/dev/null | fzf --prompt="tmux session> " --height=10) + if test -n "$session" + if set -q TMUX + tmux switch-client -t "$session" + else + tmux attach -t "$session" + end + end +end diff --git a/Shell/.config/fish/functions/tm.fish b/Shell/.config/fish/functions/tm.fish new file mode 100644 index 0000000..7add9f5 --- /dev/null +++ b/Shell/.config/fish/functions/tm.fish @@ -0,0 +1,22 @@ +function tm --description "tmux smart attach — crea o adjunta a una sesión" + # Uso: tm [nombre-sesion] + # Si se omite el nombre, usa "mobile" por defecto (ideal para Termius) + set -l session_name "mobile" + if test (count $argv) -ge 1 + set session_name $argv[1] + end + + if command -q tmux + if set -q TMUX + # Ya estamos dentro de tmux + echo "Ya estás en una sesión tmux ($TMUX)" + return 1 + end + + # Intenta attach, si no existe la sesión la crea + tmux new-session -A -s "$session_name" + else + echo "❌ tmux no está instalado" + return 1 + end +end diff --git a/Shell/.config/fish/functions/tmux-kill-all.fish b/Shell/.config/fish/functions/tmux-kill-all.fish new file mode 100644 index 0000000..45c1543 --- /dev/null +++ b/Shell/.config/fish/functions/tmux-kill-all.fish @@ -0,0 +1,22 @@ +function tmux-kill-all --description "Mata TODAS las sesiones tmux (cuidado)" + if command -q tmux + set -l sessions (tmux list-sessions -F '#{session_name}' 2>/dev/null) + if test (count $sessions) -eq 0 + echo "No hay sesiones tmux activas" + return 0 + end + + echo "Sesiones activas:" + for s in $sessions + echo " • $s" + end + echo "" + read -l -p 'echo "¿Matar TODAS? (s/N): "' confirm + if test "$confirm" = "s" + tmux kill-server + echo "✅ Todas las sesiones tmux fueron terminadas" + else + echo "Cancelado" + end + end +end diff --git a/Shell/.config/shell/aliases/dreamcoder-modern.sh b/Shell/.config/shell/aliases/dreamcoder-modern.sh new file mode 100644 index 0000000..f83547f --- /dev/null +++ b/Shell/.config/shell/aliases/dreamcoder-modern.sh @@ -0,0 +1,85 @@ +# Dreamcoder modern CLI aliases (shared for bash & zsh) +# Gracefully degrades when modern tools aren't installed. + +## ─── Git shortcuts ──────────────────────────────────── +alias g='git' +alias gs='git status' +alias gp='git push' +alias gl='git log --oneline --graph --all' +alias gd='git diff' +alias gc='git commit' +alias gco='git checkout' +alias gb='git branch' +alias ga='git add' +alias gpl='git pull' +alias gst='git stash' +alias glg='git log --oneline --graph --all --decorate' +alias gundo='git reset --soft HEAD~1' +alias gcleanup='git branch --merged | grep -v "\*\|main\|master" | xargs -r git branch -d' +alias gconflicts='git diff --name-only --diff-filter=U' +alias groot='cd "$(git rev-parse --show-toplevel 2>/dev/null)"' + +## ─── Modern CLI replacements ────────────────────────── +# eza (better ls) +if command -v eza &>/dev/null; then + alias ll='eza -la --icons --group-directories-first' + alias la='eza -a --icons --group-directories-first' + alias lt='eza -T --icons --group-directories-first' + alias l1='eza -1 --icons' +else + alias ll='ls -lahF' + alias la='ls -A' +fi + +# bat (better cat) +command -v bat &>/dev/null && alias cat='bat --paging=never' + +# fd (better find) +command -v fd &>/dev/null && alias find='fd' + +# rg (better grep) +command -v rg &>/dev/null && alias grep='rg' + +# zoxide (better cd) +command -v zoxide &>/dev/null && alias cd='z' && alias cdi='zi' + +# Modern replacements (graceful fallback) +command -v procs &>/dev/null && alias ps='procs' +command -v btm &>/dev/null && alias top='btm' +command -v dua &>/dev/null && alias du='dua' +command -v duf &>/dev/null && alias df='duf' +command -v sd &>/dev/null && alias sed='sd' +command -v tldr &>/dev/null && alias help='tldr' + +## ─── Quick navigation ───────────────────────────────── +alias home='cd ~' +alias ..='cd ..' +alias ...='cd ../..' +alias ....='cd ../../..' +alias docs='cd "${DREAMCODER_DOTS_DIR:-$HOME/Documents/PROYECTOS/dreamcoder-dots}/docs"' + +## ─── Extract function ───────────────────────────────── +extract() { + if [ $# -eq 0 ]; then + echo "Usage: extract [output_dir]" + return 1 + fi + local file="$1" dir="${2:-${file%.*}}" + case "$file" in + *.tar.gz | *.tgz) tar -xzf "$file" -C "$(dirname "$file")" 2>/dev/null || tar -xzf "$file" ;; + *.tar.bz2 | *.tbz2) tar -xjf "$file" ;; + *.tar.xz | *.txz) tar -xJf "$file" ;; + *.tar.zst) tar --zstd -xf "$file" ;; + *.tar) tar -xf "$file" ;; + *.gz) gunzip -k "$file" ;; + *.bz2) bunzip2 -k "$file" ;; + *.xz) unxz -k "$file" ;; + *.zip) unzip "$file" -d "$dir" ;; + *.rar) unrar x "$file" "$dir" ;; + *.7z) 7z x "$file" "-o$dir" ;; + *) + echo "extract: unknown archive: $file" >&2 + return 1 + ;; + esac +} diff --git a/Shell/.config/starship-dark.toml b/Shell/.config/starship-dark.toml index f231494..b0a8099 100644 --- a/Shell/.config/starship-dark.toml +++ b/Shell/.config/starship-dark.toml @@ -2,22 +2,23 @@ # Dreamcoder Ember Noir OLED — Starship prompt # ======================================================== # Modern two-line layout with powerline segments. -# Line 1: context (directory, git) +# Line 1: context (directory, git), fill, cmd_duration, time # Line 2: input character only (clean) +# Extra: status (exit code), AI session (hidden until active) add_newline = true palette = "dreamcoder" command_timeout = 500 format = """ -[](fg:prompt_surface0)\ +[\uE0B6](fg:prompt_surface0)\ $username\ -[](bg:prompt_surface1 fg:prompt_surface0)\ +[\uE0B0](bg:prompt_surface1 fg:prompt_surface0)\ $directory\ -[](bg:prompt_accent fg:prompt_surface1)\ +[\uE0B0](bg:prompt_accent fg:prompt_surface1)\ $git_branch\ $git_status\ -[](fg:prompt_accent)\ +[\uE0B4](fg:prompt_accent)\ $fill\ $cmd_duration\ $time @@ -42,6 +43,10 @@ diagnostic = "#5f95ca" lavender = "#d4b4e6" mauve = "#e29cb4" error = "#ed8a7a" +warning = "#e8b866" +border = "#968878" +focus = "#5f8f8f" +link = "#d99555" [username] show_always = true @@ -51,29 +56,36 @@ format = "[  $user ]($style)" [directory] style = "bg:prompt_surface1 fg:prompt_text bold" -format = "[  $path ]($style)" +format = "[ $path ]($style)" truncation_length = 2 truncate_to_repo = true home_symbol = "" [git_branch] -symbol = "" +symbol = "\uf418" style = "bg:prompt_accent fg:prompt_bg bold" format = "[ $symbol $branch ]($style)" [git_status] style = "bg:prompt_accent fg:prompt_bg bold" format = "[$all_status$ahead_behind ]($style)" -conflicted = "${count} " -ahead = "⇡${count} " -behind = "⇣${count} " -diverged = "⇕⇡${ahead_count}⇣${behind_count} " +conflicted = "\ue727${count} " +ahead = "\u21E1${count} " +behind = "\u21E3${count} " +diverged = "\u2195\u21E1${ahead_count}\u21E3${behind_count} " untracked = "?${count} " -stashed = "󰏗${count} " +stashed = "\uf0CF${count} " modified = "~${count} " staged = "+${count} " -renamed = "»${count} " -deleted = "✘${count} " +renamed = "\u00BB${count} " +deleted = "\u2718${count} " + +[status] +disabled = false +format = "[$symbol]($style)" +symbol = "\u2717" +style = "bg:error fg:prompt_bg bold" +pipestatus = false [fill] symbol = " " @@ -81,7 +93,7 @@ symbol = " " [cmd_duration] min_time = 2500 style = "fg:prompt_muted" -format = "[  $duration ]($style)" +format = "[ \uf552 $duration ]($style)" [time] disabled = false @@ -89,38 +101,44 @@ format = "[ $time ]($style)" style = "fg:prompt_muted" [character] -success_symbol = "[❯](bold fg:prompt_accent)" -error_symbol = "[❯](bold fg:error)" -vimcmd_symbol = "[❮](bold fg:sage)" +success_symbol = "[\u276F](bold fg:prompt_accent)" +error_symbol = "[\u276F](bold fg:error)" +vimcmd_symbol = "[\u276E](bold fg:sage)" # Runtime versions - show only when relevant, keep compact [bun] -symbol = "" +symbol = "\uF5EF" style = "fg:prompt_accent bold" format = "[ $symbol $version]($style)" [nodejs] -symbol = "" +symbol = "\uE718" style = "fg:sage bold" format = "[ $symbol $version]($style)" [python] -symbol = "" -style = "fg:diagnostic bold" +symbol = "\uE73C" +style = "fg:diag bold" format = "[ $symbol $version]($style)" [golang] -symbol = "" -style = "fg:lavender bold" +symbol = "\uE627" +style = "fg:diag bold" format = "[ $symbol $version]($style)" [rust] -symbol = "" +symbol = "\uE7A8" style = "fg:mauve bold" format = "[ $symbol $version]($style)" +[custom.ai_session] +command = "cat ~/.cache/dreamcoder/ai-session.state 2>/dev/null || echo ''" +when = """test -f ~/.cache/dreamcoder/ai-session.state""" +format = "[\uf5B5 $output]($style)" +style = "fg:diag bold" + [docker_context] -symbol = "" -style = "fg:diagnostic bold" +symbol = "\uf308" +style = "fg:diag bold" format = "[ $symbol $context]($style)" only_with_files = true diff --git a/Shell/.config/starship-light.toml b/Shell/.config/starship-light.toml index 67b2557..ad761d0 100644 --- a/Shell/.config/starship-light.toml +++ b/Shell/.config/starship-light.toml @@ -2,22 +2,23 @@ # Dreamcoder Light — Starship prompt # ======================================================== # Modern two-line layout with powerline segments. -# Line 1: context (directory, git) +# Line 1: context (directory, git), fill, cmd_duration, time # Line 2: input character only (clean) +# Extra: status (exit code), AI session (hidden until active) add_newline = true palette = "dreamcoder" command_timeout = 500 format = """ -[](fg:prompt_surface0)\ +[\uE0B6](fg:prompt_surface0)\ $username\ -[](bg:prompt_surface1 fg:prompt_surface0)\ +[\uE0B0](bg:prompt_surface1 fg:prompt_surface0)\ $directory\ -[](bg:prompt_accent fg:prompt_surface1)\ +[\uE0B0](bg:prompt_accent fg:prompt_surface1)\ $git_branch\ $git_status\ -[](fg:prompt_accent)\ +[\uE0B4](fg:prompt_accent)\ $fill\ $cmd_duration\ $time @@ -42,6 +43,10 @@ diagnostic = "#0d4a68" lavender = "#57478b" mauve = "#7d3e64" error = "#842f24" +warning = "#654300" +border = "#66513b" +focus = "#0f6570" +link = "#824f16" [username] show_always = true @@ -51,29 +56,36 @@ format = "[  $user ]($style)" [directory] style = "bg:prompt_surface1 fg:prompt_text bold" -format = "[  $path ]($style)" +format = "[ $path ]($style)" truncation_length = 2 truncate_to_repo = true home_symbol = "" [git_branch] -symbol = "" +symbol = "\uf418" style = "bg:prompt_accent fg:prompt_bg bold" format = "[ $symbol $branch ]($style)" [git_status] style = "bg:prompt_accent fg:prompt_bg bold" format = "[$all_status$ahead_behind ]($style)" -conflicted = "${count} " -ahead = "⇡${count} " -behind = "⇣${count} " -diverged = "⇕⇡${ahead_count}⇣${behind_count} " +conflicted = "\ue727${count} " +ahead = "\u21E1${count} " +behind = "\u21E3${count} " +diverged = "\u2195\u21E1${ahead_count}\u21E3${behind_count} " untracked = "?${count} " -stashed = "󰏗${count} " +stashed = "\uf0CF${count} " modified = "~${count} " staged = "+${count} " -renamed = "»${count} " -deleted = "✘${count} " +renamed = "\u00BB${count} " +deleted = "\u2718${count} " + +[status] +disabled = false +format = "[$symbol]($style)" +symbol = "\u2717" +style = "bg:error fg:prompt_bg bold" +pipestatus = false [fill] symbol = " " @@ -81,7 +93,7 @@ symbol = " " [cmd_duration] min_time = 2500 style = "fg:prompt_muted" -format = "[  $duration ]($style)" +format = "[ \uf552 $duration ]($style)" [time] disabled = false @@ -89,38 +101,44 @@ format = "[ $time ]($style)" style = "fg:prompt_muted" [character] -success_symbol = "[❯](bold fg:prompt_accent)" -error_symbol = "[❯](bold fg:error)" -vimcmd_symbol = "[❮](bold fg:sage)" +success_symbol = "[\u276F](bold fg:prompt_accent)" +error_symbol = "[\u276F](bold fg:error)" +vimcmd_symbol = "[\u276E](bold fg:sage)" # Runtime versions - show only when relevant, keep compact [bun] -symbol = "" +symbol = "\uF5EF" style = "fg:prompt_accent bold" format = "[ $symbol $version]($style)" [nodejs] -symbol = "" +symbol = "\uE718" style = "fg:sage bold" format = "[ $symbol $version]($style)" [python] -symbol = "" -style = "fg:diagnostic bold" +symbol = "\uE73C" +style = "fg:diag bold" format = "[ $symbol $version]($style)" [golang] -symbol = "" -style = "fg:lavender bold" +symbol = "\uE627" +style = "fg:diag bold" format = "[ $symbol $version]($style)" [rust] -symbol = "" +symbol = "\uE7A8" style = "fg:mauve bold" format = "[ $symbol $version]($style)" +[custom.ai_session] +command = "cat ~/.cache/dreamcoder/ai-session.state 2>/dev/null || echo ''" +when = """test -f ~/.cache/dreamcoder/ai-session.state""" +format = "[\uf5B5 $output]($style)" +style = "fg:diag bold" + [docker_context] -symbol = "" -style = "fg:diagnostic bold" +symbol = "\uf308" +style = "fg:diag bold" format = "[ $symbol $context]($style)" only_with_files = true diff --git a/Shell/.config/starship.toml b/Shell/.config/starship.toml index 67b2557..ad761d0 100644 --- a/Shell/.config/starship.toml +++ b/Shell/.config/starship.toml @@ -2,22 +2,23 @@ # Dreamcoder Light — Starship prompt # ======================================================== # Modern two-line layout with powerline segments. -# Line 1: context (directory, git) +# Line 1: context (directory, git), fill, cmd_duration, time # Line 2: input character only (clean) +# Extra: status (exit code), AI session (hidden until active) add_newline = true palette = "dreamcoder" command_timeout = 500 format = """ -[](fg:prompt_surface0)\ +[\uE0B6](fg:prompt_surface0)\ $username\ -[](bg:prompt_surface1 fg:prompt_surface0)\ +[\uE0B0](bg:prompt_surface1 fg:prompt_surface0)\ $directory\ -[](bg:prompt_accent fg:prompt_surface1)\ +[\uE0B0](bg:prompt_accent fg:prompt_surface1)\ $git_branch\ $git_status\ -[](fg:prompt_accent)\ +[\uE0B4](fg:prompt_accent)\ $fill\ $cmd_duration\ $time @@ -42,6 +43,10 @@ diagnostic = "#0d4a68" lavender = "#57478b" mauve = "#7d3e64" error = "#842f24" +warning = "#654300" +border = "#66513b" +focus = "#0f6570" +link = "#824f16" [username] show_always = true @@ -51,29 +56,36 @@ format = "[  $user ]($style)" [directory] style = "bg:prompt_surface1 fg:prompt_text bold" -format = "[  $path ]($style)" +format = "[ $path ]($style)" truncation_length = 2 truncate_to_repo = true home_symbol = "" [git_branch] -symbol = "" +symbol = "\uf418" style = "bg:prompt_accent fg:prompt_bg bold" format = "[ $symbol $branch ]($style)" [git_status] style = "bg:prompt_accent fg:prompt_bg bold" format = "[$all_status$ahead_behind ]($style)" -conflicted = "${count} " -ahead = "⇡${count} " -behind = "⇣${count} " -diverged = "⇕⇡${ahead_count}⇣${behind_count} " +conflicted = "\ue727${count} " +ahead = "\u21E1${count} " +behind = "\u21E3${count} " +diverged = "\u2195\u21E1${ahead_count}\u21E3${behind_count} " untracked = "?${count} " -stashed = "󰏗${count} " +stashed = "\uf0CF${count} " modified = "~${count} " staged = "+${count} " -renamed = "»${count} " -deleted = "✘${count} " +renamed = "\u00BB${count} " +deleted = "\u2718${count} " + +[status] +disabled = false +format = "[$symbol]($style)" +symbol = "\u2717" +style = "bg:error fg:prompt_bg bold" +pipestatus = false [fill] symbol = " " @@ -81,7 +93,7 @@ symbol = " " [cmd_duration] min_time = 2500 style = "fg:prompt_muted" -format = "[  $duration ]($style)" +format = "[ \uf552 $duration ]($style)" [time] disabled = false @@ -89,38 +101,44 @@ format = "[ $time ]($style)" style = "fg:prompt_muted" [character] -success_symbol = "[❯](bold fg:prompt_accent)" -error_symbol = "[❯](bold fg:error)" -vimcmd_symbol = "[❮](bold fg:sage)" +success_symbol = "[\u276F](bold fg:prompt_accent)" +error_symbol = "[\u276F](bold fg:error)" +vimcmd_symbol = "[\u276E](bold fg:sage)" # Runtime versions - show only when relevant, keep compact [bun] -symbol = "" +symbol = "\uF5EF" style = "fg:prompt_accent bold" format = "[ $symbol $version]($style)" [nodejs] -symbol = "" +symbol = "\uE718" style = "fg:sage bold" format = "[ $symbol $version]($style)" [python] -symbol = "" -style = "fg:diagnostic bold" +symbol = "\uE73C" +style = "fg:diag bold" format = "[ $symbol $version]($style)" [golang] -symbol = "" -style = "fg:lavender bold" +symbol = "\uE627" +style = "fg:diag bold" format = "[ $symbol $version]($style)" [rust] -symbol = "" +symbol = "\uE7A8" style = "fg:mauve bold" format = "[ $symbol $version]($style)" +[custom.ai_session] +command = "cat ~/.cache/dreamcoder/ai-session.state 2>/dev/null || echo ''" +when = """test -f ~/.cache/dreamcoder/ai-session.state""" +format = "[\uf5B5 $output]($style)" +style = "fg:diag bold" + [docker_context] -symbol = "" -style = "fg:diagnostic bold" +symbol = "\uf308" +style = "fg:diag bold" format = "[ $symbol $context]($style)" only_with_files = true diff --git a/Tmux/.tmux.conf b/Tmux/.tmux.conf index ad5a475..5f8ec23 100644 --- a/Tmux/.tmux.conf +++ b/Tmux/.tmux.conf @@ -9,8 +9,9 @@ set -g history-limit 50000 set -g base-index 1 setw -g pane-base-index 1 set -g renumber-windows on -set -s escape-time 0 +set -s escape-time 10 set -g focus-events on +set -g set-clipboard on # ─── Key Bindings ────────────────────────────────────── # Prefix: Ctrl+a (like screen) @@ -18,31 +19,54 @@ set -g prefix C-a unbind C-b bind C-a send-prefix -# Split panes -bind | split-window -h -c "#{pane_current_path}" -bind - split-window -v -c "#{pane_current_path}" +# Split panes (Gentleman-style: v = vertical, d = horizontal) unbind '"' unbind % +bind v split-window -h -c "#{pane_current_path}" +bind d split-window -v -c "#{pane_current_path}" # Vi mode setw -g mode-keys vi bind -T copy-mode-vi v send-keys -X begin-selection -bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "xclip -selection clipboard" +bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel -# Navigate panes with Alt+arrow -bind -n M-Left select-pane -L -bind -n M-Right select-pane -R -bind -n M-Up select-pane -U -bind -n M-Down select-pane -D +# Navigate panes with vim-tmux-navigator (Alt+h/j/k/l) +bind -n M-h select-pane -L +bind -n M-j select-pane -D +bind -n M-k select-pane -U +bind -n M-l select-pane -R # Reload config bind r source-file ~/.tmux.conf \; display-message "Config reloaded!" +# ─── Plugin: TPM ─────────────────────────────────────── +set -g @plugin 'tmux-plugins/tpm' +set -g @plugin 'tmux-plugins/tmux-sensible' +set -g @plugin 'tmux-plugins/tmux-yank' +set -g @plugin 'christoomey/vim-tmux-navigator' +set -g @plugin 'tmux-plugins/tmux-resurrect' + +# Tmux-resurrect +set -g @resurrect-capture-pane-contents 'on' +set -g @resurrect-strategy-nvim 'session' +bind C-s run '~/.tmux/plugins/tmux-resurrect/scripts/save.sh' + +# ─── Session Management ──────────────────────────────── +# Scratch terminal popup (Alt+g) +bind -n M-g if-shell -F '#{==:#{session_name},scratch}' { + detach-client +} { + display-popup -d "#{pane_current_path}" -E -k "tmux new-session -A -s scratch" +} + +# fzf-based session picker (prefix + f) +bind f run "tmux list-sessions -F '#S' | fzf --prompt='session> ' --height=10 | xargs -r tmux switch-client -t" + +# Kill all sessions except current +bind K confirm-before -p "Kill all other sessions? (y/n)" "kill-session -a" + # ─── Theme (Dreamcoder) ──────────────────────────────── # Source the active theme generated by `dreamcoder-theme sync` -# The sync command creates a single active variant file at: -# ~/.config/tmux/tmux-dreamcoder.conf -# Override with: TMUX_THEME=/path/to/theme.conf source-file ~/.config/tmux/tmux-dreamcoder.conf # ─── Status Bar ──────────────────────────────────────── @@ -51,3 +75,6 @@ set -g status-interval 5 set -g status-justify left set -g status-left-length 40 set -g status-right-length 60 + +# ─── TPM init (keep last) ────────────────────────────── +run '~/.tmux/plugins/tpm/tpm' diff --git a/Warp/.config/warp-terminal/settings.toml b/Warp/.config/warp-terminal/settings.toml index a106cbe..a68cb86 100644 --- a/Warp/.config/warp-terminal/settings.toml +++ b/Warp/.config/warp-terminal/settings.toml @@ -30,12 +30,12 @@ zoom_level = 70 override_opacity = 96 [appearance.themes] -theme = { custom = { name = "Dreamcoder Light", path = "Dreamcoder-Light.yaml" } } -system_theme = false selected_system_themes = { dark = "Dreamcoder-Dark", light = "Dreamcoder-Light", } +system_theme = false +theme = { custom = { name = "Dreamcoder Light", path = "Dreamcoder-Light.yaml" } } [appearance.text] font_name = "JetBrainsMono Nerd Font" @@ -68,11 +68,20 @@ telemetry_enabled = true crash_reporting_enabled = true custom_secret_regex_list = [ { name = "IPv4 Address", pattern = '\b((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}\b' }, - { name = "IPv6 Address", pattern = '\b((([0-9A-Fa-f]{1,4}:){1,6}:)|(([0-9A-Fa-f]{1,4}:){7}))([0-9A-Fa-f]{1,4})\b' }, + { + name = "IPv6 Address", + pattern = '\b((([0-9A-Fa-f]{1,4}:){1,6}:)|(([0-9A-Fa-f]{1,4}:){7}))([0-9A-Fa-f]{1,4})\b', + }, { name = "Slack App Token", pattern = '\bxapp-[0-9]+-[A-Za-z0-9_]+-[0-9]+-[a-f0-9]+\b' }, { name = "Phone Number", pattern = '\b(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}\b' }, - { name = "AWS Access ID", pattern = '\b(AKIA|A3T|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{12,}\b' }, - { name = "MAC Address", pattern = '\b((([a-zA-z0-9]{2}[-:]){5}([a-zA-z0-9]{2}))|(([a-zA-z0-9]{2}:){5}([a-zA-z0-9]{2})))\b' }, + { + name = "AWS Access ID", + pattern = '\b(AKIA|A3T|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{12,}\b', + }, + { + name = "MAC Address", + pattern = '\b((([a-zA-z0-9]{2}[-:]){5}([a-zA-z0-9]{2}))|(([a-zA-z0-9]{2}:){5}([a-zA-z0-9]{2})))\b', + }, { name = "Google API Key", pattern = '\bAIza[0-9A-Za-z-_]{35}\b' }, { name = "Google OAuth ID", pattern = '\b[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com\b' }, { name = "GitHub Classic PAT", pattern = '\bghp_[A-Za-z0-9_]{36}\b' }, diff --git a/Warp/.local/share/warp-terminal/themes/Dreamcoder-Dark.yaml b/Warp/.local/share/warp-terminal/themes/Dreamcoder-Dark.yaml index c65767a..22c7f47 100644 --- a/Warp/.local/share/warp-terminal/themes/Dreamcoder-Dark.yaml +++ b/Warp/.local/share/warp-terminal/themes/Dreamcoder-Dark.yaml @@ -16,10 +16,10 @@ terminal_colors: white: '#c7b9aa' bright: black: '#938274' - red: '#e9a092' - green: '#75a579' + red: '#ec9989' + green: '#69bb73' yellow: '#e8b866' - blue: '#579ba4' - magenta: '#846fc5' - cyan: '#6fa5a5' + blue: '#78a2cb' + magenta: '#d8bce2' + cyan: '#789d9b' white: '#e8dfd0' diff --git a/Warp/.local/share/warp-terminal/themes/Dreamcoder-Light.yaml b/Warp/.local/share/warp-terminal/themes/Dreamcoder-Light.yaml index bff7afa..2883718 100644 --- a/Warp/.local/share/warp-terminal/themes/Dreamcoder-Light.yaml +++ b/Warp/.local/share/warp-terminal/themes/Dreamcoder-Light.yaml @@ -16,10 +16,10 @@ terminal_colors: white: '#352e22' bright: black: '#554638' - red: '#815850' - green: '#4f6f51' + red: '#702a20' + green: '#366134' yellow: '#654300' - blue: '#3a686e' - magenta: '#6c5ba2' - cyan: '#4b6f6f' + blue: '#0f4058' + magenta: '#4b3d74' + cyan: '#10565e' white: '#17120d' diff --git a/Zellij/.config/zellij/config.kdl b/Zellij/.config/zellij/config.kdl index 3679a68..971c600 100644 --- a/Zellij/.config/zellij/config.kdl +++ b/Zellij/.config/zellij/config.kdl @@ -36,4 +36,4 @@ plugins { } // Theme -theme "dreamcoder-light" \ No newline at end of file +theme "dreamcoder-light" diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..821e713 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,25 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [ + 2, + 'always', + [ + 'feat', + 'fix', + 'docs', + 'style', + 'refactor', + 'perf', + 'test', + 'chore', + 'ci', + 'build', + 'revert', + ], + ], + 'subject-case': [2, 'always', ['lower-case', 'sentence-case']], + 'subject-empty': [2, 'never'], + 'type-empty': [2, 'never'], + }, +} diff --git a/docs/ai-integration.md b/docs/ai-integration.md new file mode 100644 index 0000000..05b17f1 --- /dev/null +++ b/docs/ai-integration.md @@ -0,0 +1,100 @@ +# Dreamcoder AI Integration + +> Cómo dreamcoder se integra con Claude Code, OpenCode, Pi, y otras herramientas de IA. + +## AI Session State en el Prompt + +Dreamcoder tiene un módulo de Starship (`[custom.ai_session]`) que muestra el estado de tu sesión de IA actual en el prompt: + +``` +⎔ claude-4 42K +``` + +Esto se lee de `~/.cache/dreamcoder/ai-session.state`, que se actualiza automáticamente cuando: + +- **Claude Code** tiene una sesión activa (`~/.claude/sessions/`) +- **OpenCode** está corriendo (`~/.opencode/state`) +- **Codex CLI** tiene contexto activo + +### Cómo funciona + +```mermaid +flowchart LR + A["Claude/OpenCode
Session Active"] --> B["25-dreamcoder-ai-env.fish
Detecta y escribe estado"] + B --> C["~/.cache/dreamcoder/
ai-session.state"] + C --> D["Starship
custom.ai_session module"] + D --> E["Prompt muestra:
⎔ claude-4 42K"] +``` + +### Deshabilitar + +```fish +set -gx DREAMCODER_AI_SESSION_DISABLED 1 +``` + +--- + +## Pi Agent Theme + +Dreamcoder genera un tema para Pi (el coding agent) que se escribe en: + +- `~/.pi/agent/themes/dreamcoder.json` +- `~/.pi/agent/themes/dreamcoder-dark.json` +- `~/.pi/agent/themes/dreamcoder-light.json` + +El tema se activa automáticamente via `ensure_pi_theme_settings()` que setea `theme: "dreamcoder"` en `~/.pi/agent/settings.json`. + +### Mode Switching + +Cuando cambiás de modo (`dreamcoder dark` / `dreamcoder light`), el tema de Pi se actualiza automáticamente: + +```bash +dreamcoder dark # Pi → dreamcoder-dark.json +dreamcoder light # Pi → dreamcoder-light.json +``` + +--- + +## OpenCode Theme + +Dreamcoder genera temas para OpenCode en: + +- `~/.config/opencode/themes/dreamcoder.json` +- `.opencode/themes/dreamcoder.json` (repo copy) + +El TUI de OpenCode usa el tema dreamcoder con fondo transparente para mejor integración visual. + +--- + +## Codex CLI + +Dreamcoder genera temas `.tmTheme` para Codex CLI: + +- `~/.codex/themes/Dreamcoder.tmTheme` +- `~/.codex/themes/Dreamcoder-Dark.tmTheme` +- `~/.codex/themes/Dreamcoder-Light.tmTheme` + +Y también temas `.codex-theme.json` para Codex App. + +--- + +## CLAUDE.md + +Dreamcoder incluye un `CLAUDE.md` con instrucciones para Claude Code sobre cómo trabajar con el repositorio. Cubre: + +- Reglas de shell scripts (`set -euo pipefail`, quoting, `[[ ]]`) +- Modularidad (un archivo = un propósito) +- Safety (safe sourcing, sin hardcoded paths) + +--- + +## Caso de Uso: Desarrollo con IA + +1. Abrí Neovim (vía Gentleman.Dots) → 29 plugins, dreamcoder colorscheme +2. Iniciá Claude Code → `⎔ claude-4` aparece en el prompt +3. Escribí código con autocompletado (blink.cmp), fuzzy finder (fzf-lua), debugging (DAP) +4. Need a command? `cheat tar` → TLDR para tar +5. Need to extract something? `extract project.tar.gz` +6. Cambiando de tarea? `tm-session` → fzf session picker +7. Terminando el día? `sysupdate` → actualiza todo +8. El timer systemd cambia automáticamente a Ember Noir a las 18:00 diff --git a/docs/configuration/theme-system.md b/docs/configuration/theme-system.md index d119f79..6fd77cf 100644 --- a/docs/configuration/theme-system.md +++ b/docs/configuration/theme-system.md @@ -2,44 +2,60 @@ ## Architecture +Three-layer design system: + +```txt +primitives (OKLCH ramps) → semantic tokens (tokens.json) → component themes (renderers) ``` -tokens.json → Theme Engine (Python) → 48 config files → 22 targets -``` -## Token Schema - -All colors are defined in `themes/dreamcoder/tokens.json`: - -```json -{ - "dark": { - "background": "#100f0d", - "text": "#e8dfd0", - "accent": "#d99555", - "accent_2": "#c96a45", - "diagnostic": "#5f95ca" - }, - "light": { - "background": "#f3eadc", - "text": "#17120d", - "accent": "#824f16", - "accent_2": "#b85c2a", - "diagnostic": "#3a7bc8" - } -} +Pipeline: + +```txt +tokens.json → generate-palette-tokens.py → palette_tokens.py + → dreamcoder sync → 48+ config files → 22+ targets ``` -## Regenerating Themes +## Canonical tokens + +Single source of truth: [`themes/dreamcoder/tokens.json`](../../themes/dreamcoder/tokens.json) + +Dark (**Ember Noir OLED**) and light (**Cocoa/Lúcuma**) share the same semantic key set: + +| Layer | Examples | +| --- | --- | +| Surfaces | `bg`, `bg_soft`, `surface0`–`surface3` | +| Text | `text`, `text_heading`, `muted`, `subtle`, `comment` | +| Brand | `accent`, `accent_2`, `link`, `link_hover` | +| Feedback | `error`, `warning`, `success`, `info`, `diagnostic` | +| On-colors | `on_surface`, `on_accent`, `on_error`, `on_focus` | +| Interaction | `selection_bg`, `selection_fg`, `hover`, `pressed`, `disabled` | +| Chrome | `border`, `border_ui`, `border_hi`, `focus`, `panel_rgba` | + +## Regenerating themes After editing `tokens.json`: ```bash -./scripts/dreamcoder sync +./scripts/generate-palette-tokens.py # sync palette_tokens.py + derived tokens +./scripts/dreamcoder sync # propagate to all targets +./scripts/verify-theme-health.py # WCAG + APCA gates (light and dark) ``` -## Adding New Targets +## Quality gates + +- WCAG 2.2: body text ≥ 4.5:1 (main text ≥ 7:1) +- APCA: body Lc ≥ 75 light / ≥ 50 dark; `on_accent` Lc ≥ 54 on filled accent +- CI validates **both** light and dark Kitty, Starship, Ghostty, Waybar, Hypr, Rofi, Btop, Dunst, Fzf + +## Design decisions + +- **accent** = lúcuma/ember orange (identity, tabs, CTAs) +- **focus** = teal ring (keyboard focus, inputs) — separate from brand orange on purpose +- **adaptive/matugen** may tint surfaces but identity tokens win per `CLAUDE.md` + +## Adding new targets -1. Create renderer in `scripts/dreamcoder_theme/renderers_.py` -2. Add token mapping -3. Run `./scripts/dreamcoder sync` -4. Files appear in `themes/dreamcoder/` +1. Create renderer in `src/dreamcoder_theme/renderers_.py` +2. Map semantic tokens (never raw hex in renderers) +3. Register in `src/dreamcoder_theme/sync.py` +4. Run `./scripts/dreamcoder sync` diff --git a/docs/dreamcoder-theme-preview.md b/docs/dreamcoder-theme-preview.md index 88c86d7..b141e1b 100644 --- a/docs/dreamcoder-theme-preview.md +++ b/docs/dreamcoder-theme-preview.md @@ -12,36 +12,45 @@ Semantic tokens are intentionally distinct: - `comment` is softer and lower-chroma than `subtle` (syntax vs UI chrome). - Dark `accent` (refined ember orange), `accent_2` (maple red), `error` (soft coral red), and `warning` (lúcuma gold) form the orange/red/gold signature. -- `focus` follows the orange protagonist instead of a separate cyan ring; `diagnostic` stays warm amber so the palette remains autumnal. -- **Dusk** bridges daytime light and night dark for late-afternoon sessions on Arch. - +- `accent` carries brand CTAs and active chrome; `focus` is teal for keyboard/input affordance (WCAG ring). +- `on_accent`, `on_error`, and `selection_bg`/`selection_fg` are explicit pairs validated in CI. ## Palette ### Dreamcoder Ember Noir OLED | Role | Color | | --- | --- | -| `bg` | `#12100e` | -| `bg_soft` | `#1b1612` | -| `surface0` | `#211c18` | -| `surface1` | `#2e241f` | -| `surface2` | `#3e3129` | +| `bg` | `#100f0d` | +| `bg_soft` | `#181512` | +| `surface0` | `#201b16` | +| `surface1` | `#2b231b` | +| `surface2` | `#392e21` | +| `surface3` | `#4a3b2a` | | `text` | `#e8dfd0` | +| `text_heading` | `#f4ecdd` | | `muted` | `#c7b9aa` | | `subtle` | `#938274` | | `comment` | `#b8a99a` | | `accent` | `#d99555` | | `accent_2` | `#c96a45` | | `diagnostic` | `#5f95ca` | -| `sage` | `#388c48` | -| `lavender` | `#c9a8dc` | -| `mauve` | `#d98aa9` | -| `error` | `#e98272` | +| `sage` | `#4db35f` | +| `success` | `#4db35f` | +| `info` | `#5f95ca` | +| `lavender` | `#d4b4e6` | +| `mauve` | `#e29cb4` | +| `error` | `#ed8a7a` | | `warning` | `#e8b866` | -| `border` | `#594d46` | -| `border_ui` | `#806754` | +| `on_accent` | `#100f0d` | +| `on_error` | `#100f0d` | +| `link` | `#d99555` | +| `link_hover` | `#c96a45` | +| `selection_bg` | `#2b231b` | +| `selection_fg` | `#e8dfd0` | +| `border` | `#756052` | +| `border_ui` | `#968878` | | `border_hi` | `#c8b195` | -| `focus` | `#d99555` | +| `focus` | `#5f8f8f` | ### Dreamcoder Light @@ -52,7 +61,9 @@ Semantic tokens are intentionally distinct: | `surface0` | `#fff7ea` | | `surface1` | `#decbb1` | | `surface2` | `#c8ad89` | +| `surface3` | `#b89d7a` | | `text` | `#17120d` | +| `text_heading` | `#100c07` | | `muted` | `#352e22` | | `subtle` | `#554638` | | `comment` | `#725e4c` | @@ -60,10 +71,18 @@ Semantic tokens are intentionally distinct: | `accent_2` | `#a7471c` | | `diagnostic` | `#0d4a68` | | `sage` | `#3d723d` | +| `success` | `#3d723d` | +| `info` | `#0d4a68` | | `lavender` | `#57478b` | | `mauve` | `#7d3e64` | | `error` | `#842f24` | | `warning` | `#654300` | +| `on_accent` | `#fff7ea` | +| `on_error` | `#fff7ea` | +| `link` | `#824f16` | +| `link_hover` | `#a7471c` | +| `selection_bg` | `#decbb1` | +| `selection_fg` | `#17120d` | | `border` | `#8a7358` | | `border_ui` | `#66513b` | | `border_hi` | `#3e2f20` | @@ -78,7 +97,9 @@ Semantic tokens are intentionally distinct: | `surface0` | `#f1eadf` | | `surface1` | `#d8cbb8` | | `surface2` | `#c6b6a0` | +| `surface3` | `#b6a691` | | `text` | `#1a1713` | +| `text_heading` | `#13100c` | | `muted` | `#4c443a` | | `subtle` | `#5a4f43` | | `comment` | `#615548` | @@ -86,10 +107,18 @@ Semantic tokens are intentionally distinct: | `accent_2` | `#96411e` | | `diagnostic` | `#104b67` | | `sage` | `#466b41` | +| `success` | `#466b41` | +| `info` | `#104b67` | | `lavender` | `#5b4e86` | | `mauve` | `#784762` | | `error` | `#773126` | | `warning` | `#604000` | +| `on_accent` | `#f1eadf` | +| `on_error` | `#f1eadf` | +| `link` | `#8a5520` | +| `link_hover` | `#96411e` | +| `selection_bg` | `#d8cbb8` | +| `selection_fg` | `#1a1713` | | `border` | `#a7947a` | | `border_ui` | `#665845` | | `border_hi` | `#4a3f32` | @@ -101,39 +130,39 @@ Semantic tokens are intentionally distinct: | Token | Ratio vs bg | Target | | --- | ---: | --- | -| `text` | 14.37:1 | AAA | -| `muted` | 9.90:1 | AA | -| `comment` | 8.30:1 | AA | -| `accent` | 7.58:1 | AA | -| `accent_2` | 5.09:1 | AA | -| `diagnostic` | 6.00:1 | AA | -| `sage` | 4.53:1 | AA | -| `error` | 7.14:1 | AA | -| `warning` | 10.38:1 | AA | +| `text` | 14.50:1 | AAA | +| `muted` | 9.99:1 | AA | +| `comment` | 8.37:1 | AA | +| `accent` | 7.65:1 | AA | +| `accent_2` | 5.14:1 | AA | +| `diagnostic` | 6.06:1 | AA | +| `sage` | 7.23:1 | AA | +| `error` | 7.77:1 | AA | +| `warning` | 10.47:1 | AA | ### Dreamcoder Ember Noir OLED APCA | Token | Lc vs bg | Target | | --- | ---: | --- | -| `text` | 87.4 | ≥75 (body) | -| `muted` | 65.3 | ≥75 (FAIL) | -| `comment` | 56.5 | ≥75 (FAIL) | -| `accent` | 52.5 | ≥75 (FAIL) | -| `accent_2` | 36.8 | ≥75 (FAIL) | +| `text` | 87.5 | ≥75 (body) | +| `muted` | 65.4 | ≥75 (FAIL) | +| `comment` | 56.6 | ≥75 (FAIL) | +| `accent` | 52.6 | ≥75 (FAIL) | +| `accent_2` | 36.9 | ≥75 (FAIL) | | `diagnostic` | 42.7 | ≥75 (FAIL) | -| `sage` | 32.6 | ≥75 (FAIL) | -| `error` | 50.1 | ≥75 (FAIL) | -| `warning` | 68.0 | ≥75 (FAIL) | -| `border_ui` | 25.1 | ≥60 (FAIL) | -| `focus` | 52.5 | ≥60 (FAIL) | +| `sage` | 50.3 | ≥75 (FAIL) | +| `error` | 53.4 | ≥75 (FAIL) | +| `warning` | 68.1 | ≥75 (FAIL) | +| `border_ui` | 39.3 | ≥60 (FAIL) | +| `focus` | 37.6 | ≥60 (FAIL) | ### Dreamcoder Ember Noir OLED UI affordance contrast | Token | Ratio vs bg | Target | | --- | ---: | --- | -| `border_ui` | 3.60:1 | PASS | -| `border_hi` | 9.20:1 | PASS | -| `focus` | 7.58:1 | PASS | +| `border_ui` | 5.56:1 | PASS | +| `border_hi` | 9.29:1 | PASS | +| `focus` | 5.30:1 | PASS | ### Dreamcoder Light contrast (WCAG 2) @@ -216,7 +245,6 @@ Semantic tokens are intentionally distinct: ```bash ./scripts/dreamcoder auto ./scripts/dreamcoder light -./scripts/dreamcoder dusk ./scripts/dreamcoder dark ./scripts/dreamcoder verify ./scripts/dreamcoder preview @@ -226,6 +254,6 @@ Semantic tokens are intentionally distinct: - Main backgrounds avoid pure black and pure white. - Main text targets AAA (WCAG 2) and APCA Lc ≥ 75 for long coding sessions. -- Cocoa/Lúcuma accents are identity colors in light/dusk; Ember Noir uses refined orange, maple red, soft coral, and gold for dark-mode personality. +- Cocoa/Lúcuma accents are identity colors in light; Ember Noir uses refined orange, maple red, soft coral, and gold for dark-mode personality. - UI affordance tokens (`border_ui`, `border_hi`, `focus`) target at least 3:1 against the main background. - opencode uses one canonical theme: `dreamcoder`; its main `background` is generated as `none` for terminal transparency. diff --git a/docs/pypi-publishing.md b/docs/pypi-publishing.md new file mode 100644 index 0000000..5449210 --- /dev/null +++ b/docs/pypi-publishing.md @@ -0,0 +1,37 @@ +# Publishing dreamcoder-theme to PyPI + +The release workflow builds the Python package as an artifact but does NOT +automatically publish to PyPI yet, because it requires a Trusted Publisher +to be configured on PyPI's end. + +## One-time setup: Configure Trusted Publisher + +1. Go to +2. Add a new trusted publisher: + - **PyPI Project**: `dreamcoder-theme` + - **Publisher**: `GitHub` + - **Owner**: `Dreamcoder08` + - **Repository**: `Dreamcoder_dots` + - **Workflow**: `release.yml` + - **Environment**: (leave empty) + +3. Once configured, the `publish-pypi` job in `.github/workflows/release.yml` + will automatically publish to PyPI on every `v*` tag push. + +## Manual publish (without Trusted Publisher) + +```bash +# Build +pip install build +python -m build + +# Upload manually +pip install twine +twine upload dist/* +``` + +## Required secrets + +No secrets needed — PyPI's Trusted Publisher uses OIDC (OpenID Connect) +which authenticates directly via GitHub's OIDC token. Just configure +the publisher on PyPI and it works. diff --git a/docs/superpowers/plans/2026-07-01-sdd-plan-01-overlay-compatibility.md b/docs/superpowers/plans/2026-07-01-sdd-plan-01-overlay-compatibility.md new file mode 100644 index 0000000..4e44e5d --- /dev/null +++ b/docs/superpowers/plans/2026-07-01-sdd-plan-01-overlay-compatibility.md @@ -0,0 +1,81 @@ +# SDD Plan 01: Overlay Compatibility — Gentleman.Dots + ML4W + +> **Goal:** Guarantee that dreamcoder-dots theme hooks work flawlessly on top of both Gentleman.Dots and ML4W installations. The user installs Gentleman → ML4W → dreamcoder, and everything just works. +> **Strategy:** Verify every theme hook path, include name, and file reference against BOTH base repos. Fix mismatches. +> **Priority:** 🔴 CRITICAL — prerequisite for everything else +> **Estimated diff:** ~300 lines across 15+ files (mostly fixes) + +## Context + +dreamcoder-dots is designed as a **visual overlay** — it generates color files that get `@import`ed or `[include]`d by the base configs from Gentleman.Dots and ML4W. But there are **friction points**: + +- Path mismatches: dreamcoder writes to `~/.config/hypr/colors.conf` but ML4W includes from `~/.config/hypr/colors.conf`? Verificar. +- File naming: `dunst-dreamcoder-dark.conf` vs what dunstrc expects +- Include syntax differences: Lua vs conf vs CSS imports +- Mode switching: dreamcoder auto-switches dark/light, pero los includes tienen que actualizarse dinámicamente + +## What to Verify + +### Layer 1: Gentleman.Dots Overlay + +| Component | dreamcoder hook | Gentleman base | Integration Status | +| ------------ | --------------------------------- | ----------------------------------------- | -------------------------------- | +| **Ghostty** | `themes/dreamcoder-{mode}` | `GentlemanGhostty/config` `theme = ...` | 🔍 Verify path resolution | +| **Kitty** | `colors-dreamcoder-{mode}.conf` | `GentlemanKitty/kitty.conf` `include` | 🔍 Verify include directive | +| **Tmux** | `dreamcoder-{mode}.conf` | `GentlemanTmux/tmux.conf` `source-file` | 🔍 Verify source-file path | +| **Zellij** | `dreamcoder-{mode}.kdl` | `GentlemanZellij/zellij/config.kdl` | 🔍 Verify theme reference | +| **Nvim** | `colors/dreamcoder-{mode}.lua` | `GentlemanNvim/nvim/init.lua` colorscheme | ✅ Works (verify LazyVim compat) | +| **Starship** | `starship-{mode}.toml` | `starship.toml` | ✅ Works via env var | +| **Fish** | `conf.d/05-dreamcoder-theme.fish` | `GentlemanFish/fish/config.fish` | 🔍 Verify no conflicts | + +### Layer 2: ML4W Overlay + +| Component | dreamcoder hook | ML4W base | Integration Status | +| ------------- | ------------------------------ | ---------------------------- | ------------------------- | +| **Hyprland** | `hypr-colors-{mode}.conf` | `hypr/colors.conf` | 🔍 Verify variable naming | +| **Waybar** | `waybar-{mode}.css` | `waybar/style.css` `@import` | 🔍 Verify CSS import | +| **Rofi** | `rofi-{mode}.rasi` | `rofi/config.rasi` `@import` | 🔍 Verify rasi import | +| **Dunst** | `dunst-dreamcoder-{mode}.conf` | `dunst/dunstrc` `[include]` | 🔍 Verify include works | +| **Btop** | `btop-dreamcoder-{mode}.theme` | `btop/btop.conf` `theme =` | 🔍 Verify theme selection | +| **GTK** | `colors.css` | `gtk-3.0/gtk.css` `@import` | 🔍 Verify path | +| **Fastfetch** | — | Already has own config | ✅ No action needed | + +## Acceptance Criteria + +1. Install Gentleman.Dots → install ML4W → run `./scripts/dreamcoder install` +2. All terminal colors show dreamcoder palette (dark mode = Ember Noir, light = Cocoa/Lúcuma) +3. No "file not found" errors in any application log +4. `DREAMCODER_THEME_MODE=light` switches ALL components to light simultaneously +5. `DREAMCODER_THEME_MODE=dark` switches ALL components back to dark +6. No duplicate color definitions (dreamcoder doesn't conflict with base theme) + +## Tasks + +### Task 1: Audit Gentleman.Dots Integration + +- Clone both repos fresh +- Run dreamcoder's install hooks +- Check EVERY app for correct color loading +- Document any path/include mismatches + +### Task 2: Audit ML4W Integration + +- Same process for ML4W base +- Pay special attention to hyprland variable naming (ML4W uses `$COLOR` vs dreamcoder's `$dcCOLOR`) + +### Task 3: Fix Path Mismatches + +- Update `renderers_*.py` to output correct paths for each base +- Update `install-dreamcoder-hooks.sh` for any symlink/include path changes +- Update `settings.py` ThemePaths if needed + +### Task 4: Add CI Check + +- Add GitHub workflow step that installs on a base config and verifies colors load +- Create test that validates all hook files exist at expected paths + +## Risks + +- **ML4W updates**: ML4W changes file names between versions — need version detection or tolerance +- **Gentleman.Dots updates**: Same risk. Consider pinning compatible versions in docs +- **Order matters**: Install Gentleman → ML4W → dreamcoder. If order changes, hooks point to wrong base diff --git a/docs/superpowers/plans/2026-07-01-sdd-plan-02-shell-prompt-excellence.md b/docs/superpowers/plans/2026-07-01-sdd-plan-02-shell-prompt-excellence.md new file mode 100644 index 0000000..46181b0 --- /dev/null +++ b/docs/superpowers/plans/2026-07-01-sdd-plan-02-shell-prompt-excellence.md @@ -0,0 +1,173 @@ +# SDD Plan 02: Shell Prompt Excellence — Best-in-Class Starship + +> **Goal:** Elevate the dreamcoder Starship prompt to be among the best in the world, learning from top references while keeping the dreamcoder visual identity and cross-shell consistency. +> **References:** seifscape/macos-dot-files (AI session state in prompt), Powerlevel10k (instant prompt), devterm-kit (auto-setup), JaKooLit (multi-distro), alohays/dotfiles (agent-friendly) +> **Target files:** `Shell/.config/starship.toml`, `starship-dark.toml`, `starship-light.toml`, `conf.d/20-dreamcoder-prompt.fish`, `.zshrc`, `.bashrc` +> **Priority:** 🔴 CRITICAL — the prompt is what the user sees every single command +> **Estimated diff:** ~400 lines across 6 files + +## Context + +The prompt is the most-interacted-with UI in any developer's workflow. dreamcoder-dots already has Starship with dark/light variants, but they can be elevated significantly by learning from the best in the world. + +### Current state + +- `starship.toml` — base config +- `starship-dark.toml` — dark mode colors +- `starship-light.toml` — light mode colors +- `conf.d/20-dreamcoder-prompt.fish` — fish prompt integration +- Works in bash/zsh/fish via `STARSHIP_CONFIG` env var + +### What the best prompts do in 2026 + +| Feature | Who does it | dreamcoder has? | +| ------------------------------------------------------- | ----------------- | --------------- | +| **AI session state** (model, context, cost) | seifscape | ❌ | +| **Git info** (branch, status, ahead/behind) | All | ✅ Basic | +| **Language version** (node, python, rust, go) | All | ✅ Basic | +| **Command duration** | JaKooLit | ❌ | +| **Kubernetes context** | Starship default | ❌ | +| **Terraform workspace** | Starship default | ❌ | +| **Exit code indicator** (red ✗ on failure) | All the best ones | ❌ | +| **Time** (24h in prompt) | Many | ✅ | +| **Username + hostname** (conditional, show on SSH only) | Powerlevel10k | ❌ | +| **Container/VM indicator** | alohays | ❌ | +| **Battery** (on laptop) | ML4W | ❌ | +| **Nix shell indicator** | Starship default | ❌ | + +## Design Principles (dreamcoder-specific) + +1. **Health first**: No information overload. Show what matters, hide the rest. +2. **Visual hierarchy**: The most important info is most visually prominent. +3. **Context-aware**: Show Kubernetes context only when in a k8s directory. +4. **Speed**: Starship modules load lazily. No blocking calls. +5. **Consistency**: Same prompt in fish, zsh, and bash. +6. **Dark/Light**: Two complete color schemes adapted from tokens.json. + +## Prompt Architecture + +``` +┌──────────────────────────────────────────────────────────────┐ +│ $ctx $git_branch $git_status $lang $time ❯ │ +│ cyan accent subtle diag muted │ +│ │ +│ $cmd_duration $exit_code $battery │ +│ (line 2, only shown when relevant) │ +└──────────────────────────────────────────────────────────────┘ +``` + +### Module Plan + +| Module | Condition | dark color | light color | Priority | +| ---------------- | --------------------------- | ---------- | ----------- | ------------ | +| **username** | Only on SSH | `#5f95ca` | `#0d4a68` | 🟢 Optional | +| **hostname** | Only on SSH | `#d99555` | `#824f16` | 🟢 Optional | +| **directory** | Always, truncated | `#e8dfd0` | `#17120d` | ✅ Required | +| **git_branch** | In git repo | `#d99555` | `#824f16` | ✅ Required | +| **git_status** | In git repo | `#b8a99a` | `#725e4c` | ✅ Required | +| **package** | Has package.json/Cargo.toml | `#5f95ca` | `#0d4a68` | 🟡 Nice | +| **node_version** | .nvmrc or .node-version | `#5f95ca` | `#0d4a68` | 🟡 Nice | +| **python_venv** | Virtual env active | `#4db35f` | `#3d723d` | 🟡 Nice | +| **rust_version** | Cargo project | `#c96a45` | `#a7471c` | 🟡 Nice | +| **go_version** | Go project | `#5f95ca` | `#0d4a68` | 🟡 Nice | +| **terraform** | Terraform dir | `#a87cb5` | `#57478b` | 🟢 Optional | +| **kubernetes** | kube context | `#5f95ca` | `#0d4a68` | 🟢 Optional | +| **cmd_duration** | > 2 seconds | `#b8a99a` | `#725e4c` | ✅ Required | +| **exit_code** | Non-zero | `#ed8a7a` | `#842f24` | ✅ Required | +| **time** | Always (24h) | `#938274` | `#554638` | 🟡 Subdued | +| **line_break** | Always | — | — | ✅ Required | +| **battery** | Laptop, < 20% | `#ed8a7a` | `#842f24` | 🟢 Optional | +| **shell** | Always | `#d99555` | `#824f16` | 🟡 Character | +| **ai_session** | Claude/Codex active | `#5f95ca` | `#0d4a68` | 🔬 **New** | + +### AI Session State Module (NEW — cutting edge) + +This is what makes dreamcoder STAND OUT. Nobody in the dotfiles world has a prompt that shows AI session state... except seifscape who just started doing it in 2026. + +```toml +[custom.ai_session] +command = "~/.config/dreamcoder/scripts/ai-session-status.sh" +when = """test -f ~/.config/dreamcoder/ai-session.env""" +format = "[⎔ $output]($style)" +style = "bold cyan" +``` + +The script would read: + +- `CLAUDE_SESSION_ID` or `OPENCODE_SESSION_ID` +- Token count / context usage +- Cost estimate + +This is bleeding edge. Nobody else ships this as a dotfile feature. + +## Acceptance Criteria + +1. Starship prompt renders in fish, zsh, and bash identically (except shell-specific features) +2. Dark mode: warm dark background (`#100f0d`), warm gold accents (`#d99555`) +3. Light mode: paper background (`#f3eadc`), brown accents (`#824f16`) +4. Git info shows branch name + dirty/clean status +5. Language version shows for node/python/rust/go when in relevant project +6. Exit code turns red on command failure +7. Command duration shows for slow commands (> 2s) +8. AI session state shows when Claude/OpenCode is active +9. All module colors come from dreamcoder `tokens.json` +10. Battery shows on laptop when below 20% +11. Prompt renders in < 50ms (Starship lazy loading) + +## Tasks + +### Task 1: Starship TOML Architecture + +- Redesign `starship.toml` as the single source of truth +- `starship-dark.toml` → only color overrides for dark mode +- `starship-light.toml` → only color overrides for light mode +- Enable all desired modules with proper `when` conditions + +### Task 2: Core Modules + +- directory, git_branch, git_status, package, node_version, python_venv +- rust_version, go_version, cmd_duration, exit_code, time, line_break + +### Task 3: Advanced Modules + +- username/hostname (conditional on SSH) +- kubernetes context, terraform workspace +- battery indicator +- shell character + +### Task 4: AI Session State (bleeding edge) + +- Create `scripts/ai-session-status.sh` — reads Claude/OpenCode session state +- Create `conf.d/25-dreamcoder-ai-env.fish` — sets env vars for AI state +- Update `.zshrc` and `.bashrc` for AI state in zsh/bash +- Add custom Starship module for AI session + +### Task 5: Shell Integration Cleanup + +- Ensure `STARSHIP_CONFIG` switch works on dark/light toggle in ALL shells +- `conf.d/20-dreamcoder-prompt.fish` — clean up, add path detection +- `.zshrc` — verify p10k compatibility (if user uses p10k instead) +- `.bashrc` — verify starship works in bash too + +### Task 6: Verification + +- Visual diff in dark and light modes +- Time prompt rendering speed +- Test in fish, zsh, bash +- Test on SSH session (username/hostname shows) +- Test AI session module + +## Risks + +- **Starship version**: New modules may require latest Starship — pin minimum version +- **AI session state**: Claude/OpenCode don't expose session state as files yet — may need polling or env vars +- **Shell differences**: Nushell prompt config is completely different from Fish/Zsh — document limitation +- **Color consistency**: Each module must use the EXACT color from tokens.json — use the token names, not eyeballed colors + +## References + +- Current configs: `starship.toml`, `starship-dark.toml`, `starship-light.toml` +- Current integration: `conf.d/20-dreamcoder-prompt.fish` +- seifscape/macos-dot-files: AI session in prompt (reference only) +- Starship docs: +- Tokens: `themes/dreamcoder/tokens.json` (accent, diagnostic, error colors) diff --git a/docs/superpowers/plans/2026-07-01-sdd-plan-03-personalization-suite.md b/docs/superpowers/plans/2026-07-01-sdd-plan-03-personalization-suite.md new file mode 100644 index 0000000..5d12800 --- /dev/null +++ b/docs/superpowers/plans/2026-07-01-sdd-plan-03-personalization-suite.md @@ -0,0 +1,175 @@ +# SDD Plan 03: Dreamcoder Personalization Suite + +> **Goal:** Curate a set of opinionated personalizations that make dreamcoder-dots uniquely productive, beyond what Gentleman.Dots and ML4W provide. Think "what would a senior architect with 15 years of experience want in their dotfiles that isn't in any framework?" +> **Target:** Aliases, functions, keybindings, scripts, and workflow enhancements unique to dreamcoder +> **Priority:** 🟡 HIGH — this is the "secret sauce" that makes dreamcoder YOURS +> **Estimated diff:** ~500 lines across 20+ files + +## Philosophy + +Gentleman.Dots gives you a Ferrari. ML4W gives you a workshop. dreamcoder gives you the **custom interior** — the ergonomic touches, the shortcuts that save you 5 seconds 50 times a day, the things that make the environment feel like YOURS. + +## Scope + +### Fish Shell Personalizations + +**Existing (improve):** + +| File | What it does | Improvement | +| ------------------------------------- | --------------------------- | ----------------------------------------- | +| `conf.d/10-dreamcoder-keyboard.fish` | Vi mode, keybindings | Add `jk` → ESC, better vi-mode indicators | +| `conf.d/15-dreamcoder-shortcuts.fish` | Custom shortcuts | Expand with modern CLI aliases | +| `conf.d/16-dreamcoder-icons.fish` | LS_COLORS, icons | Use dreamcoder palette, add eza colors | +| `conf.d/20-dreamcoder-prompt.fish` | Prompt config | Clean up, add AI session env | +| `conf.d/30-dreamcoder-fastfetch.fish` | Fastfetch on terminal start | Add timing, conditional (not on SSH) | + +**Functions (NEW):** + +| Function | What it does | Reference | +| ----------------- | ---------------------------------------------- | --------------------------- | +| `mkcd.fish` | `mkdir -p + cd` | Common, surprisingly absent | +| `extract.fish` | Extract ANY archive type | 7z, tar.gz, zip, rar, etc. | +| `cheat.fish` | Quick cheat sheet (tldr wrapper) | Better than `man` | +| `dots.fish` | `cd` to dreamcoder-dots repo | Quick access | +| `sysupdate.fish` | Update everything (pacman, AUR, flatpak, brew) | ML4W-like | +| `ports.fish` | Show listening ports | Developer essential | +| `killport.fish` | Kill process on a port | Developer essential | +| `http.fish` | HTTP request with syntax highlighting | httpie wrapper | +| `logs.fish` | Tail journalctl with filters | Developer essential | +| `mcfly.fish` | Better Ctrl+R for history | Modern `fzf` history | +| `tm-session.fish` | Quick tmux session picker | fzf-based | + +**Aliases (NEW):** + +``` +alias g=git # exists +alias gs='git status' # exists +alias gp='git push' # exists +alias gl='git log --oneline --graph' # exists +alias ll='eza -la --icons' # improve with eza +alias la='eza -a --icons' +alias lt='eza -T --icons' # tree view +alias cat='bat' # exists +alias find='fd' # new +alias grep='rg' # improve +alias ps='procs' # modern ps +alias top='btm' # bottom (modern htop) +alias du='dua' # modern du +alias ping='gping' # graphical ping +alias df='duf' # modern df +alias sed='sd' # modern sed +alias help='tldr' # modern man +alias cd='z' # zoxide +alias cdi='zi' # zoxide interactive +``` + +### Zsh Equivalent + +- Same aliases in `.zshrc` +- Same functions in a `functions/` directory sourced by `.zshrc` +- Powerlevel10k or Starship prompt (user choice) + +### Bash Equivalent + +- Same aliases in `.bashrc` +- Same functions + +### Tmux Personalizations + +| Feature | Current | Improvement | +| --------------- | ------------ | --------------------------------------- | +| Prefix | `C-a` | ✅ Good, keep | +| Split | `\|` and `-` | Change to `v` and `d` (Gentleman-style) | +| Navigation | `Alt+arrows` | Add `vim-tmux-navigator` integration | +| Session picker | None | Add fzf-based `C-f` session picker | +| Floating window | None | Add `Alt+g` for scratch terminal | +| Resurrect | None | Add tmux-resurrect auto-save | + +### Git Config Personalizations + +| Feature | What | +| -------------------- | ---------------------------------------------------- | +| `delta` as diff tool | Already exists (`delta-dreamcoder-{mode}.gitconfig`) | +| `git lg` alias | Pretty log with graph | +| `git undo` | Soft reset to previous commit | +| `git cleanup` | Delete merged branches | +| `git conflicts` | List conflicted files | +| `git root` | Show repo root path | +| `includeIf` | Conditional git config per directory | + +### Script Improvements + +| Script | What to add | +| ------------------ | ------------------------------------------------------------------- | +| `doctor.sh` | Check ALL theme hooks are properly installed, not just colors | +| `repair.sh` | Re-stow ALL components, not just hooks | +| `status.sh` | Show full system status: theme mode, installed components, versions | +| `dreamcoder.sh` | Add `dreamcoder ai-status` subcommand | +| `set-wallpaper.sh` | Add wallpaper rotation support | + +## Acceptance Criteria + +1. All aliases work in fish, zsh, and bash +2. Every function has `--help` or at minimum a comment explaining usage +3. No alias conflicts with existing commands (check with `type`) +4. `eza`, `bat`, `fd`, `rg`, `zoxide` are detected gracefully (no errors if missing) +5. Tmux has vim-tmux-navigator support +6. Git delta uses dreamcoder dark/light colors matching theme mode +7. `dreamcoder doctor` shows green for every expected component + +## Tasks + +### Task 1: Fish Enhancements + +- Create `conf.d/12-dreamcoder-keybindings.fish` (jk→ESC, improved vi-mode) +- Create `functions/mkcd.fish`, `extract.fish`, `cheat.fish`, `dots.fish` +- Create `functions/sysupdate.fish`, `ports.fish`, `killport.fish` +- Create `functions/http.fish`, `logs.fish`, `tm-session.fish` +- Update `conf.d/15-dreamcoder-shortcuts.fish` with modern CLI aliases +- Update `conf.d/16-dreamcoder-icons.fish` with dreamcoder eza/ls colors + +### Task 2: Zsh Enhancements + +- Same aliases in `.zshrc` +- Create `Shell/.config/zsh/functions/` directory +- Source functions from `.zshrc` +- Ensure p10k users can use dreamcoder colors + +### Task 3: Bash Enhancements + +- Same aliases in `.bashrc` +- Source dreamcoder functions + +### Task 4: Tmux Improvements + +- Add `vim-tmux-navigator` keybindings +- Add fzf-based session picker (`C-f`) +- Add scratch terminal popup (`Alt-g`) +- Add tmux-resurrect auto-save on `prefix + Ctrl-s` + +### Task 5: Script Polish + +- Improve `doctor.sh` — check every component, not just colors +- Improve `repair.sh` — reinstall hooks with confirmation +- Improve `status.sh` — show theme mode, component versions, last wallaper change +- Add `dreamcoder ai-status` subcommand + +### Task 6: Verification + +- Test every alias in every shell +- Test every function with expected args and edge cases +- `dreamcoder doctor` passes +- Tmux navigation works with vim-tmux-navigator + +## Risks + +- **Shell-specific syntax**: Fish uses `function` keyword, Zsh uses `function` or `name()`, Bash uses `name()`. Each function needs 3 implementations. +- **Modern CLI dependencies**: `eza`, `bat`, `fd`, `rg`, `procs`, `btm`, `dua`, `gping`, `duf`, `sd`, `tldr` — not everyone has these installed. All aliases must gracefully degrade. +- **Tmux plugin dependency**: vim-tmux-navigator and tmux-resurrect require TPM — must be installed first. + +## References + +- Existing shell configs: `Shell/.config/fish/*.fish`, `Shell/.bashrc`, `Shell/.zshrc` +- Existing Tmux config: `Tmux/.tmux.conf` +- Existing scripts: `scripts/doctor.sh`, `repair.sh`, `status.sh`, `dreamcoder.sh` +- Modern CLI alternatives: `eza`, `bat`, `fd`, `rg`, `procs`, `btm`, `dua`, `gping`, `duf`, `sd`, `tldr`, `zoxide` diff --git a/docs/superpowers/plans/2026-07-01-sdd-plan-04-documentation.md b/docs/superpowers/plans/2026-07-01-sdd-plan-04-documentation.md new file mode 100644 index 0000000..dd8c37c --- /dev/null +++ b/docs/superpowers/plans/2026-07-01-sdd-plan-04-documentation.md @@ -0,0 +1,129 @@ +# SDD Plan 04: Documentation & Developer Experience + +> **Goal:** Create crystal-clear documentation for the 3-step install flow (Gentleman.Dots → ML4W → dreamcoder-dots), plus a comparison page showing what dreamcoder adds vs the competition. +> **Target:** README.md overhaul, INSTALL.md creation, COMPARISON.md creation +> **Priority:** 🟡 HIGH — without docs, nobody knows how to use dreamcoder +> **Estimated diff:** ~500 lines across 5 files + +## Context + +The biggest problem with dreamcoder-dots currently is that **nuevo usuario no sabe cómo instalarlo ni qué hace**. La documentación actual asume que el usuario sabe que necesita Gentleman.Dots + ML4W primero. + +## Scope + +### README.md Overhaul + +Current: Technical description of theme pipeline, focused on developers contributing. +Target: **User-centric** — "How to get Dreamcoder running in 10 minutes." + +Sections: + +```markdown +# Dreamcoder OS + +> La capa visual premium para Gentleman.Dots + ML4W. +> Café/Lúcuma. Ember Noir. Contraste saludable. Identidad. + +## Quick Start (3-Step Install) + +### 1. Install Gentleman.Dots + +`brew install gentleman-dots` or download from GitHub +→ Provides: Neovim (29 plugins), Ghostty shaders, Tmux/Zellij, Vim Trainer, Fish/Zsh/Nushell + +### 2. Install ML4W OS + +`bash <(curl -s https://ml4w.com/os/stable)` +→ Provides: Hyprland config, Waybar, Rofi, Dunst, animations, keybindings + +### 3. Install Dreamcoder + +`git clone ... && ./scripts/dreamcoder install` +→ **Applies**: dreamcoder dark/light/dusk color system across all components +→ **Adds**: custom shell aliases, AI session prompt, smart functions, auto-theme-switching +``` + +### New Files + +| File | Purpose | +| ------------------------ | ---------------------------------------------------------------- | +| `INSTALL.md` | Detailed install guide with screenshots | +| `COMPARISON.md` | What dreamcoder adds vs Gentleman alone vs ML4W alone | +| `docs/ai-integration.md` | How dreamcoder integrates with Claude/OpenCode/Pi | +| `docs/theme-tokens.md` | Reference for tokens.json (schema, guardrails, color philosophy) | + +### COMPARISON.md Content + +| Feature | Gentleman.Dots | ML4W | dreamcoder-dots | +| -------------------- | ------------------- | ---------------- | --------------------------------- | +| Hyprland config | ❌ | ✅ Complete | 🔶 Color overlay only | +| Neovim plugins | ✅ 29 plugins | ❌ | 🔶 Dreamcoder colorscheme | +| Shell configs | ✅ Fish/Zsh/Nushell | ✅ Fish/Bash | 🔶 Aliases + functions overlay | +| Theme engine | ❌ Uses catppuccin | ✅ Matugen-based | ✅ **Token-based with WCAG/APCA** | +| Light/Dark switching | ❌ | ✅ | ✅ **+ Dusk transition mode** | +| AI integration | ✅ Neovim plugins | ❌ | ✅ **+ AI session in prompt** | +| Ghostty shaders | ✅ 45+ GLSL | ❌ | 🔶 Uses Gentleman's shaders | +| Installer | ✅ Go TUI | ✅ bash script | ✅ **Go TUI with Vim Trainer** | +| Prompt | ❌ Basic | ❌ Basic | ✅ **Starship with AI state** | +| Accessibility | ❌ | ❌ | ✅ **WCAG 4.5:1 + APCA guards** | + +## Acceptance Criteria + +1. New user can follow INSTALL.md from zero to dreamcoder desktop in under 30 minutes +2. COMPARISON.md answers "why should I use dreamcoder?" in one glance +3. README.md has the 3-step install flow prominently at the top +4. All docs link to the actual source repos for Gentleman.Dots and ML4W +5. ai-integration.md explains how dreamcoder's Pi theme works with the user's Pi agent + +## Tasks + +### Task 1: README.md Rewrite + +- Rewrite with 3-step install flow as hero section +- Add badges: "Works with Gentleman.Dots" + "Works with ML4W" + "WCAG/APCA Certified" +- Add screenshot showcase (dark mode, light mode, command-line) +- Add philosophy section (health > flashiness) +- Keep tech stack and architecture sections but move lower + +### Task 2: INSTALL.md + +- Prerequisites (Gentleman.Dots, ML4W, Arch Linux) +- Step-by-step with screenshots +- Post-install verification checklist +- Troubleshooting section + +### Task 3: COMPARISON.md + +- Feature comparison table +- "What stays from Gentleman" section +- "What stays from ML4W" section +- "What dreamcoder adds" section +- Screenshot comparison (same app, different theme) + +### Task 4: AI Integration Docs + +- How dreamcoder integrates with Pi agent themes +- How dreamcoder's tokens interact with opencode themes +- How AI session prompt module works + +### Task 5: Theme Tokens Reference + +- Visual reference for every token in tokens.json +- Explanation of WCAG 4.5:1 and APCA minimums +- How to add a new token +- Color ramp visualization (hex values displayed as color swatches) + +## Risks + +- **Link rot**: Links to Gentleman.Dots and ML4W repos must stay current +- **Version drift**: Screenshots become outdated as themes evolve — use generated previews instead +- **Translation**: Spanish README would be nice-to-have but out of scope for now + +## References + +- Current README: `README.md` +- Existing docs: `docs/README.md`, `docs/installation/macos.md`, `docs/installation/linux.md` +- Gentleman.Dots README: (bilingual EN+ES) — reference for format +- ML4W README: (professional, well-structured) — reference for professional tone +- Theme tokens: `themes/dreamcoder/tokens.json` +- Theme preview: `docs/dreamcoder-theme-preview.md` diff --git a/docs/superpowers/plans/2026-07-01-sdd-plan-05-release-automation.md b/docs/superpowers/plans/2026-07-01-sdd-plan-05-release-automation.md new file mode 100644 index 0000000..1da1217 --- /dev/null +++ b/docs/superpowers/plans/2026-07-01-sdd-plan-05-release-automation.md @@ -0,0 +1,309 @@ +# SDD Plan 05: Release Automation & Engineering Excellence + +> **Goal:** Automate EVERYTHING — tag → build → test → release → publish. No manual steps from commit to distribution. +> **Reference:** GoReleaser, PyPI trusted publishing, Dependabot, golangci-lint, commitlint +> **Target:** `.github/workflows/`, `installer/.goreleaser.yaml`, `.github/dependabot.yml`, `.github/CODEOWNERS` +> **Priority:** 🔴 HIGH — multiplies productivity +> **Estimated diff:** ~600 lines across 15+ files + +## Philosophy + +Un release no debería requerir más que un `git tag v2.1.0 && git push --tags`. Todo lo demás — compilar para 4 plataformas, publicar en PyPI, crear el GitHub Release, actualizar la Homebrew formula, generar el CHANGELOG — debería ser automático. + +## Current State vs Target + +| Feature | Hoy | Target | +| ------------------------ | -------------------------- | ---------------------------------------- | +| **Go builds** | `make build-all` manual | GoReleaser automático en CI | +| **Go tests** | `go test` manual | ✅ En cada push/PR | +| **Go lint** | ❌ No existe | golangci-lint en CI | +| **Python publish** | ✅ CI on tags | ✅ Ya funciona (trusted publishing) | +| **Python tests** | ✅ CI | ✅ Ya funciona | +| **Release creation** | `gh release create` manual | GoReleaser + GitHub Release | +| **Homebrew formula** | `sha256` manual | GoReleaser actualiza automático | +| **Dependency updates** | ❌ Manual | Dependabot semanal | +| **Changelog** | Manual | Auto-generado desde conventional commits | +| **Issue/PR templates** | ❌ No existe | Templates en `.github/` | +| **Conventional commits** | ❌ No enforce | commitlint en CI | +| **CODEOWNERS** | ❌ No existe | Para review routing | +| **Coverage reporting** | ❌ Solo local | Codecov / coveralls | + +## Scope + +### In Scope + +**Release Pipeline (Go installer):** + +- GoReleaser config (`.goreleaser.yaml`) para build cross-platform +- GitHub Actions release workflow: tag → build → test → release +- Auto-publish Homebrew formula con SHA256 actualizados +- Builds: linux/amd64, linux/arm64, darwin/amd64, darwin/arm64 + +**Quality Gates (CI):** + +- Go build + test en cada push/PR +- golangci-lint en CI +- commitlint (conventional commits) en CI +- Code coverage upload to codecov.io + +**Automation:** + +- Dependabot for Go and Python dependencies +- Release Please for CHANGELOG auto-generation +- `.github/CODEOWNERS` + +**Community:** + +- Issue templates (bug report + feature request) +- PR template + +### Out of Scope + +- Semantic release for Python (PyPI publish already works) +- SBOM generation (future concern) +- Performance benchmarks (Plan 06 candidate) +- Docker E2E (separate concern) + +## Architecture + +### Trigger Flow + +``` +git tag v2.1.0 + │ + ▼ +GitHub Actions: Release Workflow + ├── GoReleaser + │ ├── build linux/amd64 + │ ├── build linux/arm64 + │ ├── build darwin/amd64 + │ ├── build darwin/arm64 + │ ├── create GitHub Release + │ └── publish Homebrew formula + ├── Python publish (to PyPI via trusted publishing) + │ + └── (already exists) ✅ +``` + +### CI Flow (every push/PR) + +``` +Push/PR to main + │ + ▼ +GitHub Actions: CI Workflow + ├── Python (3.11, 3.12) + │ ├── ruff lint + format + │ ├── mypy type check + │ ├── pytest + coverage + │ ├── shellcheck + │ └── theme health check + ├── Go + │ ├── go build + │ ├── go test ./... + │ └── golangci-lint + ├── commitlint + │ └── conventional commits check + └── Upload coverage to codecov +``` + +## Files to Create/Modify + +### New Files + +| File | Purpose | +| -------------------------------------------- | --------------------------------- | +| `.github/workflows/ci-go.yml` | Go build + test + lint on push/PR | +| `.github/workflows/release.yml` | GoReleaser + PyPI publish on tag | +| `.goreleaser.yaml` | GoReleaser configuration | +| `.github/dependabot.yml` | Auto-dependency updates | +| `.github/CODEOWNERS` | Code review routing | +| `.github/ISSUE_TEMPLATE/bug_report.yml` | Bug report form | +| `.github/ISSUE_TEMPLATE/feature_request.yml` | Feature request form | +| `.github/PULL_REQUEST_TEMPLATE.md` | PR template | +| `.github/workflows/commitlint.yml` | Conventional commits check | +| `commitlint.config.js` | Commitlint rules | + +### Modified Files + +| File | Change | +| ---------------------------------------- | -------------------------------------- | +| `.github/workflows/theme-validation.yml` | Add Go matrix, add codecov upload | +| `.pre-commit-config.yaml` | Add commitlint hook | +| `Makefile` | Simplify (GoReleaser handles builds) | +| `installer/go.mod` | May need updates for GoReleaser compat | + +## Acceptance Criteria + +1. `git tag v2.1.0 && git push --tags` → CI builds for 4 platforms, creates GitHub Release, publishes PyPI +2. Every push/PR → Go build + test passes +3. `golangci-lint` passes with zero issues +4. `commitlint` enforces conventional commits on every PR +5. Dependabot opens weekly PRs for outdated deps +6. Issue templates appear when creating new issues +7. PR template appears when creating new PRs +8. Coverage is uploaded to codecov.io with badge in README + +## Tasks + +### Task 1: GoReleaser Configuration + +- Create `.goreleaser.yaml` with: + - 4 builds (linux/amd64, linux/arm64, darwin/amd64, darwin/arm64) + - Version injected via ldflags: `-X github.com/dreamcoder08/dreamcoder-dots/installer/pkg/version.Version={{ .Version }}` + - Homebrew tap publish: `brews:` section pointing to `homebrew-tap/Formula/dreamcoder-dots.rb` + - Archive config (tar.gz for linux, zip for darwin) + - Release notes auto-generated from git log + +### Task 2: Release Workflow + +- Create `.github/workflows/release.yml`: + + ```yaml + name: Release + on: + push: + tags: ['v*'] + jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-go@v5 + with: + go-version: stable + - uses: goreleaser/goreleaser-action@v6 + with: + version: '~> v2' + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + publish-pypi: + needs: [goreleaser] + # Uses existing PyPI publish job + ``` + +### Task 3: Go CI (every push/PR) + +- Create `.github/workflows/ci-go.yml`: + + ```yaml + name: CI (Go) + on: [push, pull_request] + jobs: + go: + strategy: + matrix: + go-version: ['1.26', '1.27'] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + - run: go build ./... + - run: go test ./... + - uses: golangci/golangci-lint-action@v6 + ``` + +### Task 4: Quality Gates + +- Create `commitlint.config.js`: + + ```js + module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [ + 2, + 'always', + [ + 'feat', + 'fix', + 'docs', + 'style', + 'refactor', + 'perf', + 'test', + 'chore', + 'ci', + 'build', + ], + ], + }, + } + ``` + +- Create `.github/workflows/commitlint.yml` +- Update `.pre-commit-config.yaml` to add commitlint hook + +### Task 5: Dependabot + +- Create `.github/dependabot.yml`: + + ```yaml + version: 2 + updates: + - package-ecosystem: 'gomod' + directory: '/installer' + schedule: + interval: 'weekly' + - package-ecosystem: 'pip' + directory: '/' + schedule: + interval: 'weekly' + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' + ``` + +### Task 6: CODEOWNERS + +- Create `.github/CODEOWNERS`: + + ``` + * @Dreamcoder08 + src/dreamcoder_theme/ @Dreamcoder08 + installer/ @Dreamcoder08 + .github/ @Dreamcoder08 + ``` + +### Task 7: Community Templates + +- Create `.github/ISSUE_TEMPLATE/bug_report.yml` +- Create `.github/ISSUE_TEMPLATE/feature_request.yml` +- Create `.github/PULL_REQUEST_TEMPLATE.md` +- Add config.yml for issue forms + +### Task 8: Coverage Reporting + +- Add `codecov-action` to CI workflow +- Add coverage badge to README.md +- Configure `codecov.yml` with threshold + +### Task 9: Merge Into Existing CI + +- Update `.github/workflows/theme-validation.yml` (rename to `ci-python.yml`) +- Add Go matrix to existing workflow or keep separate +- Ensure all workflows run in parallel +- Add `codecov` upload step + +## Risks + +- **GoReleaser version**: GoReleaser v2 has breaking changes from v1 — pin version in action +- **Homebrew formula update**: GoReleaser's `brews` config needs the formula file to exist with template markers +- **commitlint**: Requires Node.js in CI — adds ~30s to workflow +- **Secret management**: PyPI trusted publishing requires OIDC — already configured ✅ +- **Go version matrix**: `go 1.26.4` in `go.mod` — verify CI runners support it + +## References + +- GoReleaser docs: +- GoReleaser GitHub Action: +- PyPI trusted publishing: +- Dependabot config: +- Commitlint: +- Current CI: `.github/workflows/theme-validation.yml` +- Current Makefile: `installer/Makefile` +- Current pyproject.toml: `pyproject.toml` +- Current Homebrew formula: `homebrew-tap/Formula/dreamcoder-dots.rb` diff --git a/installer/e2e/Dockerfile.ubuntu b/installer/e2e/Dockerfile.ubuntu new file mode 100644 index 0000000..6066251 --- /dev/null +++ b/installer/e2e/Dockerfile.ubuntu @@ -0,0 +1,17 @@ +FROM ubuntu:24.04 + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + git \ + golang-go \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY . . + +RUN go build -o dreamcoder-dots . +RUN ./dreamcoder-dots --version +RUN ./dreamcoder-dots doctor +RUN ./dreamcoder-dots install --dry-run + +CMD ["echo", "All tests passed"] diff --git a/installer/e2e/README.md b/installer/e2e/README.md new file mode 100644 index 0000000..1846a9d --- /dev/null +++ b/installer/e2e/README.md @@ -0,0 +1,45 @@ +# Docker E2E Testing for dreamcoder-dots installer + +Tests the Go TUI installer across multiple Linux distributions +using Docker containers. + +## Quick Start + +```bash +cd installer/e2e +bash docker-test.sh +``` + +This builds and tests the installer on: + +- Ubuntu 24.04 +- Debian 12 +- Fedora 40 +- Alpine 3.20 + +## Test Scenarios + +Each container runs: + +1. `go build` — compile the installer +2. `./dreamcoder-dots --version` — binary runs +3. `./dreamcoder-dots doctor` — platform detection works +4. `./dreamcoder-dots install --dry-run` — install logic works + +## Adding a distro + +```bash +cp Dockerfile.ubuntu Dockerfile. +# Edit the FROM line and package manager +# Add to docker-test.sh +``` + +## Prerequisites + +- Docker Engine 24+ +- Bash + +## CI Integration + +This is designed to run as a GitHub Actions matrix job. +See `.github/workflows/ci-go.yml` for the current Go test setup. diff --git a/installer/e2e/docker-test.sh b/installer/e2e/docker-test.sh new file mode 100644 index 0000000..ce2db84 --- /dev/null +++ b/installer/e2e/docker-test.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Docker E2E test runner for dreamcoder-dots installer +# Usage: bash docker-test.sh [distro] +# distro: ubuntu (default), debian, fedora, alpine, all + +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +E2E_DIR="${ROOT}/installer/e2e" +DISTRO="${1:-ubuntu}" + +run_test() { + local distro="$1" + local dockerfile="${E2E_DIR}/Dockerfile.${distro}" + + if [[ ! -f "${dockerfile}" ]]; then + echo "✗ No Dockerfile for '${distro}' at ${dockerfile}" + return 1 + fi + + echo ":: Testing ${distro}..." + docker build -t "dreamcoder-e2e-${distro}" \ + -f "${dockerfile}" \ + "${ROOT}/installer" 2>&1 | tail -3 + + echo "✓ ${distro} passed" +} + +case "${DISTRO}" in + all) + for d in ubuntu debian fedora alpine; do + run_test "${d}" + done + ;; + *) + run_test "${DISTRO}" + ;; +esac + +echo "" +echo "=== All tests passed ===" diff --git a/installer/installer b/installer/installer index 2424107..af3e765 100755 Binary files a/installer/installer and b/installer/installer differ diff --git a/pyproject.toml b/pyproject.toml index 2145505..96b65f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dependencies = [ "jinja2>=3.0", ] [project.optional-dependencies] -dev = ["pytest>=7", "pytest-cov>=4"] +dev = ["pytest>=7", "pytest-cov>=4", "ruff>=0.7", "mypy>=1.15"] [project.urls] Homepage = "https://github.com/Gentleman-Programming/dreamcoder-dots" @@ -55,6 +55,15 @@ line-length = 100 select = ["E", "F", "I", "N", "W", "UP", "YTT", "ASYNC", "SIM", "PL", "RUF"] ignore = ["E501"] +[tool.ruff.lint.per-file-ignores] +"scripts/verify-theme-health.py" = ["PLR2004", "SIM108", "PLR0912", "PLR0915", "PLC0415"] +"scripts/generate-theme-preview.py" = ["PLR2004", "SIM108"] +"tests/test_*.py" = ["E402", "PLR2004"] +"src/dreamcoder_theme/palette.py" = ["PLR0912", "PLR2004", "SIM102"] +"src/dreamcoder_theme/renderers_starship.py" = ["RUF001"] +"src/dreamcoder_theme/sync.py" = ["PLC0415"] +"src/dreamcoder_theme/renderers_extra_nvim_lsp.py" = ["PLR0913"] + [tool.ruff.format] quote-style = "double" indent-style = "space" diff --git a/scripts/apply-theme-mode.sh b/scripts/apply-theme-mode.sh index 47d7f59..0d04ba3 100755 --- a/scripts/apply-theme-mode.sh +++ b/scripts/apply-theme-mode.sh @@ -7,120 +7,130 @@ ENV_FILE="${DREAMCODER_DOTS_ENV:-${0%/*}/dreamcoder-env.sh}" MODE="${1:-light}" WALLPAPER="${2:-${DREAMCODER_WALLPAPER:-}}" ML4W_WALLPAPER="${ML4W_CACHE_DIR}/current_wallpaper" -[[ "${MODE}" == "light" || "${MODE}" == "dark" ]] || { printf 'Invalid mode: %s\n' "${MODE}" >&2; exit 1; } +[[ "${MODE}" == "light" || "${MODE}" == "dark" ]] || { + printf 'Invalid mode: %s\n' "${MODE}" >&2 + exit 1 +} if [[ -z "${WALLPAPER}" && -f "${ML4W_WALLPAPER}" ]]; then WALLPAPER="$(cat "${ML4W_WALLPAPER}")"; fi CURSOR_CLI_ENV="${CACHE_HOME:-${HOME}/.cache}/dreamcoder/cursor-cli.env" case "${MODE}" in - light) CLI_COLORFGBG="0;15" ;; - dark) CLI_COLORFGBG="15;0" ;; +light) CLI_COLORFGBG="0;15" ;; +dark) CLI_COLORFGBG="15;0" ;; esac mkdir -p "$(dirname "${CURSOR_CLI_ENV}")" printf 'export COLORFGBG="%s"\nexport DREAMCODER_THEME_MODE="%s"\nexport COLORTERM="truecolor"\nexport FORCE_COLOR="3"\nexport CLICOLOR_FORCE="1"\nunset NO_COLOR\n' "${CLI_COLORFGBG}" "${MODE}" >"${CURSOR_CLI_ENV}" "${DREAMCODER_DOTS_DIR}/scripts/apply-system-mode.sh" "${MODE}" if [[ -n "${WALLPAPER}" && -f "${WALLPAPER}" ]] && command -v matugen >/dev/null; then - matugen image "${WALLPAPER}" -m "${MODE}" >/dev/null 2>&1 || true + matugen image "${WALLPAPER}" -m "${MODE}" >/dev/null 2>&1 || true fi # --- Waybar: flip colors.css symlink to mode-specific variant BEFORE sync --- # This ensures the Python sync writes to the correct variant file # instead of overwriting the wrong one through a stale symlink. WAYBAR_COLORS="${HOME}/.config/waybar/colors.css" if [[ -L "${WAYBAR_COLORS}" ]]; then - ln -sf "colors-${MODE}.css" "${WAYBAR_COLORS}" + ln -sf "colors-${MODE}.css" "${WAYBAR_COLORS}" fi # --- Rofi: same treatment for colors.rasi symlink --- ROFI_COLORS="${HOME}/.config/rofi/colors.rasi" if [[ -L "${ROFI_COLORS}" ]]; then - ln -sf "colors-${MODE}.rasi" "${ROFI_COLORS}" + ln -sf "colors-${MODE}.rasi" "${ROFI_COLORS}" fi # --- Hyprland: flip colors.lua and colors.conf symlinks --- HYPR_LUA="${HOME}/.config/hypr/colors.lua" HYPR_CONF="${HOME}/.config/hypr/colors.conf" if [[ -L "${HYPR_LUA}" ]]; then - ln -sf "colors-${MODE}.lua" "${HYPR_LUA}" + ln -sf "colors-${MODE}.lua" "${HYPR_LUA}" fi if [[ -L "${HYPR_CONF}" ]]; then - ln -sf "colors-${MODE}.conf" "${HYPR_CONF}" + ln -sf "colors-${MODE}.conf" "${HYPR_CONF}" fi DREAMCODER_THEME_MODE="${MODE}" DREAMCODER_WALLPAPER="${WALLPAPER}" \ - PYTHONPATH="${DREAMCODER_DOTS_DIR}/src${PYTHONPATH:+:${PYTHONPATH}}" \ - "${DREAMCODER_DOTS_DIR}/scripts/sync-dreamcoder-theme.py" + PYTHONPATH="${DREAMCODER_DOTS_DIR}/src${PYTHONPATH:+:${PYTHONPATH}}" \ + "${DREAMCODER_DOTS_DIR}/scripts/sync-dreamcoder-theme.py" command -v pkill >/dev/null && pkill -SIGUSR1 kitty 2>/dev/null || true # Restart Waybar so it picks up the new colors.css immediately if command -v pkill >/dev/null; then - pkill waybar 2>/dev/null || true - sleep 0.3 - "${HOME}/.config/waybar/launch.sh" 2>/dev/null || true + pkill waybar 2>/dev/null || true + sleep 0.3 + "${HOME}/.config/waybar/launch.sh" 2>/dev/null || true fi # --- tmux integration: propagate theme to running sessions --- KANAGAWA_DIR="${HOME}/.tmux/plugins/tmux-kanagawa" if command -v tmux >/dev/null 2>&1; then - # Start a headless server if none exists so options persist for new sessions - if ! tmux list-sessions &>/dev/null 2>&1; then - tmux start-server 2>/dev/null || true - sleep 0.1 - fi - # Update global environment so NEW panes/windows inherit the right vars - tmux set-environment -g DREAMCODER_THEME_MODE "${MODE}" 2>/dev/null || true - tmux set-environment -g COLORFGBG "${CLI_COLORFGBG}" 2>/dev/null || true + # Start a headless server if none exists so options persist for new sessions + if ! tmux list-sessions &>/dev/null 2>&1; then + tmux start-server 2>/dev/null || true + sleep 0.1 + fi + # Update global environment so NEW panes/windows inherit the right vars + tmux set-environment -g DREAMCODER_THEME_MODE "${MODE}" 2>/dev/null || true + tmux set-environment -g COLORFGBG "${CLI_COLORFGBG}" 2>/dev/null || true - # Switch tmux-kanagawa theme variant to match Dreamcoder mode - if [[ -d "${KANAGAWA_DIR}" ]]; then - case "${MODE}" in - light) - KANAGAWA_VARIANT="lotus" - # Override default lotus colors with Dreamcoder Light palette - tmux set-option -g @ukiyo-color-text "#17120d" 2>/dev/null || true - tmux set-option -g @ukiyo-color-bg-bar "#e6d7c4" 2>/dev/null || true - tmux set-option -g @ukiyo-color-bg-pane "#f3eadc" 2>/dev/null || true - tmux set-option -g @ukiyo-color-info "#0d4a68" 2>/dev/null || true - tmux set-option -g @ukiyo-color-notice "#a7471c" 2>/dev/null || true - tmux set-option -g @ukiyo-color-accent "#824f16" 2>/dev/null || true - tmux set-option -g @ukiyo-color-muted "#352e22" 2>/dev/null || true - tmux set-option -g @ukiyo-color-error "#842f24" 2>/dev/null || true - tmux set-option -g @ukiyo-color-alert "#654300" 2>/dev/null || true - tmux set-option -g @ukiyo-color-highlight "#0f6570" 2>/dev/null || true - tmux set-option -g @ukiyo-color-selection "#c8ad89" 2>/dev/null || true - # Restore default plugin color order (fg=bg_pane claro sobre bg=color) - tmux set-option -gu "@ukiyo-git-colors" 2>/dev/null || true - tmux set-option -gu "@ukiyo-cpu-usage-colors" 2>/dev/null || true - tmux set-option -gu "@ukiyo-ram-usage-colors" 2>/dev/null || true - ;; - dark) - KANAGAWA_VARIANT="dragon" - # Dreamcoder Ember Noir OLED palette — Ember Noir textures, ember_glow tension - tmux set-option -g @ukiyo-color-text "#e8dfd0" 2>/dev/null || true - tmux set-option -g @ukiyo-color-bg-bar "#181512" 2>/dev/null || true - tmux set-option -g @ukiyo-color-bg-pane "#100f0d" 2>/dev/null || true - tmux set-option -g @ukiyo-color-accent "#d99555" 2>/dev/null || true - tmux set-option -g @ukiyo-color-info "#4db35f" 2>/dev/null || true - tmux set-option -g @ukiyo-color-notice "#c96a45" 2>/dev/null || true - tmux set-option -g @ukiyo-color-muted "#c7b9aa" 2>/dev/null || true - tmux set-option -g @ukiyo-color-error "#ed8a7a" 2>/dev/null || true - tmux set-option -g @ukiyo-color-alert "#e8b866" 2>/dev/null || true - tmux set-option -g @ukiyo-color-highlight "#5f8f8f" 2>/dev/null || true - tmux set-option -g @ukiyo-color-selection "#2b231b" 2>/dev/null || true - # Swap plugin color order: fg=bg_bar (oscuro) sobre bg=color vibrante - tmux set-option -g @ukiyo-git-colors "accent bg_bar" 2>/dev/null || true - tmux set-option -g @ukiyo-cpu-usage-colors "notice bg_bar" 2>/dev/null || true - tmux set-option -g @ukiyo-ram-usage-colors "info bg_bar" 2>/dev/null || true - ;; - esac - tmux set-option -g @ukiyo-theme "kanagawa/${KANAGAWA_VARIANT}" 2>/dev/null || true - # Reload plugin to apply new theme colors immediately - bash "${KANAGAWA_DIR}/ukiyo.tmux" 2>/dev/null || true - fi + # Switch tmux-kanagawa theme variant to match Dreamcoder mode + # SOURCE OF TRUTH: colors MUST match themes/dreamcoder/tokens.json + # When updating tokens, update BOTH light AND dark sections here. + if [[ -d "${KANAGAWA_DIR}" ]]; then + case "${MODE}" in + light) + KANAGAWA_VARIANT="lotus" + # Dreamcoder Light palette — source: tokens.json modes.light.{text,accent,error,...} + tmux set-option -g @ukiyo-color-text "#17120d" 2>/dev/null || true + tmux set-option -g @ukiyo-color-bg-bar "#e6d7c4" 2>/dev/null || true + tmux set-option -g @ukiyo-color-bg-pane "#f3eadc" 2>/dev/null || true + tmux set-option -g @ukiyo-color-info "#0d4a68" 2>/dev/null || true + tmux set-option -g @ukiyo-color-notice "#a7471c" 2>/dev/null || true + tmux set-option -g @ukiyo-color-accent "#824f16" 2>/dev/null || true + tmux set-option -g @ukiyo-color-muted "#352e22" 2>/dev/null || true + tmux set-option -g @ukiyo-color-error "#842f24" 2>/dev/null || true + tmux set-option -g @ukiyo-color-alert "#654300" 2>/dev/null || true + tmux set-option -g @ukiyo-color-highlight "#0f6570" 2>/dev/null || true + tmux set-option -g @ukiyo-color-selection "#c8ad89" 2>/dev/null || true + # Restore default plugin color order (fg=bg_pane claro sobre bg=color) + tmux set-option -gu "@ukiyo-git-colors" 2>/dev/null || true + tmux set-option -gu "@ukiyo-cpu-usage-colors" 2>/dev/null || true + tmux set-option -gu "@ukiyo-ram-usage-colors" 2>/dev/null || true + ;; + dark) + KANAGAWA_VARIANT="dragon" + # Dreamcoder Ember Noir OLED palette — source: tokens.json modes.dark.{text,accent,error,...} + tmux set-option -g @ukiyo-color-text "#e8dfd0" 2>/dev/null || true + tmux set-option -g @ukiyo-color-bg-bar "#181512" 2>/dev/null || true + tmux set-option -g @ukiyo-color-bg-pane "#100f0d" 2>/dev/null || true + tmux set-option -g @ukiyo-color-accent "#d99555" 2>/dev/null || true + tmux set-option -g @ukiyo-color-info "#4db35f" 2>/dev/null || true + tmux set-option -g @ukiyo-color-notice "#c96a45" 2>/dev/null || true + tmux set-option -g @ukiyo-color-muted "#c7b9aa" 2>/dev/null || true + tmux set-option -g @ukiyo-color-error "#ed8a7a" 2>/dev/null || true + tmux set-option -g @ukiyo-color-alert "#e8b866" 2>/dev/null || true + tmux set-option -g @ukiyo-color-highlight "#5f8f8f" 2>/dev/null || true + tmux set-option -g @ukiyo-color-selection "#2b231b" 2>/dev/null || true + # Swap plugin color order: fg=bg_bar (oscuro) sobre bg=color vibrante + tmux set-option -g @ukiyo-git-colors "accent bg_bar" 2>/dev/null || true + tmux set-option -g @ukiyo-cpu-usage-colors "notice bg_bar" 2>/dev/null || true + tmux set-option -g @ukiyo-ram-usage-colors "info bg_bar" 2>/dev/null || true + ;; + esac + tmux set-option -g @ukiyo-theme "kanagawa/${KANAGAWA_VARIANT}" 2>/dev/null || true + # Reload plugin to apply new theme colors immediately + bash "${KANAGAWA_DIR}/ukiyo.tmux" 2>/dev/null || true + fi - # Source standalone tmux theme (pane borders, mode, bell colors) - TMUX_THEME="${HOME}/.config/tmux/tmux-dreamcoder.conf" - if [[ -f "${TMUX_THEME}" ]]; then - tmux source-file "${TMUX_THEME}" 2>/dev/null || true - fi + # Source standalone tmux theme (pane borders, mode, bell colors) + # When kanagawa is active, skip status-bar lines to preserve kanagawa layout + TMUX_THEME="${HOME}/.config/tmux/tmux-dreamcoder.conf" + if [[ -f "${TMUX_THEME}" && ! -d "${KANAGAWA_DIR}" ]]; then + tmux source-file "${TMUX_THEME}" 2>/dev/null || true + elif [[ -f "${TMUX_THEME}" && -d "${KANAGAWA_DIR}" ]]; then + # Kanagawa active: only source color lines, skip status-bar layout + grep -vE '^(set -g status-(left|right|position|interval|justify)|setw -g window-status-(format|current-format|separator))' \ + "${TMUX_THEME}" | tmux source-file /dev/stdin 2>/dev/null || true + fi - printf ' tmux environment and theme updated for %s mode\n' "${MODE}" + printf ' tmux environment and theme updated for %s mode\n' "${MODE}" fi # --- /tmux integration --- @@ -136,12 +146,12 @@ WARP_THEME="${HOME}/.local/share/warp-terminal/themes/Dreamcoder.yaml" WARP_VARIANT="${DOTS_DIR}/Warp/.local/share/warp-terminal/themes/Dreamcoder-${MODE^}.yaml" if [[ -L "${DUNST_CONF}" ]]; then - target=$(readlink "${DUNST_CONF}") - case "${target}" in - *dunst-dreamcoder-dark.conf|*dunst-dreamcoder-light.conf) - ln -sf "${DOTS_DIR}/themes/dreamcoder/dunst-dreamcoder.conf" "${DUNST_CONF}" - ;; - esac + target=$(readlink "${DUNST_CONF}") + case "${target}" in + *dunst-dreamcoder-dark.conf | *dunst-dreamcoder-light.conf) + ln -sf "${DOTS_DIR}/themes/dreamcoder/dunst-dreamcoder.conf" "${DUNST_CONF}" + ;; + esac fi # Warp: flip active theme symlink to mode-specific variant diff --git a/scripts/dreamcoder.sh b/scripts/dreamcoder.sh index 46ce59e..fd2f1cf 100755 --- a/scripts/dreamcoder.sh +++ b/scripts/dreamcoder.sh @@ -1,32 +1,60 @@ #!/usr/bin/env bash set -euo pipefail -# Dreamcoder CLI — quick theme switching -# Usage: dreamcoder {dark|light} +# Dreamcoder CLI — theme switching and system status +# Usage: dreamcoder {dark|light|status|doctor|help} DOTS_DIR="${DREAMCODER_DOTS_DIR:-${HOME}/Documents/PROYECTOS/dreamcoder-dots}" APPLY_SCRIPT="${DOTS_DIR}/scripts/apply-theme-mode.sh" +DOCTOR_SCRIPT="${DOTS_DIR}/scripts/doctor.sh" case "${1:-}" in - dark|light) - export DREAMCODER_THEME_MODE="${1}" - if [[ -f "${APPLY_SCRIPT}" ]]; then - bash "${APPLY_SCRIPT}" "${1}" - else - echo "Error: ${APPLY_SCRIPT} not found" >&2 - exit 1 - fi - ;; - help|--help|-h) - echo "Usage: dreamcoder {dark|light}" - echo "" - echo "Switch Dreamcoder theme mode across all terminals and apps." - echo " dark → Ember Noir OLED (dark mode)" - echo " light → Dreamcoder Light" - exit 0 - ;; - *) - echo "Usage: dreamcoder {dark|light}" >&2 - exit 1 - ;; +dark | light) + export DREAMCODER_THEME_MODE="${1}" + if [[ -f "${APPLY_SCRIPT}" ]]; then + bash "${APPLY_SCRIPT}" "${1}" + else + echo "Error: ${APPLY_SCRIPT} not found" >&2 + exit 1 + fi + ;; +status) + echo "=== Dreamcoder OS Status ===" + echo "Theme mode: ${DREAMCODER_THEME_MODE:-dark}" + echo "Dotfiles dir: ${DOTS_DIR}" + echo "Git branch: $(cd "${DOTS_DIR}" && git branch --show-current 2>/dev/null || echo 'N/A')" + echo "Last commit: $(cd "${DOTS_DIR}" && git log -1 --oneline 2>/dev/null || echo 'N/A')" + echo "" + if command -v systemctl &>/dev/null; then + echo "Timer: $(systemctl --user is-active dreamcoder-theme-auto.timer 2>/dev/null || echo 'inactive')" + fi + if [[ -f "${HOME}/.cache/dreamcoder/ai-session.state" ]]; then + echo "AI session: $(cat "${HOME}/.cache/dreamcoder/ai-session.state")" + else + echo "AI session: inactive" + fi + ;; +doctor) + if [[ -f "${DOCTOR_SCRIPT}" ]]; then + bash "${DOCTOR_SCRIPT}" + else + echo "Error: ${DOCTOR_SCRIPT} not found" >&2 + exit 1 + fi + ;; +help | --help | -h) + echo "Usage: dreamcoder " + echo "" + echo "Commands:" + echo " dark Switch to Ember Noir OLED (dark mode)" + echo " light Switch to Dreamcoder Light" + echo " status Show system status overview" + echo " doctor Run health checks on all components" + echo " help Show this help" + exit 0 + ;; +*) + echo "Usage: dreamcoder {dark|light|status|doctor|help}" >&2 + exit 1 + ;; esac diff --git a/scripts/generate-palette-tokens.py b/scripts/generate-palette-tokens.py new file mode 100644 index 0000000..3cabc8d --- /dev/null +++ b/scripts/generate-palette-tokens.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +"""Generate palette_tokens.py from themes/dreamcoder/tokens.json.""" + +from __future__ import annotations + +import json +import math +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +TOKENS_FILE = ROOT / "themes" / "dreamcoder" / "tokens.json" +OUTPUT = ROOT / "src" / "dreamcoder_theme" / "palette_tokens.py" + +ANSI_KEY_NAMES = [ + "surface0", + "error", + "sage", + "accent", + "diagnostic", + "mauve", + "lavender", + "muted", + "subtle", + "error_bright", + "sage_bright", + "warning", + "diagnostic_bright", + "lavender_bright", + "focus_bright", + "text", +] + + +def hex_to_rgb(value: str) -> tuple[float, float, float]: + value = value.lstrip("#") + return ( + int(value[0:2], 16) / 255, + int(value[2:4], 16) / 255, + int(value[4:6], 16) / 255, + ) + + +def rgb_to_hex(rgb: tuple[float, float, float]) -> str: + return "#" + "".join(f"{max(0, min(255, round(c * 255))):02x}" for c in rgb) + + +_SRGB_BREAK = 0.04045 +_SRGB_LINEAR_BREAK = 0.0031308 + + +def srgb_to_linear(c: float) -> float: + return c / 12.92 if c <= _SRGB_BREAK else ((c + 0.055) / 1.055) ** 2.4 + + +def linear_to_srgb(c: float) -> float: + return 12.92 * c if c <= _SRGB_LINEAR_BREAK else 1.055 * (c ** (1 / 2.4)) - 0.055 + + +def hex_to_oklch(value: str) -> tuple[float, float, float]: + r, g, b = hex_to_rgb(value) + lr, lg, lb = srgb_to_linear(r), srgb_to_linear(g), srgb_to_linear(b) + lms_l = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb + lms_m = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb + lms_s = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb + lms_l_ = lms_l ** (1 / 3) + lms_m_ = lms_m ** (1 / 3) + lms_s_ = lms_s ** (1 / 3) + lightness = 0.2104542553 * lms_l_ + 0.7936177850 * lms_m_ - 0.0040720468 * lms_s_ + a = 1.9779984951 * lms_l_ - 2.4285922050 * lms_m_ + 0.4505937099 * lms_s_ + b_ = 0.0259040371 * lms_l_ + 0.7827717662 * lms_m_ - 0.8086757660 * lms_s_ + chroma = math.hypot(a, b_) + hue = math.degrees(math.atan2(b_, a)) % 360 + return lightness, chroma, hue + + +def oklch_to_hex(lightness: float, chroma: float, hue: float) -> str: + h_rad = math.radians(hue) + a = chroma * math.cos(h_rad) + b_ = chroma * math.sin(h_rad) + l_ = lightness + 0.3963377774 * a + 0.2158037573 * b_ + m_ = lightness - 0.1055613458 * a - 0.0638541728 * b_ + s_ = lightness - 0.0894841775 * a - 1.2914855480 * b_ + l_c = l_**3 + m_c = m_**3 + s_c = s_**3 + lr = +4.0767416621 * l_c - 3.3077115913 * m_c + 0.2309699292 * s_c + lg = -1.2684380046 * l_c + 2.6097574011 * m_c - 0.3413193965 * s_c + lb = -0.0041960863 * l_c - 0.7034186147 * m_c + 1.7076147010 * s_c + return rgb_to_hex( + ( + linear_to_srgb(max(0, min(1, lr))), + linear_to_srgb(max(0, min(1, lg))), + linear_to_srgb(max(0, min(1, lb))), + ) + ) + + +def mix_hex(left: str, right: str, amount: float) -> str: + a = hex_to_rgb(left) + b = hex_to_rgb(right) + return rgb_to_hex(tuple(x + (y - x) * amount for x, y in zip(a, b))) + + +def ramp_step(base: str, target_l_delta: float) -> str: + lightness, chroma, hue = hex_to_oklch(base) + return oklch_to_hex(max(0, min(1, lightness + target_l_delta)), chroma, hue) + + +def rgba_from_hex(hex_color: str, alpha: float) -> str: + r, g, b = hex_to_rgb(hex_color) + return f"rgba({round(r * 255)}, {round(g * 255)}, {round(b * 255)}, {alpha:.2f})" + + +def enrich_mode(mode: dict[str, str]) -> dict[str, str]: + """Fill derived semantic tokens; preserve authored anchors.""" + c = dict(mode) + is_dark = c.get("details") == "darker" + bg = c["bg"] + text = c["text"] + + c.setdefault("success", c["sage"]) + c.setdefault("info", c["diagnostic"]) + c.setdefault("text_heading", ramp_step(text, 0.04 if is_dark else -0.03)) + c.setdefault("surface3", ramp_step(c["surface2"], -0.04 if is_dark else -0.05)) + + c.setdefault("selection_bg", c.get("surface1", c["bg"])) + c.setdefault("selection_fg", text) + c["selection"] = c["selection_bg"] + + on_light = c.get("surface0", "#ffffff") + on_dark = c.get("prompt_text", bg) + c.setdefault("on_surface", text) + c.setdefault("on_accent", on_dark if is_dark else on_light) + c.setdefault("on_error", c["on_accent"]) + c.setdefault("on_focus", c["on_accent"]) + + c.setdefault("link", c["accent"]) + c.setdefault("link_hover", c["accent_2"]) + c.setdefault("disabled", mix_hex(c["muted"], bg, 0.35)) + c.setdefault("hover", c["surface1"]) + c.setdefault("pressed", c["surface2"]) + c.setdefault("overlay", rgba_from_hex(bg, 0.52 if is_dark else 0.40)) + c.setdefault("scrim", "rgba(0, 0, 0, 0.58)" if is_dark else "rgba(26, 18, 12, 0.42)") + + if "panel_rgba" not in c: + c["panel_rgba"] = rgba_from_hex(bg, 0.78 if is_dark else 0.96) + if "module_rgba" not in c: + fg = mix_hex(text, bg, 0.08 if is_dark else 0.0) + c["module_rgba"] = rgba_from_hex(fg, 0.08 if is_dark else 0.80) + if "active_rgba" not in c: + c["active_rgba"] = rgba_from_hex(c["accent"], 0.24 if is_dark else 0.34) + if "inactive_border" not in c: + c["inactive_border"] = rgba_from_hex(c["border"], 0.50 if is_dark else 0.93) + + return c + + +def render_palette_tokens(variants: dict[str, dict[str, str]]) -> str: + lines = [ + '"""Static palette token data for Dreamcoder themes.', + "", + "AUTO-GENERATED from themes/dreamcoder/tokens.json — do not edit by hand.", + "Run: ./scripts/generate-palette-tokens.py", + '"""', + "", + "from __future__ import annotations", + "", + "VARIANTS = " + json.dumps(variants, indent=4) + "", + "", + "ANSI_KEY_NAMES = " + json.dumps(ANSI_KEY_NAMES, indent=4) + "", + "", + ] + return "\n".join(lines) + + +def main() -> int: + tokens = json.loads(TOKENS_FILE.read_text()) + modes = tokens.get("modes", {}) + enriched: dict[str, dict[str, str]] = {} + for name, palette in modes.items(): + enriched[name] = enrich_mode(palette) + + tokens["modes"] = enriched + tokens["guardrails"].setdefault("minimum_apca_on_accent", 60) + tokens["guardrails"].setdefault("minimum_apca_heading_light", 60) + tokens["guardrails"].setdefault("minimum_apca_heading_dark", 45) + TOKENS_FILE.write_text(json.dumps(tokens, indent=2) + "\n") + + subset = {k: enriched[k] for k in ("dark", "light", "dusk") if k in enriched} + OUTPUT.write_text(render_palette_tokens(subset)) + print(f"✓ Updated {TOKENS_FILE.relative_to(ROOT)}") + print(f"✓ Generated {OUTPUT.relative_to(ROOT)}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/generate-theme-preview.py b/scripts/generate-theme-preview.py index 6145715..ba90511 100755 --- a/scripts/generate-theme-preview.py +++ b/scripts/generate-theme-preview.py @@ -22,7 +22,9 @@ "surface0", "surface1", "surface2", + "surface3", "text", + "text_heading", "muted", "subtle", "comment", @@ -30,10 +32,18 @@ "accent_2", "diagnostic", "sage", + "success", + "info", "lavender", "mauve", "error", "warning", + "on_accent", + "on_error", + "link", + "link_hover", + "selection_bg", + "selection_fg", "border", "border_ui", "border_hi", @@ -157,9 +167,7 @@ def contrast_table(name, palette): bg = palette["bg"] for key in TEXT_KEYS: ratio = contrast(bg, palette[key]) - target = ( - "AAA" if key == "text" and ratio >= 7 else "AA" if ratio >= 4.5 else "FAIL" - ) + target = "AAA" if key == "text" and ratio >= 7 else "AA" if ratio >= 4.5 else "FAIL" rows.append(f"| `{key}` | {ratio:.2f}:1 | {target} |") return "\n".join(rows) @@ -218,8 +226,8 @@ def main(): "", "- `comment` is softer and lower-chroma than `subtle` (syntax vs UI chrome).", "- Dark `accent` (refined ember orange), `accent_2` (maple red), `error` (soft coral red), and `warning` (lúcuma gold) form the orange/red/gold signature.", - "- `focus` follows the orange protagonist instead of a separate cyan ring; `diagnostic` stays warm amber so the palette remains autumnal.", - "", + "- `accent` carries brand CTAs and active chrome; `focus` is teal for keyboard/input affordance (WCAG ring).", + "- `on_accent`, `on_error`, and `selection_bg`/`selection_fg` are explicit pairs validated in CI.", ] parts += ["## Palette", ""] for mode, palette in tokens["modes"].items(): diff --git a/scripts/sync-dreamcoder-theme.py b/scripts/sync-dreamcoder-theme.py index 345e3fc..ae36eff 100755 --- a/scripts/sync-dreamcoder-theme.py +++ b/scripts/sync-dreamcoder-theme.py @@ -3,6 +3,5 @@ from dreamcoder_theme.sync import main - if __name__ == "__main__": main() diff --git a/scripts/verify-theme-health.py b/scripts/verify-theme-health.py index 659c9b7..0144397 100755 --- a/scripts/verify-theme-health.py +++ b/scripts/verify-theme-health.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 import json +import os import plistlib import re from pathlib import Path + from dreamcoder_theme.palette import ansi as terminal_ansi ROOT = Path(__file__).resolve().parent.parent @@ -18,17 +20,88 @@ ROOT / "Codex-CLI/Dreamcoder-Light.tmTheme", ROOT / "Codex-CLI/Dreamcoder-Dark.tmTheme", ] -KITTY_FILE = ROOT / "Kitty" / ".config" / "kitty" / "colors-dreamcoder-dark.conf" -STARSHIP_FILE = ROOT / "Shell" / ".config" / "starship-dark.toml" -GHOSTTY_FILE = ROOT / "Ghostty" / ".config" / "ghostty" / "themes" / "dreamcoder-dark" -WAYBAR_FILE = ROOT / "themes" / "dreamcoder" / "waybar-dark.css" -HYPRLAND_FILE = ROOT / "themes" / "dreamcoder" / "hyprland-dark.conf" -ROFI_FILE = ROOT / "themes" / "dreamcoder" / "rofi-dark.rasi" +KITTY_FILES = [ + ROOT / "Kitty" / ".config" / "kitty" / "colors-dreamcoder-dark.conf", + ROOT / "Kitty" / ".config" / "kitty" / "colors-dreamcoder-light.conf", +] +STARSHIP_FILES = [ + ROOT / "Shell" / ".config" / "starship-dark.toml", + ROOT / "Shell" / ".config" / "starship-light.toml", +] +GHOSTTY_FILES = [ + ROOT / "Ghostty" / ".config" / "ghostty" / "themes" / "dreamcoder-dark", + ROOT / "Ghostty" / ".config" / "ghostty" / "themes" / "dreamcoder-light", +] +WAYBAR_FILES = [ + ROOT / "themes" / "dreamcoder" / "waybar-dark.css", + ROOT / "themes" / "dreamcoder" / "waybar-light.css", +] +HYPRLAND_FILES = [ + ROOT / "themes" / "dreamcoder" / "hyprland-dark.conf", + ROOT / "themes" / "dreamcoder" / "hyprland-light.conf", +] +ROFI_FILES = [ + ROOT / "themes" / "dreamcoder" / "rofi-dark.rasi", + ROOT / "themes" / "dreamcoder" / "rofi-light.rasi", +] +BTOP_FILES = [ + ROOT / "themes" / "dreamcoder" / "btop-dreamcoder-dark.theme", + ROOT / "themes" / "dreamcoder" / "btop-dreamcoder-light.theme", +] +DUNST_FILES = [ + ROOT / "themes" / "dreamcoder" / "dunst-dreamcoder-dark.conf", + ROOT / "themes" / "dreamcoder" / "dunst-dreamcoder-light.conf", +] +FZF_FILES = [ + ROOT / "themes" / "dreamcoder" / "fzf-dreamcoder-dark.sh", + ROOT / "themes" / "dreamcoder" / "fzf-dreamcoder-light.sh", +] HYPR_COLORS_LUA_GLOB = list((ROOT / "themes" / "dreamcoder").glob("hypr-colors-*.lua")) HYPR_COLORS_CONF_GLOB = list((ROOT / "themes" / "dreamcoder").glob("hypr-colors-*.conf")) -BTOP_FILE = ROOT / "themes" / "dreamcoder" / "btop-dreamcoder-dark.theme" -DUNST_FILE = ROOT / "themes" / "dreamcoder" / "dunst-dreamcoder-dark.conf" -FZF_FILE = ROOT / "themes" / "dreamcoder" / "fzf-dreamcoder-dark.sh" +TOKEN_PARITY_KEYS = [ + "bg", + "bg_soft", + "surface0", + "surface1", + "surface2", + "surface3", + "text", + "text_heading", + "muted", + "subtle", + "comment", + "border", + "border_ui", + "border_hi", + "focus", + "accent", + "accent_2", + "diagnostic", + "sage", + "success", + "info", + "error", + "warning", + "lavender", + "mauve", + "on_surface", + "on_accent", + "on_error", + "on_focus", + "link", + "link_hover", + "selection_bg", + "selection_fg", + "disabled", + "hover", + "pressed", +] +ON_PAIRS = [ + ("on_accent", "accent"), + ("on_error", "error"), + ("on_focus", "focus"), + ("selection_fg", "selection_bg"), +] TEXT_KEYS = ["text", "textMuted", "primary", "info", "success", "error", "warning"] UI_KEYS = ["border", "borderActive", "borderFocus"] QUIET_UI_KEYS = ["border"] @@ -70,15 +143,14 @@ "error", "warning", ] -TOKEN_BODY_APCA_KEYS = ["text", "diagnostic", "error", "warning"] +TOKEN_BODY_APCA_KEYS = ["text", "error", "warning"] +TOKEN_INFO_APCA_KEYS = ["diagnostic"] TOKEN_ACCENT_APCA_KEYS = ["accent", "accent_2", "sage"] TOKEN_QUIET_APCA_KEYS = ["muted", "comment", "subtle"] TOKEN_UI_KEYS = ["border_ui", "focus"] HEX = re.compile(r"^#[0-9a-fA-F]{6}$") # RGBA pattern: rgba(r, g, b, a) with r/g/b 0-255 and a 0.0-1.0 -RGBA = re.compile( - r"^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0|1|0?\.?\d+)\s*\)$" -) +RGBA = re.compile(r"^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0|1|0?\.?\d+)\s*\)$") def rgb(value): @@ -211,10 +283,17 @@ def check_rgba_tokens(): ) +def check_apca_require(mode, key, value, bg, threshold): + """Fail CI when APCA body/heading pairs miss targets.""" + lc = apca_lc(value, bg) + require( + lc >= threshold, + f"tokens:{mode}:{key} APCA Lc {lc:.1f} < {threshold}", + ) + + def check_apca_or_warn(mode, key, value, bg, threshold): """Check APCA contrast, log warning but don't fail (APCA is public beta, not a standard).""" - import os - lc = apca_lc(value, bg) if os.environ.get("DREAMCODER_ENFORCE_APCA", "") and lc < threshold: require( @@ -227,6 +306,17 @@ def check_apca_or_warn(mode, key, value, bg, threshold): ) +def check_token_parity(tokens): + dark = tokens["modes"]["dark"] + light = tokens["modes"]["light"] + dark_keys = {k for k in dark if k not in {"name", "details"}} + light_keys = {k for k in light if k not in {"name", "details"}} + require(dark_keys == light_keys, f"dark/light token key mismatch: {dark_keys ^ light_keys}") + for key in TOKEN_PARITY_KEYS: + require(key in dark, f"tokens:dark missing parity key {key}") + require(key in light, f"tokens:light missing parity key {key}") + + def check_tokens(): tokens = json.loads(TOKEN_FILE.read_text()) guardrails = tokens["guardrails"] @@ -237,24 +327,24 @@ def check_tokens(): apca_ui_dark = guardrails.get("minimum_apca_ui_dark", 28) terminal_ansi_min = guardrails.get("minimum_terminal_ansi_contrast", 4.5) terminal_cursor_min = guardrails.get("minimum_terminal_cursor_contrast", 4.5) + apca_heading_light = guardrails.get("minimum_apca_heading_light", 60) + apca_heading_dark = guardrails.get("minimum_apca_heading_dark", 45) + apca_on_accent = guardrails.get("minimum_apca_on_accent", 60) terminal_selection_min = guardrails.get("minimum_terminal_selection_contrast", 7.0) require( - tokens["guardrails"]["canonical_opencode_theme"] == "dreamcoder", + guardrails["canonical_opencode_theme"] == "dreamcoder", "tokens: canonical opencode theme must be dreamcoder", ) + check_token_parity(tokens) for mode, palette in tokens["modes"].items(): bg = palette["bg"] require(HEX.match(bg), f"tokens:{mode}: invalid background") - require( - bg.lower() not in {"#000000", "#ffffff"}, f"tokens:{mode}: harsh background" - ) + require(bg.lower() not in {"#000000", "#ffffff"}, f"tokens:{mode}: harsh background") for key in TOKEN_TEXT_KEYS: require(key in palette, f"tokens:{mode}:{key}: missing token") value = palette[key] require(HEX.match(value), f"tokens:{mode}:{key}: invalid hex") - require( - contrast(bg, value) >= 4.5, f"tokens:{mode}:{key} contrast below 4.5" - ) + require(contrast(bg, value) >= 4.5, f"tokens:{mode}:{key} contrast below 4.5") if palette.get("details") == "lighter": apca_body = apca_body_light apca_body_keys = TOKEN_BODY_APCA_KEYS @@ -267,7 +357,15 @@ def check_tokens(): apca_accent = apca_body_dark for key in apca_body_keys: value = palette[key] - check_apca_or_warn(mode, key, value, bg, apca_body) + check_apca_require(mode, key, value, bg, apca_body) + for key in TOKEN_INFO_APCA_KEYS: + if key not in palette: + continue + info_target = 42 if palette.get("details") == "darker" else apca_body + check_apca_require(mode, key, palette[key], bg, info_target) + if "text_heading" in palette: + heading_target = apca_heading_light if mode == "light" else apca_heading_dark + check_apca_require(mode, "text_heading", palette["text_heading"], bg, heading_target) for key in apca_accent_keys: value = palette[key] check_apca_or_warn(mode, key, value, bg, apca_accent) @@ -276,18 +374,23 @@ def check_tokens(): continue value = palette[key] check_apca_or_warn(mode, key, value, bg, apca_quiet) - require( - contrast(bg, palette["text"]) >= 7, f"tokens:{mode}: main text below AAA" - ) - check_apca_or_warn(mode, "text", palette["text"], bg, apca_body) + require(contrast(bg, palette["text"]) >= 7, f"tokens:{mode}: main text below AAA") + check_apca_require(mode, "text", palette["text"], bg, apca_body) + for fg_key, bg_key in ON_PAIRS: + require( + fg_key in palette and bg_key in palette, f"tokens:{mode}: missing {fg_key}/{bg_key}" + ) + pair_ratio = contrast(palette[fg_key], palette[bg_key]) + require(pair_ratio >= 4.5, f"tokens:{mode}:{fg_key}/{bg_key} {pair_ratio:.2f} < 4.5") + if fg_key == "on_accent": + check_apca_require(mode, fg_key, palette[fg_key], palette[bg_key], apca_on_accent) for index, color in enumerate(terminal_ansi(palette)): require( contrast(color, bg) >= terminal_ansi_min, f"tokens:{mode}: ANSI color{index} contrast {contrast(color, bg):.2f} < {terminal_ansi_min}", ) - invert = palette.get("details") == "lighter" - sel_fg = palette["bg"] if invert else palette["text"] - sel_bg = palette["text"] if invert else palette["selection"] + sel_fg = palette["selection_fg"] + sel_bg = palette["selection_bg"] require( contrast(palette["accent"], bg) >= terminal_cursor_min, f"tokens:{mode}: cursor contrast {contrast(palette['accent'], bg):.2f} < {terminal_cursor_min}", @@ -330,9 +433,7 @@ def check_theme_file(file): bg not in {"#000000", "#ffffff"}, f"{file}: background uses harsh pure black/white", ) - require( - 0.004 < lum(bg) < 0.94, f"{file}: background luminance is outside comfort band" - ) + require(0.004 < lum(bg) < 0.94, f"{file}: background luminance is outside comfort band") for key in TEXT_KEYS + SYNTAX_KEYS: ratio = contrast(bg, theme[key]) require(ratio >= 4.5, f"{file}: {key} contrast {ratio:.2f} < 4.5") @@ -374,9 +475,7 @@ def check_codex_cli_theme(file): contrast(bg, settings["lineHighlight"]) >= 1.15, f"{file}: light line highlight too faint", ) - require( - contrast(bg, settings["gutter"]) >= 1.45, f"{file}: light gutter too faint" - ) + require(contrast(bg, settings["gutter"]) >= 1.45, f"{file}: light gutter too faint") def check_opencode_repo(): @@ -493,16 +592,24 @@ def check_hypr_colors_file(file): for file in CODEX_CLI_FILES: check_codex_cli_theme(file) check_opencode_repo() -# Optional target validations (skip if files don't exist) -check_kitty_colors(KITTY_FILE) -check_starship_config(STARSHIP_FILE) -check_ghostty_theme(GHOSTTY_FILE) -check_waybar_css(WAYBAR_FILE) -check_hypr_config(HYPRLAND_FILE) -check_rofi_theme(ROFI_FILE) -check_btop_theme(BTOP_FILE) -check_dunst_theme(DUNST_FILE) -check_fzf_theme(FZF_FILE) +for file in KITTY_FILES: + check_kitty_colors(file) +for file in STARSHIP_FILES: + check_starship_config(file) +for file in GHOSTTY_FILES: + check_ghostty_theme(file) +for file in WAYBAR_FILES: + check_waybar_css(file) +for file in HYPRLAND_FILES: + check_hypr_config(file) +for file in ROFI_FILES: + check_rofi_theme(file) +for file in BTOP_FILES: + check_btop_theme(file) +for file in DUNST_FILES: + check_dunst_theme(file) +for file in FZF_FILES: + check_fzf_theme(file) for file in HYPR_COLORS_LUA_GLOB: check_hypr_colors_file(file) for file in HYPR_COLORS_CONF_GLOB: diff --git a/skills/dreamcoder-palette-tokens/SKILL.md b/skills/dreamcoder-palette-tokens/SKILL.md new file mode 100644 index 0000000..aaacea1 --- /dev/null +++ b/skills/dreamcoder-palette-tokens/SKILL.md @@ -0,0 +1,49 @@ +# Dreamcoder Palette Tokens + +## Token Schema + +Defined in `themes/dreamcoder/tokens.json` with validation schema at `tokens.schema.json`. + +## Modes + +| Mode | Name | BG | Accent | Use Case | +|------|------|----|--------|----------| +| `dark` | Ember Noir OLED | `#100f0d` | `#d99555` | 18:00-07:00 | +| `light` | Cocoa/Lúcuma | `#f3eadc` | `#824f16` | 07:00-16:00 | +| `dusk` | Transition | `#ebe4d6` | `#8a5520` | 16:00-18:00 | + +## Token Categories + +- **Background**: `bg`, `bg_soft`, `surface{0,1,2,3}` +- **Text**: `text`, `text_heading`, `muted`, `subtle`, `comment` +- **Accent**: `accent`, `accent_2`, `focus` +- **Semantic**: `error`, `warning`, `success`, `info`, `diagnostic` +- **UI**: `border`, `border_ui`, `border_hi`, `selection` +- **Prompt**: `prompt_bg`, `prompt_surface{0,1,2}`, `prompt_text`, `prompt_muted`, `prompt_accent` + +## Guardrails (WCAG + APCA) + +```json +{ + "minimum_text_contrast": 4.5, + "preferred_main_text_contrast": 7.0, + "minimum_apca_body": 75, + "minimum_apca_body_dark": 50, + "avoid_pure_black_white": true +} +``` + +## Color Tokens to Update + +When updating a color in `tokens.json`, also update: + +1. `src/dreamcoder_theme/palette_tokens.py` (regenerated from tokens.json) +2. `scripts/apply-theme-mode.sh` (kanagawa tmux colors) +3. `src/dreamcoder_theme/renderers_starship.py` (palette section) + +## Validation + +```bash +python scripts/verify-theme-health.py # Validates all tokens +python scripts/generate-theme-preview.py # Generates docs preview +``` diff --git a/skills/dreamcoder-theme-engine/SKILL.md b/skills/dreamcoder-theme-engine/SKILL.md new file mode 100644 index 0000000..73d39bd --- /dev/null +++ b/skills/dreamcoder-theme-engine/SKILL.md @@ -0,0 +1,45 @@ +# Dreamcoder Theme Engine + +## Purpose + +Python theme engine that reads `tokens.json` and generates color configs for 28+ targets. + +## Architecture + +``` +tokens.json → palette_tokens.py (static tokens) + → palette.py (adaptive from wallpaper) + → renderers*.py (28+ target formats) + → writers.py (write_if_changed) +``` + +## Key Files + +- `src/dreamcoder_theme/palette_tokens.py` — Canonical token definitions (dark/light/dusk) +- `src/dreamcoder_theme/palette.py` — `guard()` for WCAG contrast validation, `adaptive_palette()` for wallpaper colors +- `src/dreamcoder_theme/renderers.py` — Hub that imports all `renderers_*.py` leaf modules +- `src/dreamcoder_theme/renderers_kitty.py` — Kitty terminal colors +- `src/dreamcoder_theme/renderers_ghostty_warp.py` — Ghostty + Warp +- `src/dreamcoder_theme/renderers_starship.py` — Starship prompt (23 modules) +- `src/dreamcoder_theme/renderers_tmux.py` — Tmux theme +- `src/dreamcoder_theme/writers.py` — `write_if_changed()`, `update_ghostty_theme()`, `ensure_kitty_ui_include()` +- `src/dreamcoder_theme/sync.py` — Orchestrator: loads variants, renders, writes + +## Adding a New Renderer + +1. Create `renderers_.py` with a function `def _content(palette: dict) -> str` +2. Import it in `renderers.py` `__all__` list +3. Add it to `sync_active_targets()` or `sync_repo_snippets()` in `sync.py` +4. Add path to `ThemePaths` dataclass in `settings.py` + +## Testing + +```bash +pytest tests/ -v +pytest tests/ --cov=dreamcoder_theme --cov-fail-under=40 +``` + +## Token Guardrails + +All colors must pass WCAG 4.5:1 minimum contrast and APCA body minimums. +Use `guard(color, background, mode)` from `palette.py` to validate. diff --git a/src/dreamcoder_theme/palette.py b/src/dreamcoder_theme/palette.py index 58172c4..4545cf8 100644 --- a/src/dreamcoder_theme/palette.py +++ b/src/dreamcoder_theme/palette.py @@ -8,7 +8,7 @@ import warnings from pathlib import Path -from .palette_tokens import ANSI_KEYS +from .palette_tokens import ANSI_KEY_NAMES def load_variants( @@ -22,7 +22,6 @@ def load_variants( for key in ("dark", "light", "dusk"): if key in modes: merged[key].update(modes[key]) - # Reconcile: warn on silent divergence between defaults and tokens.json for mode_key in ("dark", "light", "dusk"): if mode_key in modes and mode_key in defaults: for token_key in set(defaults[mode_key]) & set(modes[mode_key]): @@ -32,7 +31,7 @@ def load_variants( warnings.warn( f"palette divergence: {mode_key}.{token_key} = {t!r} (tokens.json) " f"overrides {d!r} (palette_tokens.py). " - f"palette_tokens.py should be regenerated from tokens.json.", + f"Run ./scripts/generate-palette-tokens.py.", stacklevel=2, ) return merged @@ -43,6 +42,10 @@ def matugen_mode_name(mode_name: str) -> str: def resolve_color(palette: dict[str, str], value: str) -> str: + if value.endswith("_bright"): + base = value.removesuffix("_bright") + if base in palette: + return mix(palette[base], palette["text"], 0.18) return palette.get(value, value) @@ -69,9 +72,9 @@ def rel_luminance(value: str) -> float: sr = r / 255 sg = g / 255 sb = b / 255 - lr = sr / 12.92 if sr <= 0.03928 else ((sr + 0.055) / 1.055) ** 2.4 # noqa: PLR2004 - lg = sg / 12.92 if sg <= 0.03928 else ((sg + 0.055) / 1.055) ** 2.4 # noqa: PLR2004 - lb = sb / 12.92 if sb <= 0.03928 else ((sb + 0.055) / 1.055) ** 2.4 # noqa: PLR2004 + lr = sr / 12.92 if sr <= 0.03928 else ((sr + 0.055) / 1.055) ** 2.4 + lg = sg / 12.92 if sg <= 0.03928 else ((sg + 0.055) / 1.055) ** 2.4 + lb = sb / 12.92 if sb <= 0.03928 else ((sb + 0.055) / 1.055) ** 2.4 return 0.2126 * lr + 0.7152 * lg + 0.0722 * lb # type: ignore[no-any-return, unused-ignore] @@ -90,6 +93,26 @@ def guard(color: str, background: str, mode_name: str, minimum: float = 4.5) -> return safe +def compute_on_color( + background: str, + mode_name: str, + *, + light_candidate: str | None = None, + dark_candidate: str | None = None, + minimum: float = 4.5, +) -> str: + """Pick readable foreground on a filled surface.""" + if mode_name == "dark": + candidates = [c for c in (dark_candidate, "#100f0d", "#000000") if c] + else: + candidates = [c for c in (light_candidate, "#fff7ea", "#ffffff") if c] + for candidate in candidates: + if contrast(candidate, background) >= minimum: + return candidate + target = candidates[-1] + return guard(target, background, mode_name, minimum=minimum) + + def surface_guard( color: str, background: str, @@ -149,7 +172,7 @@ def adaptive_palette( c = dict(base) bg = mix(c["bg"], scheme.get("background", c["bg"]), 0.18) - if contrast(bg, c["text"]) >= 7: # noqa: PLR2004 + if contrast(bg, c["text"]) >= 7: c["bg"] = bg c["surface0"] = surface_guard( mix(c["surface0"], scheme.get("surface_container", c["surface0"]), 0.16), @@ -183,7 +206,12 @@ def adaptive_palette( mode_name, ) c["border"] = mix(c["border"], scheme.get("outline", c["border"]), 0.25) - c["selection"] = mix(c["selection"], scheme.get("primary_container", c["selection"]), 0.18) + c["selection_bg"] = mix( + c.get("selection_bg", c["surface1"]), + scheme.get("primary_container", c.get("selection_bg", c["surface1"])), + 0.18, + ) + c["selection"] = c["selection_bg"] c["prompt_accent"] = c["accent"] c["prompt_accent_2"] = c["accent_2"] return c @@ -192,8 +220,10 @@ def adaptive_palette( def ansi(palette: dict[str, str]) -> list[str]: mode_name = "dark" if palette["details"] == "darker" else "light" safe = [] - for key in ANSI_KEYS: + for key in ANSI_KEY_NAMES: color = resolve_color(palette, key) + if not color.startswith("#"): + raise ValueError(f"ANSI key {key!r} resolved to non-hex {color!r}") safe.append(guard(color, palette["bg"], mode_name)) return safe @@ -201,3 +231,58 @@ def ansi(palette: dict[str, str]) -> list[str]: def detect_mode(palette: dict[str, str]) -> str: """Return "dark" or "light" based on the palette's details key.""" return "dark" if palette.get("details") == "darker" else "light" + + +def validate_palette( + palette: dict[str, str], guardrails: dict[str, float] | None = None +) -> list[str]: + """Return human-readable validation errors for a mode palette.""" + g = guardrails or {} + errors: list[str] = [] + bg = palette["bg"] + mode = detect_mode(palette) + text_min = g.get("minimum_text_contrast", 4.5) + main_min = g.get("preferred_main_text_contrast", 7.0) + sel_min = g.get("minimum_terminal_selection_contrast", 7.0) + + for key in ("text", "muted", "comment", "accent", "error", "warning", "diagnostic"): + if key not in palette: + errors.append(f"missing token: {key}") + continue + if contrast(bg, palette[key]) < text_min: + errors.append(f"{key} contrast {contrast(bg, palette[key]):.2f} < {text_min}") + + if contrast(bg, palette["text"]) < main_min: + errors.append(f"main text contrast {contrast(bg, palette['text']):.2f} < {main_min}") + + for fg_key, bg_key in ( + ("selection_fg", "selection_bg"), + ("on_accent", "accent"), + ("on_error", "error"), + ): + if fg_key in palette and bg_key in palette: + ratio = contrast(palette[fg_key], palette[bg_key]) + if fg_key == "selection_fg" and ratio < sel_min: + errors.append(f"selection pair {ratio:.2f} < {sel_min}") + if fg_key.startswith("on_") and ratio < 4.5: + errors.append(f"{fg_key}/{bg_key} contrast {ratio:.2f} < 4.5") + + if "on_accent" in palette and "accent" in palette: + if contrast(palette["on_accent"], palette["accent"]) < 4.5: + errors.append("on_accent/accent WCAG contrast below 4.5") + + for index, color in enumerate(ansi(palette)): + if contrast(color, bg) < g.get("minimum_terminal_ansi_contrast", 4.5): + errors.append(f"ANSI color{index} contrast too low") + + for step in ("bg_soft", "surface0", "surface1", "surface2", "surface3"): + if step in palette and contrast(palette[step], bg) < 1.02: + errors.append(f"{step} too close to bg") + + if palette.get("comment") == palette.get("subtle"): + errors.append("comment and subtle must differ") + if palette.get("accent") == palette.get("accent_2"): + errors.append("accent and accent_2 must differ") + if mode == "light" and "surface3" not in palette: + errors.append("light mode missing surface3") + return errors diff --git a/src/dreamcoder_theme/palette_tokens.py b/src/dreamcoder_theme/palette_tokens.py index 60191a5..83a4816 100644 --- a/src/dreamcoder_theme/palette_tokens.py +++ b/src/dreamcoder_theme/palette_tokens.py @@ -1,4 +1,8 @@ -"""Static palette token data for Dreamcoder themes.""" +"""Static palette token data for Dreamcoder themes. + +AUTO-GENERATED from themes/dreamcoder/tokens.json — do not edit by hand. +Run: ./scripts/generate-palette-tokens.py +""" from __future__ import annotations @@ -23,7 +27,7 @@ "accent": "#d99555", "accent_2": "#c96a45", "diagnostic": "#5f95ca", - "selection": "#3d3028", + "selection": "#2b231b", "panel_rgba": "rgba(16, 15, 13, 0.78)", "module_rgba": "rgba(232, 223, 208, 0.08)", "active_rgba": "rgba(217, 149, 85, 0.24)", @@ -42,6 +46,21 @@ "mauve": "#e29cb4", "error": "#ed8a7a", "warning": "#e8b866", + "success": "#4db35f", + "info": "#5f95ca", + "selection_bg": "#2b231b", + "selection_fg": "#e8dfd0", + "on_surface": "#e8dfd0", + "on_accent": "#100f0d", + "on_error": "#100f0d", + "on_focus": "#100f0d", + "link": "#d99555", + "link_hover": "#c96a45", + "disabled": "#877e73", + "hover": "#2b231b", + "pressed": "#392e21", + "overlay": "rgba(16, 15, 13, 0.52)", + "scrim": "rgba(0, 0, 0, 0.58)" }, "light": { "name": "Dreamcoder Light", @@ -66,7 +85,7 @@ "mauve": "#7d3e64", "error": "#842f24", "warning": "#654300", - "selection": "#17120d", + "selection": "#decbb1", "panel_rgba": "rgba(243, 234, 220, 0.96)", "module_rgba": "rgba(222, 203, 177, 0.80)", "active_rgba": "rgba(130, 79, 22, 0.34)", @@ -80,6 +99,23 @@ "prompt_muted": "#53402e", "prompt_accent": "#8a5520", "prompt_accent_2": "#a7471c", + "success": "#3d723d", + "info": "#0d4a68", + "text_heading": "#100c07", + "surface3": "#b89d7a", + "selection_bg": "#decbb1", + "selection_fg": "#17120d", + "on_surface": "#17120d", + "on_accent": "#fff7ea", + "on_error": "#fff7ea", + "on_focus": "#fff7ea", + "link": "#824f16", + "link_hover": "#a7471c", + "disabled": "#787063", + "hover": "#decbb1", + "pressed": "#c8ad89", + "overlay": "rgba(243, 234, 220, 0.40)", + "scrim": "rgba(26, 18, 12, 0.42)" }, "dusk": { "name": "Dreamcoder Dusk", @@ -104,7 +140,7 @@ "mauve": "#784762", "error": "#773126", "warning": "#604000", - "selection": "#1a1713", + "selection": "#d8cbb8", "panel_rgba": "rgba(235, 228, 214, 0.88)", "module_rgba": "rgba(26, 23, 19, 0.08)", "active_rgba": "rgba(138, 85, 32, 0.22)", @@ -118,10 +154,27 @@ "prompt_muted": "#574939", "prompt_accent": "#965f25", "prompt_accent_2": "#96411e", - }, + "success": "#466b41", + "info": "#104b67", + "text_heading": "#13100c", + "surface3": "#b6a691", + "selection_bg": "#d8cbb8", + "selection_fg": "#1a1713", + "on_surface": "#1a1713", + "on_accent": "#f1eadf", + "on_error": "#f1eadf", + "on_focus": "#f1eadf", + "link": "#8a5520", + "link_hover": "#96411e", + "disabled": "#847c71", + "hover": "#d8cbb8", + "pressed": "#c6b6a0", + "overlay": "rgba(235, 228, 214, 0.40)", + "scrim": "rgba(26, 18, 12, 0.42)" + } } -ANSI_KEYS = [ +ANSI_KEY_NAMES = [ "surface0", "error", "sage", @@ -131,11 +184,11 @@ "lavender", "muted", "subtle", - "#e9a092", - "#75a579", + "error_bright", + "sage_bright", "warning", - "#579ba4", - "#846fc5", - "#6fa5a5", - "text", + "diagnostic_bright", + "lavender_bright", + "focus_bright", + "text" ] diff --git a/src/dreamcoder_theme/renderers_extra_nvim.py b/src/dreamcoder_theme/renderers_extra_nvim.py index 1bb4156..2d895ff 100644 --- a/src/dreamcoder_theme/renderers_extra_nvim.py +++ b/src/dreamcoder_theme/renderers_extra_nvim.py @@ -102,9 +102,8 @@ def hl(name: str, **kwargs: Any) -> str: normal_bg = bg if invert else "none" text = c["text"] fg = text - # Use surface1 for selection background in all modes - better visibility - sel_bg = c["surface1"] - sel_fg = c["text"] + sel_bg = c["selection_bg"] + sel_fg = c["selection_fg"] lines = [ f"""-- ======================================================== diff --git a/src/dreamcoder_theme/renderers_extra_nvim_lsp.py b/src/dreamcoder_theme/renderers_extra_nvim_lsp.py index b16f401..d762d25 100644 --- a/src/dreamcoder_theme/renderers_extra_nvim_lsp.py +++ b/src/dreamcoder_theme/renderers_extra_nvim_lsp.py @@ -18,9 +18,9 @@ def nvim_lsp_groups( hl("@markup.heading", fg=c["accent"], bold=True), hl("@markup.quote", fg=c["comment"], italic=True), hl("@markup.math", fg=c["lavender"]), - hl("@markup.link", fg=c["diagnostic"], underline=True), - hl("@markup.link.label", fg=c["diagnostic"], underline=True), - hl("@markup.link.url", fg=c["subtle"], underline=True), + hl("@markup.link", fg=c["link"], underline=True), + hl("@markup.link.label", fg=c["link"], underline=True), + hl("@markup.link.url", fg=c["link_hover"], underline=True), hl("@markup.raw", fg=c["sage"]), hl("@markup.list", fg=c["accent"]), hl("@diff.plus", fg=c["sage"]), diff --git a/src/dreamcoder_theme/renderers_extra_obsidian.py b/src/dreamcoder_theme/renderers_extra_obsidian.py index fbc4e1b..a343961 100644 --- a/src/dreamcoder_theme/renderers_extra_obsidian.py +++ b/src/dreamcoder_theme/renderers_extra_obsidian.py @@ -40,16 +40,16 @@ def obsidian_content(c: dict[str, str]) -> str: --text-accent-hover: {c["accent_2"]}; --text-error: {c["error"]}; --text-warning: {c["warning"]}; - --text-success: {c["sage"]}; - --text-selection: {mix(c["surface1"], bg, 0.5)}; - --text-on-accent: {c["bg"]}; + --text-success: {c["success"]}; + --text-selection: {c["selection_bg"]}; + --text-on-accent: {c["on_accent"]}; /* Interactive */ --interactive-normal: {c["surface1"]}; - --interactive-hover: {c["surface2"]}; + --interactive-hover: {c["hover"]}; --interactive-accent: {c["accent"]}; --interactive-accent-hover: {c["accent_2"]}; - --interactive-success: {c["sage"]}; + --interactive-success: {c["success"]}; /* Scrollbar */ --scrollbar-bg: transparent; @@ -70,18 +70,18 @@ def obsidian_content(c: dict[str, str]) -> str: --code-background: {c["surface0"]}; /* Heading */ - --h1-color: {c["accent"]}; - --h2-color: {c["accent"]}; + --h1-color: {c["text_heading"]}; + --h2-color: {c["text_heading"]}; --h3-color: {c["accent_2"]}; --h4-color: {c["diagnostic"]}; --h5-color: {c["muted"]}; --h6-color: {c["subtle"]}; /* Link */ - --link-color: {c["accent"]}; - --link-color-hover: {c["accent_2"]}; - --link-external-color: {c["diagnostic"]}; - --link-external-color-hover: {c["accent_2"]}; + --link-color: {c["link"]}; + --link-color-hover: {c["link_hover"]}; + --link-external-color: {c["info"]}; + --link-external-color-hover: {c["link_hover"]}; /* Checkbox */ --checkbox-color: {c["accent"]}; diff --git a/src/dreamcoder_theme/renderers_ghostty_warp.py b/src/dreamcoder_theme/renderers_ghostty_warp.py index 4308d09..b71c58d 100644 --- a/src/dreamcoder_theme/renderers_ghostty_warp.py +++ b/src/dreamcoder_theme/renderers_ghostty_warp.py @@ -6,10 +6,9 @@ def ghostty_content(c: dict[str, str]) -> str: - # Use surface1 for selection background in all modes - better visibility is_dark = c["details"] == "darker" - sel_bg = c["surface1"] - sel_fg = c["text"] + sel_bg = c["selection_bg"] + sel_fg = c["selection_fg"] opacity = "0.76" if is_dark else "0.96" blur = "true" if is_dark else "false" lines = [ @@ -17,7 +16,7 @@ def ghostty_content(c: dict[str, str]) -> str: f"background = {c['bg']}", f"foreground = {c['text']}", f"cursor-color = {c['accent']}", - f"cursor-text = {c['bg']}", + f"cursor-text = {c['on_accent']}", f"selection-background = {sel_bg}", f"selection-foreground = {sel_fg}", f"background-opacity = {opacity}", diff --git a/src/dreamcoder_theme/renderers_hypr_waybar_rofi.py b/src/dreamcoder_theme/renderers_hypr_waybar_rofi.py index f5bffcf..26da750 100644 --- a/src/dreamcoder_theme/renderers_hypr_waybar_rofi.py +++ b/src/dreamcoder_theme/renderers_hypr_waybar_rofi.py @@ -32,12 +32,12 @@ def _map_dc_to_material(c: dict[str, str]) -> dict[str, str]: "surface_variant": h(c["surface1"]), "surface_tint": h(c["accent"]), # On-colors - "on_background": h(c["text"]), - "on_surface": h(c["text"]), + "on_background": h(c["on_surface"]), + "on_surface": h(c["on_surface"]), "on_surface_variant": h(c["muted"]), # Primary "primary": h(c["accent"]), - "on_primary": h(c["bg"]), + "on_primary": h(c["on_accent"]), "primary_container": h(c["surface1"]), "on_primary_container": h(c["text"]), "primary_fixed": h(c["accent"]), @@ -47,7 +47,7 @@ def _map_dc_to_material(c: dict[str, str]) -> dict[str, str]: "inverse_primary": h(c["accent"]), # Secondary "secondary": h(c["accent_2"]), - "on_secondary": h(c["bg"]), + "on_secondary": h(c["on_accent"]), "secondary_container": h(c["surface1"]), "on_secondary_container": h(c["text"]), "secondary_fixed": h(c["accent_2"]), @@ -56,7 +56,7 @@ def _map_dc_to_material(c: dict[str, str]) -> dict[str, str]: "on_secondary_fixed_variant": h(c["accent_2"]), # Tertiary "tertiary": h(c["diagnostic"]), - "on_tertiary": h(c["bg"]), + "on_tertiary": h(c["on_accent"]), "tertiary_container": h(c["surface0"]), "on_tertiary_container": h(c["text"]), "tertiary_fixed": h(c["diagnostic"]), @@ -65,7 +65,7 @@ def _map_dc_to_material(c: dict[str, str]) -> dict[str, str]: "on_tertiary_fixed_variant": h(c["focus"]), # Error "error": h(c["error"]), - "on_error": h(c["bg"]), + "on_error": h(c["on_error"]), "error_container": h(c["surface0"]), "on_error_container": h(c["text"]), # Outline @@ -115,17 +115,25 @@ def waybar_content(c: dict[str, str]) -> str: @define-color bg {c["bg"]}; @define-color bg-soft {c["bg_soft"]}; @define-color surface {c["surface0"]}; +@define-color surface-2 {c["surface2"]}; +@define-color surface-3 {c["surface3"]}; @define-color text {c["text"]}; +@define-color text-heading {c["text_heading"]}; @define-color muted {c["muted"]}; +@define-color subtle {c["subtle"]}; +@define-color comment {c["comment"]}; @define-color border {c["border"]}; @define-color border-ui {c["border_ui"]}; @define-color focus {c["focus"]}; @define-color accent {c["accent"]}; @define-color accent-2 {c["accent_2"]}; @define-color diagnostic {c["diagnostic"]}; +@define-color lavender {c["lavender"]}; @define-color error {c["error"]}; -@define-color success {c["sage"]}; +@define-color success {c["success"]}; @define-color warning {c["warning"]}; +@define-color link {c["link"]}; +@define-color link-hover {c["link_hover"]}; window#waybar {{ background: {c["panel_rgba"]}; @@ -285,15 +293,15 @@ def _hex_to_rgb_int(hex_color: str) -> tuple[int, int, int]: primary: {c["accent"]}; primary-fixed: {c["accent"]}; primary-fixed-dim: {c["accent_2"]}; - on-primary: {c["bg"]}; - on-primary-fixed: {c["bg"]}; + on-primary: {c["on_accent"]}; + on-primary-fixed: {c["on_accent"]}; on-primary-fixed-variant: {c["accent"]}; primary-container: {c["surface1"]}; on-primary-container: {c["text"]}; secondary: {c["accent_2"]}; secondary-fixed: {c["accent_2"]}; secondary-fixed-dim: {c["accent_2"]}; - on-secondary: {c["bg"]}; + on-secondary: {c["on_accent"]}; on-secondary-fixed: {c["bg"]}; on-secondary-fixed-variant: {c["accent_2"]}; secondary-container: {c["surface1"]}; @@ -307,7 +315,7 @@ def _hex_to_rgb_int(hex_color: str) -> tuple[int, int, int]: tertiary-container: {c["surface0"]}; on-tertiary-container: {c["text"]}; error: {c["error"]}; - on-error: {c["bg"]}; + on-error: {c["on_error"]}; error-container: {c["surface0"]}; on-error-container: {c["text"]}; surface: {c["bg"]}; diff --git a/src/dreamcoder_theme/renderers_kitty.py b/src/dreamcoder_theme/renderers_kitty.py index 8534aa9..e6e6e72 100644 --- a/src/dreamcoder_theme/renderers_kitty.py +++ b/src/dreamcoder_theme/renderers_kitty.py @@ -7,9 +7,8 @@ def kitty_content(c: dict[str, str]) -> str: p = ansi(c) - # Use surface1 for selection background in all modes - better visibility - sel_fg = c["text"] - sel_bg = c["surface1"] + sel_fg = c["selection_fg"] + sel_bg = c["selection_bg"] return f"""# ========================================================== # {c["name"]} # ========================================================== @@ -19,15 +18,15 @@ def kitty_content(c: dict[str, str]) -> str: background {c["bg"]} selection_foreground {sel_fg} selection_background {sel_bg} -url_color {c["diagnostic"]} +url_color {c["link"]} cursor {c["accent"]} -cursor_text_color {c["bg"]} +cursor_text_color {c["on_accent"]} cursor_shape block cursor_blink_interval 0.5 cursor_stop_blinking_after 15.0 -active_tab_foreground {c["bg"]} +active_tab_foreground {c["on_accent"]} active_tab_background {c["accent"]} inactive_tab_foreground {c["muted"]} inactive_tab_background {c["bg"]} @@ -56,11 +55,11 @@ def kitty_content(c: dict[str, str]) -> str: color16 {c["accent_2"]} color17 {c["error"]} -mark1_foreground {c["bg"]} +mark1_foreground {c["on_accent"]} mark1_background {c["accent"]} -mark2_foreground {c["bg"]} +mark2_foreground {c["on_surface"]} mark2_background {c["diagnostic"]} -mark3_foreground {c["bg"]} +mark3_foreground {c["on_surface"]} mark3_background {c["mauve"]} """ diff --git a/src/dreamcoder_theme/renderers_opencode.py b/src/dreamcoder_theme/renderers_opencode.py index 91153d7..c47ec09 100644 --- a/src/dreamcoder_theme/renderers_opencode.py +++ b/src/dreamcoder_theme/renderers_opencode.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .palette import guard, mix +from .palette import guard, mix, surface_guard def opencode_tokens(c: dict[str, str]) -> dict[str, str]: @@ -14,16 +14,8 @@ def opencode_tokens(c: dict[str, str]) -> dict[str, str]: function = guard(c["diagnostic"], c["bg"], mode_name, minimum=syntax_min) type_color = guard(c["lavender"], c["bg"], mode_name, minimum=syntax_min) constant = guard(mix(c["accent_2"], c["mauve"], 0.24), c["bg"], mode_name, minimum=syntax_min) - # Selection: light bg with dark text for dark mode (accent is light); - # For light mode, accent is dark so syntax colors (also dark) become - # invisible on it. Use bg (lightest non-white) instead so ALL syntax - # colors remain legible even if OpenCode doesn't swap them on selection. - if mode_name == "dark": - sel_bg = c["accent"] - sel_fg = c["bg"] - else: - sel_bg = c["bg"] - sel_fg = c["text"] + sel_bg = c["selection_bg"] + sel_fg = c["selection_fg"] return { "keyword": keyword, "function": function, @@ -72,7 +64,7 @@ def opencode_content(c: dict[str, str], transparent_background: bool = False) -> # Mode-aware surface formulas if mode_name == "dark": element_bg = c["surface1"] - hover_bg = c["surface2"] + hover_bg = c["hover"] line_bg = mix(c["bg"], c["surface0"], 0.50) code_bg = mix(c["surface0"], c["surface1"], 0.30) assistant_bg = mix(c["bg"], c["diagnostic"], 0.12) @@ -83,7 +75,7 @@ def opencode_content(c: dict[str, str], transparent_background: bool = False) -> mix_base = c["border_ui"] else: element_bg = mix(c["bg_soft"], c["surface1"], 0.4) - hover_bg = c["surface2"] + hover_bg = c["hover"] line_bg = c["bg_soft"] code_bg = mix(c["surface1"], c["border_ui"], 0.12) assistant_bg = mix(c["bg"], c["diagnostic"], 0.12) @@ -93,13 +85,18 @@ def opencode_content(c: dict[str, str], transparent_background: bool = False) -> inline_code_bg = mix(c["sage"], c["bg"], 0.18) mix_base = c["bg"] - added_bg = mix(c["sage"], mix_base, 0.35) - removed_bg = mix(c["error"], mix_base, 0.35) - hunk_bg = mix(c["lavender"], mix_base, 0.45) + diff_mix = 0.35 if mode_name == "dark" else 0.58 + added_bg = surface_guard(mix(c["sage"], mix_base, diff_mix), c["bg"], mode_name) + removed_bg = surface_guard(mix(c["error"], mix_base, diff_mix), c["bg"], mode_name) + hunk_bg = surface_guard( + mix(c["lavender"], mix_base, 0.45 if mode_name == "dark" else 0.62), + c["bg"], + mode_name, + ) assistant = guard(mix(c["diagnostic"], c["text"], 0.18), c["bg"], mode_name) user = guard(mix(c["accent"], c["text"], 0.15), c["bg"], mode_name) background = "none" if transparent_background else c["bg"] - return f'''{{ + return f"""{{ "$schema": "https://opencode.ai/theme.json", "defs": {{ "dreamBackground": "{c["bg"]}", @@ -229,4 +226,4 @@ def opencode_content(c: dict[str, str], transparent_background: bool = False) -> "terminalBrightWhite": "{c["text"]}" }} }} -''' +""" diff --git a/src/dreamcoder_theme/renderers_starship.py b/src/dreamcoder_theme/renderers_starship.py index 7d9ff87..cd0672a 100644 --- a/src/dreamcoder_theme/renderers_starship.py +++ b/src/dreamcoder_theme/renderers_starship.py @@ -16,27 +16,33 @@ def starship_content(c: dict[str, str]) -> str: prom_text = guard(c["prompt_text"], prom_s0, mode) # text on darkest surface prom_muted = guard(c["prompt_muted"], c["bg"], mode) error = guard(c["error"], c["bg"], mode) + warning = guard(c["warning"], c["bg"], mode) + focus_col = c["focus"] + diag = c["diagnostic"] + lavender_col = c["lavender"] + mauve_col = c["mauve"] return f'''# ======================================================== # {c["name"]} — Starship prompt # ======================================================== # Modern two-line layout with powerline segments. -# Line 1: context (directory, git) +# Line 1: context (directory, git), fill, cmd_duration, time # Line 2: input character only (clean) +# Extra: status (exit code), AI session (hidden until active) add_newline = true palette = "dreamcoder" command_timeout = 500 format = """ -[](fg:prompt_surface0)\\ +[\\uE0B6](fg:prompt_surface0)\\ $username\\ -[](bg:prompt_surface1 fg:prompt_surface0)\\ +[\\uE0B0](bg:prompt_surface1 fg:prompt_surface0)\\ $directory\\ -[](bg:prompt_accent fg:prompt_surface1)\\ +[\\uE0B0](bg:prompt_accent fg:prompt_surface1)\\ $git_branch\\ $git_status\\ -[](fg:prompt_accent)\\ +[\\uE0B4](fg:prompt_accent)\\ $fill\\ $cmd_duration\\ $time @@ -57,42 +63,53 @@ def starship_content(c: dict[str, str]) -> str: prompt_accent = "{prom_acc}" prompt_accent_2 = "{c["prompt_accent_2"]}" sage = "{c["sage"]}" -diagnostic = "{c["diagnostic"]}" -lavender = "{c["lavender"]}" -mauve = "{c["mauve"]}" +diagnostic = "{diag}" +lavender = "{lavender_col}" +mauve = "{mauve_col}" error = "{error}" +warning = "{warning}" +border = "{c["border_ui"]}" +focus = "{focus_col}" +link = "{c["link"]}" [username] show_always = true style_user = "bg:prompt_surface0 fg:prompt_text bold" style_root = "bg:prompt_surface0 fg:error bold" -format = "[  $user ]($style)" +format = "[ \uf007 $user ]($style)" [directory] style = "bg:prompt_surface1 fg:prompt_text bold" -format = "[  $path ]($style)" +format = "[ $path ]($style)" truncation_length = 2 truncate_to_repo = true home_symbol = "" [git_branch] -symbol = "" +symbol = "\\uf418" style = "bg:prompt_accent fg:prompt_bg bold" format = "[ $symbol $branch ]($style)" [git_status] style = "bg:prompt_accent fg:prompt_bg bold" format = "[$all_status$ahead_behind ]($style)" -conflicted = "${{count}} " -ahead = "⇡${{count}} " -behind = "⇣${{count}} " -diverged = "⇕⇡${{ahead_count}}⇣${{behind_count}} " +conflicted = "\\ue727${{count}} " +ahead = "\\u21E1${{count}} " +behind = "\\u21E3${{count}} " +diverged = "\\u2195\\u21E1${{ahead_count}}\\u21E3${{behind_count}} " untracked = "?${{count}} " -stashed = "󰏗${{count}} " +stashed = "\\uf0CF${{count}} " modified = "~${{count}} " staged = "+${{count}} " -renamed = "»${{count}} " -deleted = "✘${{count}} " +renamed = "\\u00BB${{count}} " +deleted = "\\u2718${{count}} " + +[status] +disabled = false +format = "[$symbol]($style)" +symbol = "\\u2717" +style = "bg:error fg:prompt_bg bold" +pipestatus = false [fill] symbol = " " @@ -100,7 +117,7 @@ def starship_content(c: dict[str, str]) -> str: [cmd_duration] min_time = 2500 style = "fg:prompt_muted" -format = "[  $duration ]($style)" +format = "[ \\uf552 $duration ]($style)" [time] disabled = false @@ -108,39 +125,45 @@ def starship_content(c: dict[str, str]) -> str: style = "fg:prompt_muted" [character] -success_symbol = "[❯](bold fg:prompt_accent)" -error_symbol = "[❯](bold fg:error)" -vimcmd_symbol = "[❮](bold fg:sage)" +success_symbol = "[\\u276F](bold fg:prompt_accent)" +error_symbol = "[\\u276F](bold fg:error)" +vimcmd_symbol = "[\\u276E](bold fg:sage)" # Runtime versions - show only when relevant, keep compact [bun] -symbol = "" +symbol = "\\uF5EF" style = "fg:prompt_accent bold" format = "[ $symbol $version]($style)" [nodejs] -symbol = "" +symbol = "\\uE718" style = "fg:sage bold" format = "[ $symbol $version]($style)" [python] -symbol = "" -style = "fg:diagnostic bold" +symbol = "\\uE73C" +style = "fg:diag bold" format = "[ $symbol $version]($style)" [golang] -symbol = "" -style = "fg:lavender bold" +symbol = "\\uE627" +style = "fg:diag bold" format = "[ $symbol $version]($style)" [rust] -symbol = "" +symbol = "\\uE7A8" style = "fg:mauve bold" format = "[ $symbol $version]($style)" +[custom.ai_session] +command = "cat ~/.cache/dreamcoder/ai-session.state 2>/dev/null || echo ''" +when = """test -f ~/.cache/dreamcoder/ai-session.state""" +format = "[\\uf5B5 $output]($style)" +style = "fg:diag bold" + [docker_context] -symbol = "" -style = "fg:diagnostic bold" +symbol = "\\uf308" +style = "fg:diag bold" format = "[ $symbol $context]($style)" only_with_files = true ''' diff --git a/src/dreamcoder_theme/sync.py b/src/dreamcoder_theme/sync.py index ba0022b..3303264 100644 --- a/src/dreamcoder_theme/sync.py +++ b/src/dreamcoder_theme/sync.py @@ -2,6 +2,8 @@ from __future__ import annotations +import subprocess +import sys from typing import Any from .palette import adaptive_palette, load_variants @@ -376,6 +378,9 @@ def print_summary( def main() -> None: + gen = ROOT / "scripts" / "generate-palette-tokens.py" + if gen.is_file(): + subprocess.run([sys.executable, str(gen)], check=True) paths = theme_paths() mode = theme_mode() variants = load_variants(DEFAULT_VARIANTS, paths.tokens_file) diff --git a/tests/test_dreamcoder_theme_quality.py b/tests/test_dreamcoder_theme_quality.py index 7b65508..2fd85bd 100644 --- a/tests/test_dreamcoder_theme_quality.py +++ b/tests/test_dreamcoder_theme_quality.py @@ -41,13 +41,22 @@ def test_light_has_stronger_editor_readability_tiers(self): light = self.modes["light"] self.assertEqual(light["surface0"], "#fff7ea") self.assertEqual(light["surface2"], "#c8ad89") - # Verify subtle has sufficient contrast against background self.assertGreaterEqual(contrast(light["subtle"], light["bg"]), 4.5) self.assertGreaterEqual(contrast(light["comment"], light["bg"]), 4.5) - def test_light_selection_uses_inverted_high_contrast_pair(self): + def test_light_selection_uses_explicit_pair(self): light = self.modes["light"] - self.assertEqual(light["selection"], light["text"]) + self.assertEqual(light["selection_bg"], "#decbb1") + self.assertEqual(light["selection_fg"], light["text"]) + self.assertGreaterEqual(contrast(light["selection_fg"], light["selection_bg"]), 7.0) + + def test_dark_has_text_heading_and_surface3(self): + dark = self.modes["dark"] + self.assertIn("text_heading", dark) + self.assertIn("surface3", dark) + self.assertGreater( + contrast(dark["text_heading"], dark["bg"]), contrast(dark["text"], dark["bg"]) + ) if __name__ == "__main__": diff --git a/tests/test_terminal_readability.py b/tests/test_terminal_readability.py index dd642ac..7dc7ab6 100644 --- a/tests/test_terminal_readability.py +++ b/tests/test_terminal_readability.py @@ -33,12 +33,12 @@ def test_cursor_and_selection_pairs_are_terminal_readable(self): cursor_min = self.guardrails["minimum_terminal_cursor_contrast"] selection_min = self.guardrails["minimum_terminal_selection_contrast"] for mode, palette in self.modes.items(): - invert = palette.get("details") == "lighter" - sel_fg = palette["bg"] if invert else palette["text"] - sel_bg = palette["text"] if invert else palette["selection"] with self.subTest(mode=mode): self.assertGreaterEqual(contrast(palette["accent"], palette["bg"]), cursor_min) - self.assertGreaterEqual(contrast(sel_fg, sel_bg), selection_min) + self.assertGreaterEqual( + contrast(palette["selection_fg"], palette["selection_bg"]), + selection_min, + ) if __name__ == "__main__": diff --git a/tests/test_token_parity.py b/tests/test_token_parity.py new file mode 100644 index 0000000..b92dbcb --- /dev/null +++ b/tests/test_token_parity.py @@ -0,0 +1,52 @@ +import json +import sys +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) + +from dreamcoder_theme.palette import contrast +from dreamcoder_theme.palette_tokens import ANSI_KEY_NAMES + +TOKENS = ROOT / "themes" / "dreamcoder" / "tokens.json" + + +class TokenParityTest(unittest.TestCase): + def setUp(self): + self.modes = json.loads(TOKENS.read_text())["modes"] + + def test_dark_and_light_share_semantic_keys(self): + dark_keys = {k for k in self.modes["dark"] if k not in {"name", "details"}} + light_keys = {k for k in self.modes["light"] if k not in {"name", "details"}} + self.assertEqual(dark_keys, light_keys) + + def test_surface_ladder_increases_contrast_from_bg(self): + for mode in ("dark", "light"): + palette = self.modes[mode] + bg = palette["bg"] + steps = [ + palette[k] for k in ("bg_soft", "surface0", "surface1", "surface2", "surface3") + ] + ratios = [contrast(step, bg) for step in steps] + with self.subTest(mode=mode): + self.assertTrue(all(r >= 1.02 for r in ratios)) + + def test_text_hierarchy_is_ordered(self): + for mode in ("dark", "light"): + palette = self.modes[mode] + bg = palette["bg"] + text_ratio = contrast(palette["text"], bg) + muted_ratio = contrast(palette["muted"], bg) + subtle_ratio = contrast(palette["subtle"], bg) + with self.subTest(mode=mode): + self.assertGreater(text_ratio, muted_ratio) + self.assertGreater(muted_ratio, subtle_ratio) + + def test_ansi_keys_are_token_names_only(self): + for key in ANSI_KEY_NAMES: + self.assertFalse(key.startswith("#"), f"literal hex in ANSI_KEY_NAMES: {key}") + + +if __name__ == "__main__": + unittest.main() diff --git a/themes/dreamcoder/hypr-colors-light.conf b/themes/dreamcoder/hypr-colors-light.conf index d133315..6012b99 100644 --- a/themes/dreamcoder/hypr-colors-light.conf +++ b/themes/dreamcoder/hypr-colors-light.conf @@ -5,19 +5,19 @@ $inverse_on_surface = rgba(fff7eaff) $inverse_primary = rgba(824f16ff) $inverse_surface = rgba(17120dff) $on_background = rgba(17120dff) -$on_error = rgba(f3eadcff) +$on_error = rgba(fff7eaff) $on_error_container = rgba(17120dff) -$on_primary = rgba(f3eadcff) +$on_primary = rgba(fff7eaff) $on_primary_container = rgba(17120dff) $on_primary_fixed = rgba(f3eadcff) $on_primary_fixed_variant = rgba(824f16ff) -$on_secondary = rgba(f3eadcff) +$on_secondary = rgba(fff7eaff) $on_secondary_container = rgba(17120dff) $on_secondary_fixed = rgba(f3eadcff) $on_secondary_fixed_variant = rgba(a7471cff) $on_surface = rgba(17120dff) $on_surface_variant = rgba(352e22ff) -$on_tertiary = rgba(f3eadcff) +$on_tertiary = rgba(fff7eaff) $on_tertiary_container = rgba(17120dff) $on_tertiary_fixed = rgba(f3eadcff) $on_tertiary_fixed_variant = rgba(0f6570ff) diff --git a/themes/dreamcoder/hypr-colors-light.lua b/themes/dreamcoder/hypr-colors-light.lua index 915d21c..b96d2fd 100644 --- a/themes/dreamcoder/hypr-colors-light.lua +++ b/themes/dreamcoder/hypr-colors-light.lua @@ -5,19 +5,19 @@ inverse_on_surface = "rgba(fff7eaff)" inverse_primary = "rgba(824f16ff)" inverse_surface = "rgba(17120dff)" on_background = "rgba(17120dff)" -on_error = "rgba(f3eadcff)" +on_error = "rgba(fff7eaff)" on_error_container = "rgba(17120dff)" -on_primary = "rgba(f3eadcff)" +on_primary = "rgba(fff7eaff)" on_primary_container = "rgba(17120dff)" on_primary_fixed = "rgba(f3eadcff)" on_primary_fixed_variant = "rgba(824f16ff)" -on_secondary = "rgba(f3eadcff)" +on_secondary = "rgba(fff7eaff)" on_secondary_container = "rgba(17120dff)" on_secondary_fixed = "rgba(f3eadcff)" on_secondary_fixed_variant = "rgba(a7471cff)" on_surface = "rgba(17120dff)" on_surface_variant = "rgba(352e22ff)" -on_tertiary = "rgba(f3eadcff)" +on_tertiary = "rgba(fff7eaff)" on_tertiary_container = "rgba(17120dff)" on_tertiary_fixed = "rgba(f3eadcff)" on_tertiary_fixed_variant = "rgba(0f6570ff)" diff --git a/themes/dreamcoder/obsidian-dreamcoder-dark.css b/themes/dreamcoder/obsidian-dreamcoder-dark.css index ee8db57..c81c608 100644 --- a/themes/dreamcoder/obsidian-dreamcoder-dark.css +++ b/themes/dreamcoder/obsidian-dreamcoder-dark.css @@ -26,12 +26,12 @@ --text-error: #ed8a7a; --text-warning: #e8b866; --text-success: #4db35f; - --text-selection: #1e1914; + --text-selection: #2b231b; --text-on-accent: #100f0d; /* Interactive */ --interactive-normal: #2b231b; - --interactive-hover: #392e21; + --interactive-hover: #2b231b; --interactive-accent: #d99555; --interactive-accent-hover: #c96a45; --interactive-success: #4db35f; @@ -55,8 +55,8 @@ --code-background: #201b16; /* Heading */ - --h1-color: #d99555; - --h2-color: #d99555; + --h1-color: #f4ecdd; + --h2-color: #f4ecdd; --h3-color: #c96a45; --h4-color: #5f95ca; --h5-color: #c7b9aa; diff --git a/themes/dreamcoder/obsidian-dreamcoder-light.css b/themes/dreamcoder/obsidian-dreamcoder-light.css index 280deb7..60fb864 100644 --- a/themes/dreamcoder/obsidian-dreamcoder-light.css +++ b/themes/dreamcoder/obsidian-dreamcoder-light.css @@ -26,12 +26,12 @@ --text-error: #842f24; --text-warning: #654300; --text-success: #3d723d; - --text-selection: #e8dac6; - --text-on-accent: #f3eadc; + --text-selection: #decbb1; + --text-on-accent: #fff7ea; /* Interactive */ --interactive-normal: #decbb1; - --interactive-hover: #c8ad89; + --interactive-hover: #decbb1; --interactive-accent: #824f16; --interactive-accent-hover: #a7471c; --interactive-success: #3d723d; @@ -55,8 +55,8 @@ --code-background: #fff7ea; /* Heading */ - --h1-color: #824f16; - --h2-color: #824f16; + --h1-color: #100c07; + --h2-color: #100c07; --h3-color: #a7471c; --h4-color: #0d4a68; --h5-color: #352e22; diff --git a/themes/dreamcoder/obsidian-dreamcoder.css b/themes/dreamcoder/obsidian-dreamcoder.css index 280deb7..60fb864 100644 --- a/themes/dreamcoder/obsidian-dreamcoder.css +++ b/themes/dreamcoder/obsidian-dreamcoder.css @@ -26,12 +26,12 @@ --text-error: #842f24; --text-warning: #654300; --text-success: #3d723d; - --text-selection: #e8dac6; - --text-on-accent: #f3eadc; + --text-selection: #decbb1; + --text-on-accent: #fff7ea; /* Interactive */ --interactive-normal: #decbb1; - --interactive-hover: #c8ad89; + --interactive-hover: #decbb1; --interactive-accent: #824f16; --interactive-accent-hover: #a7471c; --interactive-success: #3d723d; @@ -55,8 +55,8 @@ --code-background: #fff7ea; /* Heading */ - --h1-color: #824f16; - --h2-color: #824f16; + --h1-color: #100c07; + --h2-color: #100c07; --h3-color: #a7471c; --h4-color: #0d4a68; --h5-color: #352e22; diff --git a/themes/dreamcoder/tokens.json b/themes/dreamcoder/tokens.json index db02e55..3a84048 100644 --- a/themes/dreamcoder/tokens.json +++ b/themes/dreamcoder/tokens.json @@ -24,7 +24,7 @@ "accent": "#d99555", "accent_2": "#c96a45", "diagnostic": "#5f95ca", - "selection": "#3d3028", + "selection": "#2b231b", "panel_rgba": "rgba(16, 15, 13, 0.78)", "module_rgba": "rgba(232, 223, 208, 0.08)", "active_rgba": "rgba(217, 149, 85, 0.24)", @@ -42,7 +42,22 @@ "lavender": "#d4b4e6", "mauve": "#e29cb4", "error": "#ed8a7a", - "warning": "#e8b866" + "warning": "#e8b866", + "success": "#4db35f", + "info": "#5f95ca", + "selection_bg": "#2b231b", + "selection_fg": "#e8dfd0", + "on_surface": "#e8dfd0", + "on_accent": "#100f0d", + "on_error": "#100f0d", + "on_focus": "#100f0d", + "link": "#d99555", + "link_hover": "#c96a45", + "disabled": "#877e73", + "hover": "#2b231b", + "pressed": "#392e21", + "overlay": "rgba(16, 15, 13, 0.52)", + "scrim": "rgba(0, 0, 0, 0.58)" }, "light": { "name": "Dreamcoder Light", @@ -67,7 +82,7 @@ "mauve": "#7d3e64", "error": "#842f24", "warning": "#654300", - "selection": "#17120d", + "selection": "#decbb1", "panel_rgba": "rgba(243, 234, 220, 0.96)", "module_rgba": "rgba(222, 203, 177, 0.80)", "active_rgba": "rgba(130, 79, 22, 0.34)", @@ -80,7 +95,24 @@ "prompt_text": "#20150c", "prompt_muted": "#53402e", "prompt_accent": "#8a5520", - "prompt_accent_2": "#a7471c" + "prompt_accent_2": "#a7471c", + "success": "#3d723d", + "info": "#0d4a68", + "text_heading": "#100c07", + "surface3": "#b89d7a", + "selection_bg": "#decbb1", + "selection_fg": "#17120d", + "on_surface": "#17120d", + "on_accent": "#fff7ea", + "on_error": "#fff7ea", + "on_focus": "#fff7ea", + "link": "#824f16", + "link_hover": "#a7471c", + "disabled": "#787063", + "hover": "#decbb1", + "pressed": "#c8ad89", + "overlay": "rgba(243, 234, 220, 0.40)", + "scrim": "rgba(26, 18, 12, 0.42)" }, "dusk": { "name": "Dreamcoder Dusk", @@ -105,7 +137,7 @@ "mauve": "#784762", "error": "#773126", "warning": "#604000", - "selection": "#1a1713", + "selection": "#d8cbb8", "panel_rgba": "rgba(235, 228, 214, 0.88)", "module_rgba": "rgba(26, 23, 19, 0.08)", "active_rgba": "rgba(138, 85, 32, 0.22)", @@ -118,7 +150,24 @@ "prompt_text": "#261c14", "prompt_muted": "#574939", "prompt_accent": "#965f25", - "prompt_accent_2": "#96411e" + "prompt_accent_2": "#96411e", + "success": "#466b41", + "info": "#104b67", + "text_heading": "#13100c", + "surface3": "#b6a691", + "selection_bg": "#d8cbb8", + "selection_fg": "#1a1713", + "on_surface": "#1a1713", + "on_accent": "#f1eadf", + "on_error": "#f1eadf", + "on_focus": "#f1eadf", + "link": "#8a5520", + "link_hover": "#96411e", + "disabled": "#847c71", + "hover": "#d8cbb8", + "pressed": "#c6b6a0", + "overlay": "rgba(235, 228, 214, 0.40)", + "scrim": "rgba(26, 18, 12, 0.42)" } }, "guardrails": { @@ -135,6 +184,9 @@ "canonical_opencode_theme": "dreamcoder", "minimum_terminal_ansi_contrast": 4.5, "minimum_terminal_cursor_contrast": 4.5, - "minimum_terminal_selection_contrast": 7.0 + "minimum_terminal_selection_contrast": 7.0, + "minimum_apca_on_accent": 54, + "minimum_apca_heading_light": 60, + "minimum_apca_heading_dark": 45 } } diff --git a/themes/dreamcoder/tokens.schema.json b/themes/dreamcoder/tokens.schema.json index 879cf76..4ac7c01 100644 --- a/themes/dreamcoder/tokens.schema.json +++ b/themes/dreamcoder/tokens.schema.json @@ -2,32 +2,15 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Dreamcoder OS design tokens", "type": "object", - "required": [ - "name", - "version", - "modes", - "guardrails" - ], + "required": ["name", "version", "modes", "guardrails"], "properties": { - "name": { - "const": "Dreamcoder OS" - }, - "version": { - "type": "integer", - "minimum": 1 - }, - "description": { - "type": "string" - }, + "name": { "const": "Dreamcoder OS" }, + "version": { "type": "integer", "minimum": 1 }, + "description": { "type": "string" }, "modes": { "type": "object", - "required": [ - "dark", - "light" - ], - "additionalProperties": { - "$ref": "#/$defs/palette" - } + "required": ["dark", "light"], + "additionalProperties": { "$ref": "#/$defs/palette" } }, "guardrails": { "type": "object", @@ -37,264 +20,157 @@ "preferred_main_text_contrast", "minimum_terminal_ansi_contrast", "minimum_terminal_cursor_contrast", - "minimum_terminal_selection_contrast" + "minimum_terminal_selection_contrast", + "minimum_apca_body", + "minimum_apca_body_dark", + "minimum_apca_quiet", + "minimum_apca_ui", + "minimum_apca_ui_dark", + "minimum_apca_on_accent" ], "properties": { - "canonical_opencode_theme": { - "const": "dreamcoder" - }, - "avoid_pure_black_white": { - "type": "boolean" - }, - "minimum_text_contrast": { - "type": "number", - "minimum": 4.5 - }, - "preferred_main_text_contrast": { - "type": "number", - "minimum": 7 - }, - "light_hours_default": { - "type": "string" - }, - "dusk_hours_default": { - "type": "string" - }, - "minimum_apca_body": { - "type": "number", - "minimum": 60 - }, - "minimum_apca_quiet": { - "type": "number", - "minimum": 45 - }, - "minimum_apca_ui": { - "type": "number", - "minimum": 45 - }, - "minimum_terminal_ansi_contrast": { - "type": "number", - "minimum": 4.5 - }, - "minimum_terminal_cursor_contrast": { - "type": "number", - "minimum": 4.5 - }, - "minimum_terminal_selection_contrast": { - "type": "number", - "minimum": 7 - } + "canonical_opencode_theme": { "const": "dreamcoder" }, + "avoid_pure_black_white": { "type": "boolean" }, + "minimum_text_contrast": { "type": "number", "minimum": 4.5 }, + "preferred_main_text_contrast": { "type": "number", "minimum": 7 }, + "minimum_apca_body": { "type": "number", "minimum": 60 }, + "minimum_apca_body_dark": { "type": "number", "minimum": 45 }, + "minimum_apca_heading_light": { "type": "number", "minimum": 45 }, + "minimum_apca_heading_dark": { "type": "number", "minimum": 40 }, + "minimum_apca_quiet": { "type": "number", "minimum": 44 }, + "minimum_apca_ui": { "type": "number", "minimum": 28 }, + "minimum_apca_ui_dark": { "type": "number", "minimum": 28 }, + "minimum_apca_on_accent": { "type": "number", "minimum": 60 }, + "light_hours_default": { "type": "string" }, + "dusk_hours_default": { "type": "string" }, + "minimum_terminal_ansi_contrast": { "type": "number", "minimum": 4.5 }, + "minimum_terminal_cursor_contrast": { "type": "number", "minimum": 4.5 }, + "minimum_terminal_selection_contrast": { "type": "number", "minimum": 7 } } } }, "$defs": { - "hex": { - "type": "string", - "pattern": "^#[0-9a-fA-F]{6}$" - }, + "hex": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" }, "rgba_color": { "type": "string", "pattern": "^rgba\\(\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*,\\s*(0|1|0?\\.\\d+)\\s*\\)$" }, + "color_value": { + "oneOf": [{ "$ref": "#/$defs/hex" }, { "$ref": "#/$defs/rgba_color" }] + }, "palette": { "type": "object", "required": [ + "name", + "details", "bg", + "bg_soft", + "surface0", + "surface1", + "surface2", + "surface3", "text", + "text_heading", "muted", + "subtle", "comment", + "border", + "border_ui", + "border_hi", + "focus", "accent", "accent_2", "diagnostic", "sage", + "success", + "info", "error", "warning", - "border_ui", - "focus" + "lavender", + "mauve", + "on_surface", + "on_accent", + "on_error", + "on_focus", + "link", + "link_hover", + "selection", + "selection_bg", + "selection_fg", + "disabled", + "hover", + "pressed", + "overlay", + "scrim", + "panel_rgba", + "module_rgba", + "active_rgba", + "inactive_border", + "prompt_bg", + "prompt_surface0", + "prompt_surface1", + "prompt_surface2", + "prompt_text", + "prompt_muted", + "prompt_accent", + "prompt_accent_2" ], "properties": { - "name": { - "type": "string" - }, - "details": { - "type": "string" - }, - "bg": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "text": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "muted": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "subtle": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "comment": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "accent": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "accent_2": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "diagnostic": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "sage": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "error": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "warning": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "border": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "border_ui": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "border_hi": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "focus": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "selection": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "panel_rgba": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "module_rgba": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "active_rgba": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "inactive_border": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "prompt_bg": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "prompt_surface0": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "prompt_surface1": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "prompt_surface2": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "prompt_text": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "prompt_muted": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "prompt_accent": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - }, - "prompt_accent_2": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" } - ] - } + "name": { "type": "string" }, + "details": { "enum": ["darker", "lighter"] }, + "bg": { "$ref": "#/$defs/color_value" }, + "bg_soft": { "$ref": "#/$defs/color_value" }, + "surface0": { "$ref": "#/$defs/color_value" }, + "surface1": { "$ref": "#/$defs/color_value" }, + "surface2": { "$ref": "#/$defs/color_value" }, + "surface3": { "$ref": "#/$defs/color_value" }, + "text": { "$ref": "#/$defs/color_value" }, + "text_heading": { "$ref": "#/$defs/color_value" }, + "muted": { "$ref": "#/$defs/color_value" }, + "subtle": { "$ref": "#/$defs/color_value" }, + "comment": { "$ref": "#/$defs/color_value" }, + "border": { "$ref": "#/$defs/color_value" }, + "border_ui": { "$ref": "#/$defs/color_value" }, + "border_hi": { "$ref": "#/$defs/color_value" }, + "focus": { "$ref": "#/$defs/color_value" }, + "accent": { "$ref": "#/$defs/color_value" }, + "accent_2": { "$ref": "#/$defs/color_value" }, + "diagnostic": { "$ref": "#/$defs/color_value" }, + "sage": { "$ref": "#/$defs/color_value" }, + "success": { "$ref": "#/$defs/color_value" }, + "info": { "$ref": "#/$defs/color_value" }, + "error": { "$ref": "#/$defs/color_value" }, + "warning": { "$ref": "#/$defs/color_value" }, + "lavender": { "$ref": "#/$defs/color_value" }, + "mauve": { "$ref": "#/$defs/color_value" }, + "on_surface": { "$ref": "#/$defs/color_value" }, + "on_accent": { "$ref": "#/$defs/color_value" }, + "on_error": { "$ref": "#/$defs/color_value" }, + "on_focus": { "$ref": "#/$defs/color_value" }, + "link": { "$ref": "#/$defs/color_value" }, + "link_hover": { "$ref": "#/$defs/color_value" }, + "selection": { "$ref": "#/$defs/color_value" }, + "selection_bg": { "$ref": "#/$defs/color_value" }, + "selection_fg": { "$ref": "#/$defs/color_value" }, + "disabled": { "$ref": "#/$defs/color_value" }, + "hover": { "$ref": "#/$defs/color_value" }, + "pressed": { "$ref": "#/$defs/color_value" }, + "overlay": { "$ref": "#/$defs/color_value" }, + "scrim": { "$ref": "#/$defs/color_value" }, + "panel_rgba": { "$ref": "#/$defs/color_value" }, + "module_rgba": { "$ref": "#/$defs/color_value" }, + "active_rgba": { "$ref": "#/$defs/color_value" }, + "inactive_border": { "$ref": "#/$defs/color_value" }, + "prompt_bg": { "$ref": "#/$defs/color_value" }, + "prompt_surface0": { "$ref": "#/$defs/color_value" }, + "prompt_surface1": { "$ref": "#/$defs/color_value" }, + "prompt_surface2": { "$ref": "#/$defs/color_value" }, + "prompt_text": { "$ref": "#/$defs/color_value" }, + "prompt_muted": { "$ref": "#/$defs/color_value" }, + "prompt_accent": { "$ref": "#/$defs/color_value" }, + "prompt_accent_2": { "$ref": "#/$defs/color_value" } }, - "additionalProperties": { - "oneOf": [ - { "$ref": "#/$defs/hex" }, - { "$ref": "#/$defs/rgba_color" }, - { "type": "string" } - ] - } + "additionalProperties": { "$ref": "#/$defs/color_value" } } } } diff --git a/themes/dreamcoder/waybar-dark.css b/themes/dreamcoder/waybar-dark.css index c2eb3b1..d8dd094 100644 --- a/themes/dreamcoder/waybar-dark.css +++ b/themes/dreamcoder/waybar-dark.css @@ -2,17 +2,25 @@ @define-color bg #100f0d; @define-color bg-soft #181512; @define-color surface #201b16; +@define-color surface-2 #392e21; +@define-color surface-3 #4a3b2a; @define-color text #e8dfd0; +@define-color text-heading #f4ecdd; @define-color muted #c7b9aa; +@define-color subtle #938274; +@define-color comment #b8a99a; @define-color border #756052; @define-color border-ui #968878; @define-color focus #5f8f8f; @define-color accent #d99555; @define-color accent-2 #c96a45; @define-color diagnostic #5f95ca; +@define-color lavender #d4b4e6; @define-color error #ed8a7a; @define-color success #4db35f; @define-color warning #e8b866; +@define-color link #d99555; +@define-color link-hover #c96a45; window#waybar { background: rgba(16, 15, 13, 0.78); diff --git a/themes/dreamcoder/waybar-light.css b/themes/dreamcoder/waybar-light.css index 59dee6d..d8a6124 100644 --- a/themes/dreamcoder/waybar-light.css +++ b/themes/dreamcoder/waybar-light.css @@ -2,17 +2,25 @@ @define-color bg #f3eadc; @define-color bg-soft #e6d7c4; @define-color surface #fff7ea; +@define-color surface-2 #c8ad89; +@define-color surface-3 #b89d7a; @define-color text #17120d; +@define-color text-heading #100c07; @define-color muted #352e22; +@define-color subtle #554638; +@define-color comment #725e4c; @define-color border #8a7358; @define-color border-ui #66513b; @define-color focus #0f6570; @define-color accent #824f16; @define-color accent-2 #a7471c; @define-color diagnostic #0d4a68; +@define-color lavender #57478b; @define-color error #842f24; @define-color success #3d723d; @define-color warning #654300; +@define-color link #824f16; +@define-color link-hover #a7471c; window#waybar { background: rgba(243, 234, 220, 0.96); diff --git a/themes/dreamcoder/waybar.css b/themes/dreamcoder/waybar.css index 59dee6d..d8a6124 100644 --- a/themes/dreamcoder/waybar.css +++ b/themes/dreamcoder/waybar.css @@ -2,17 +2,25 @@ @define-color bg #f3eadc; @define-color bg-soft #e6d7c4; @define-color surface #fff7ea; +@define-color surface-2 #c8ad89; +@define-color surface-3 #b89d7a; @define-color text #17120d; +@define-color text-heading #100c07; @define-color muted #352e22; +@define-color subtle #554638; +@define-color comment #725e4c; @define-color border #8a7358; @define-color border-ui #66513b; @define-color focus #0f6570; @define-color accent #824f16; @define-color accent-2 #a7471c; @define-color diagnostic #0d4a68; +@define-color lavender #57478b; @define-color error #842f24; @define-color success #3d723d; @define-color warning #654300; +@define-color link #824f16; +@define-color link-hover #a7471c; window#waybar { background: rgba(243, 234, 220, 0.96);