Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
bf81902
feat: universal theme installer — scraper, generators, CLI
basiclines Mar 18, 2026
6974f9e
data: add scraped themes from VSCode Marketplace
basiclines Mar 18, 2026
b6e9789
Add new set of scrapped themes
basiclines Mar 18, 2026
70f7fbd
feat: add dedup and pair-themes pipeline scripts
basiclines Mar 18, 2026
5880626
feat: multi-sort for theme list + rating fields in schema
basiclines Mar 18, 2026
8532410
feat: theme --preview command — browser preview via Bun.serve
basiclines Mar 18, 2026
c0c3173
feat: polish --preview UI
basiclines Mar 18, 2026
4f7e339
chore: run dedup + pair pipeline on full theme dataset
basiclines Mar 18, 2026
9fd592a
New round of metadata themes
basiclines Mar 19, 2026
5cc2a7e
Metadata themes
basiclines Mar 19, 2026
afd848c
feat(cli): universal theme color engine with LAB interpolation
basiclines Mar 20, 2026
ecaee86
feat(theme): improve dark/light pairing + fuzzy list search
basiclines Mar 20, 2026
f0003d5
feat(pairing): handle implicit light/dark counterparts (Pass 3)
basiclines Mar 20, 2026
bcb9023
fix(pairing): require same publisher in Pass 3 implicit matching
basiclines Mar 20, 2026
621bf3e
feat(pairing): Pass 4 — color fingerprint + name similarity matching
basiclines Mar 20, 2026
4df90c4
feat(theme list): add --min-installs filter
basiclines Mar 20, 2026
19defe0
feat(theme list): add --min-contrast filter
basiclines Mar 20, 2026
78977e3
feat(theme list): add --min-distinct deduplication filter
basiclines Mar 20, 2026
1e574b1
fix(min-distinct): full OKLCH signature + same-publisher scoping
basiclines Mar 20, 2026
23eaa5d
docs: add rampa theme subcommand documentation
basiclines Mar 20, 2026
9a6ef19
fix: address PR review comments — SUPPORTED_APPS aliases and Windows …
basiclines Mar 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
21 changes: 17 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ sdk/src/ ← npm package (@basiclines/rampa-sdk)
cli/src/ ← npm package (@basiclines/rampa)
├── index.ts Main CLI entry (citty framework)
├── colorspace.ts `rampa colorspace` subcommand
└── palette.ts `rampa palette` subcommand
├── palette.ts `rampa palette` subcommand
├── theme.ts `rampa theme` subcommand — list/search/install/show/preview
└── theme-generators/ Per-app theme generators (ghostty, iterm2, alacritty, kitty, warp…)

src/ ← React web app (rampa.studio)
├── components/ shadcn/ui + @react-three/fiber for 3D viewer
Expand Down Expand Up @@ -107,12 +109,23 @@ All color space constructors validate that input colors use the same format (hex
When adding a new color space type, update ALL export functions in `src/usecases/GenerateExports.ts`: `getSpaceColors`, `generateSpaceJsonExport`, `generateSpaceSdkExport`, `generateSpaceCliExport`, `generateSpaceCssExport`.

### CLI Argument Patterns
CLI uses manual argument parsing (not a framework for `colorspace` subcommand). When adding flags:
- Add parsing in `parseColorspaceArgs()`
- Add config file support in `loadConfig()`
CLI uses manual argument parsing (not a framework for `colorspace` and `theme` subcommands). When adding flags:
- Add parsing in `parseColorspaceArgs()` or `parseThemeArgs()`
- Add config file support in `loadConfig()` (colorspace)
- Preserve new fields in the config merge block (lines ~339-348)
- Guard `interpolation === false` for modes that need it

### Theme Subcommand
`rampa theme` installs/lists color themes from the VS Code marketplace. Key files:
- `cli/src/theme.ts` — `parseThemeArgs()`, `listThemes()`, `showTheme()`, `installTheme()`
- `cli/src/theme-generators/` — one file per app (ghostty, iterm2, alacritty, kitty, windows-terminal, warp, hyper, vscode, xcode, android-studio)
- `themes/` — 7,800+ YAML files, each with `colors`, `meta` (mode/pair/contrast), `source`
- `scripts/pair-themes.ts` — Pairs dark/light variants; run after scraping

Theme pairing uses 4 passes: (1) same-publisher exact token match, (2) cross-publisher token match, (3) implicit counterpart (no token on one side), (4) full OKLCH color signature + name similarity for flavor-named families (Catppuccin etc.).

List filters: `--paired`, `--sort`, `--min-installs`, `--min-contrast` (avg APCA), `--min-distinct` (full OKLCH signature dedup, same-publisher scoped).

### Testing
- Runtime: `bun:test` with `describe`/`it`/`expect`
- SDK tests verify palette sizes, index clamping, format conversion, and interpolation modes
Expand Down
57 changes: 57 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,63 @@ rampa palette photo.jpg --average
rampa palette photo.jpg --temperature
```

### Color Themes

Install and manage color themes across multiple terminal and editor apps. Themes are sourced from the VS Code marketplace and converted to native formats.

```bash
rampa theme list
rampa theme list "Dracula"
rampa theme list --paired --sort installs
rampa theme "Tokyo Night" --show
rampa theme "Dracula" --install ghostty
rampa theme "Catppuccin Mocha" --install ghostty,kitty,alacritty --dry-run
```

**Supported apps:** `ghostty`, `iterm2`, `alacritty`, `kitty`, `windows-terminal`, `warp`, `hyper`, `vscode`, `xcode`, `android-studio`

#### List flags

| Flag | Description | Default |
|------|-------------|---------|
| `[query]` | Fuzzy search by name (positional) | — |
| `--paired` | Show only dark/light pairs (one row per pair) | off |
| `--sort <field>` | Sort by: `name`, `installs`, `rating`, `ratings`, `mode` (chainable) | `name` |
| `--min-installs <n>` | Only show themes with at least n installs | 0 |
| `--min-contrast <n>` | Only show themes where avg APCA contrast of fg + tonal colors ≥ n (0–108) | 0 |
| `--min-distinct <n>` | Deduplicate similar themes within the same publisher using full OKLCH signature (0–100) | 0 |
| `--all` | Show all results (default cap: 100) | off |
| `--local` | Use local `themes/` directory instead of fetching from GitHub | off |

#### Install / inspect flags

| Flag | Description |
|------|-------------|
| `--install <app>` | Generate and write theme file for the given app |
| `--show` | Print theme colors and metadata |
| `--preview` | Open interactive browser preview |
| `--dry-run` | Print generated output without writing |

#### Examples

```bash
# Curated paired list — popular, good contrast, deduplicated
rampa theme list --local --sort installs --paired \
--min-installs 1000 --min-contrast 50 --min-distinct 20 --all

# Fuzzy search + paired filter
rampa theme list --paired "Solarized"

# Install to multiple apps
rampa theme "Gruvbox Dark Hard" --install alacritty
rampa theme "Dracula" --install ghostty,kitty,iterm2
rampa theme "Catppuccin Mocha" --install warp --dry-run

# Preview before installing
rampa theme "Tokyo Night" --show
rampa theme "Aura Dark" --preview
```

## Contextual Help

Run any flag without a value to see detailed help:
Expand Down
20 changes: 20 additions & 0 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ if (args[0] === 'palette') {
process.exit(0);
}

// Intercept theme subcommand
if (args[0] === 'theme') {
const { runTheme } = await import('./theme');
await runTheme(args.slice(1));
process.exit(0);
}

if (args.includes('--help') || args.includes('-h') || args.includes('help') || args.length === 0) {
showHelp();
}
Expand Down Expand Up @@ -157,6 +164,15 @@ IMAGE PALETTE
${cyan}rampa palette${reset} ${dim}Extract color palettes from images${reset}
${dim}Use rampa palette --help for details${reset}

COLOR THEMES
${cyan}rampa theme list${reset} ${dim}List all available themes${reset}
${cyan}rampa theme list "Aura"${reset} ${dim}Search themes by name (fuzzy)${reset}
${cyan}rampa theme list --paired${reset} ${dim}Show only dark/light pairs${reset}
${cyan}rampa theme list --paired --sort installs --min-installs 1000 --min-contrast 50 --min-distinct 20${reset}
${cyan}rampa theme "Dracula" --show${reset} ${dim}Inspect theme colors${reset}
${cyan}rampa theme "Dracula" --install ghostty${reset} ${dim}Install theme for an app${reset}
${dim}Use rampa theme --help for details${reset}

OTHER
${cyan}-h, --help${reset} ${dim}Show this help${reset}
${cyan}-v, --version${reset} ${dim}Show version${reset}
Expand All @@ -178,6 +194,10 @@ EXAMPLES
${cyan}rampa inspect -c '#ff6600'${reset}
${cyan}rampa palette photo.jpg${reset}
${cyan}rampa palette photo.jpg --ansi --count 3${reset}
${cyan}rampa theme list "Dracula"${reset}
${cyan}rampa theme list --paired --sort rating${reset}
${cyan}rampa theme "Tokyo Night" --install ghostty,kitty,vscode${reset}
${cyan}rampa theme "Aura Dark" --install alacritty --dry-run${reset}
`;
console.log(help.trim());
process.exit(0);
Expand Down
106 changes: 106 additions & 0 deletions cli/src/theme-color-engine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type { ThemeYAML } from './theme-schema';
import { PlaneColorSpace } from '../../sdk/src/plane-color-space';
import { hexToOklch, oklchToHex } from '../../src/engine/OklchMathEngine';

export interface EditorPalette {
// Backgrounds
bg: string;
bgAlt: string;
lineHighlight: string;
selection: string;
selectionBorder: string;

// Foregrounds
fg: string;
fgMuted: string;
comment: string;

// Cursor
cursor: string;

// Syntax tokens
keyword: string;
string: string;
number: string;
fn: string;
type: string;
variable: string;
operator: string;

// Diff backgrounds
added: string;
removed: string;
modified: string;

// Status colors
error: string;
warning: string;
info: string;

// Pass-through ANSI
ansi: ThemeYAML['colors'];
mode: 'dark' | 'light';
}

const PLANE_SIZE = 10;

const h = (c: any): string => `${c}`;

export function deriveEditorPalette(theme: ThemeYAML): EditorPalette {
const c = theme.colors;
const bg = c.bg;
const fg = c.fg;
const blue = c.blue;
const green = c.green;
const yellow = c.yellow;
const red = c.red;
const magenta = c.magenta;
const cyan = c.cyan;

// type: blue rotated -90° toward teal
const [bl, bc, bh] = hexToOklch(blue);
const teal = oklchToHex(bl, bc, ((bh - 90) + 360) % 360);

const plane = (hue: string) => new PlaneColorSpace(bg, fg, hue).interpolation('lab').size(PLANE_SIZE);

const bluePlane = plane(blue);
const greenPlane = plane(green);
const yellowPlane = plane(yellow);
const redPlane = plane(red);
const magPlane = plane(magenta);
const typePlane = plane(teal);
const neutralPlane = plane(blue);

return {
bg,
bgAlt: h(neutralPlane.at(2, 1)),
lineHighlight: h(neutralPlane.at(2, 1)),
selection: h(bluePlane.at(8, 3)),
selectionBorder: h(bluePlane.at(5, 4)),

fg,
fgMuted: h(neutralPlane.at(0, 7)),
comment: h(neutralPlane.at(0, 4)),

cursor: h(bluePlane.at(9, 7)),

keyword: h(bluePlane.at(9, 7)),
string: h(greenPlane.at(9, 7)),
number: h(yellowPlane.at(9, 6)),
fn: h(yellowPlane.at(9, 7)),
type: h(typePlane.at(6, 8)),
variable: fg,
operator: h(magPlane.at(9, 7)),

added: h(greenPlane.at(5, 3)),
removed: h(redPlane.at(5, 3)),
modified: h(yellowPlane.at(5, 3)),

error: c.red,
warning: c.yellow,
info: c.cyan,

ansi: c,
mode: theme.meta.mode,
};
}
57 changes: 57 additions & 0 deletions cli/src/theme-generators/alacritty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { ThemeYAML } from '../theme-schema';
import type { ThemeGenerator } from './base';
import { deriveEditorPalette } from '../theme-color-engine';

export const alacrittyGenerator: ThemeGenerator = {
name: 'alacritty',

generate(theme: ThemeYAML): string {
const p = deriveEditorPalette(theme);
const c = theme.colors;
const lines: string[] = [];

lines.push('[colors.primary]');
lines.push(`background = "${c.bg}"`);
lines.push(`foreground = "${c.fg}"`);
lines.push('');
lines.push('[colors.cursor]');
lines.push(`cursor = "${p.cursor}"`);
lines.push(`text = "${c.bg}"`);
lines.push('');
lines.push('[colors.selection]');
lines.push(`background = "${p.selection}"`);
lines.push(`text = "${c.fg}"`);
lines.push('');
lines.push('[colors.normal]');
lines.push(`black = "${c.black}"`);
lines.push(`red = "${c.red}"`);
lines.push(`green = "${c.green}"`);
lines.push(`yellow = "${c.yellow}"`);
lines.push(`blue = "${c.blue}"`);
lines.push(`magenta = "${c.magenta}"`);
lines.push(`cyan = "${c.cyan}"`);
lines.push(`white = "${c.white}"`);
lines.push('');
lines.push('[colors.bright]');
lines.push(`black = "${c.brightBlack}"`);
lines.push(`red = "${c.brightRed}"`);
lines.push(`green = "${c.brightGreen}"`);
lines.push(`yellow = "${c.brightYellow}"`);
lines.push(`blue = "${c.brightBlue}"`);
lines.push(`magenta = "${c.brightMagenta}"`);
lines.push(`cyan = "${c.brightCyan}"`);
lines.push(`white = "${c.brightWhite}"`);

return lines.join('\n') + '\n';
},

installPath(os) {
if (os === 'darwin' || os === 'linux') return '~/.config/alacritty/themes';
if (os === 'win32') return '%APPDATA%\\alacritty\\themes';
return null;
},

fileExtension() {
return '.toml';
},
};
Loading
Loading