diff --git a/.gitignore b/.gitignore index d9ac13c..e7ab7f1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules/ dist/ .vite/ .DS_Store -*.log \ No newline at end of file +*.log +__pycache__/ \ No newline at end of file diff --git a/README.md b/README.md index ff4873b..d3a7d6f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-OpenPi, a desktop workbench for the Pi coding agent +pidex, a desktop workbench for the Pi coding agent **A desktop workbench for the Pi coding agent.** **Workspace-first, not chat-first.** @@ -16,19 +16,25 @@ ## Why this exists -Daily coding-agent work means juggling repos, branches, and sessions, and the current tools are chat-first: the primary screen is a conversation, not your work. OpenPi flips that. It is a kitchen bench where every active repo, its branch, its sessions, and what has gone stale are visible at a glance. +Daily coding-agent work means juggling repos, branches, and sessions, and the current tools are chat-first: the primary screen is a conversation, not your work. pidex flips that. It is a kitchen bench where every active repo, its branch, its sessions, and what has gone stale are visible at a glance. -| The pain, observed daily | OpenPi's answer | +| The pain, observed daily | pidex's answer | |---|---| | Workspace switching takes too many clicks | One dashboard of every repo with Pi sessions, sorted by last activity | | Losing the thread when resuming | Repo, branch, and session state as first-class UI, not buried chat history | | Long sessions get sluggish | [Pretext](https://github.com/chenglou/pretext) measures multiline text off the DOM hot path, ~600x faster than browser measurement | | Worktrees feel unreliable | Git and worktree state surfaced directly, no chatting with the agent to switch branches | -OpenPi is a personal project: learn by building, sharpen day-to-day DX, and share a cleaner local-first harness pattern for coding agents. Not a commercial product, not an OpenCode clone. Install, BYOK, and provider wiring belong to its sibling, Weldable; OpenPi focuses entirely on the daily coding workflow. +pidex is a personal project: learn by building, sharpen day-to-day DX, and share a cleaner local-first harness pattern for coding agents. Not a commercial product, not an OpenCode clone. Install, BYOK, and provider wiring belong to its sibling, Weldable; pidex focuses entirely on the daily coding workflow. > A developer's kitchen bench, not a chat pane. +
+ +pidex dashboard preview + +
+ ## Quick Start You need [mise](https://mise.jdx.dev/) and the [Pi CLI](https://pi.dev/). The dashboard reads existing sessions from `~/.pi/agent/sessions`, so run `pi` in a repo or two first if you want it to light up. @@ -128,6 +134,33 @@ Long term: - [ ] Task tracker awareness alongside sessions - [ ] Optional adapters for other agent runtimes +## Design tokens + +The workbench inherits its palette from the [Pi coding agent](https://pi.dev) so the desktop harness feels like part of the same family as the runtime it drives. Source of truth: [`scripts/_design.py`](scripts/_design.py), hexes pulled from `pi.dev/style.css`. + +| Token | Hex | Role | +|---|---|---| +| warm-black | `#13110f` | Primary window background | +| bg-deep | `#0d1116` | Deepest surface | +| bg-canvas | `#161d27` | Canvas surface | +| panel | `#212730` | Sidebar / panel surface | +| panel-soft | `#252f3d` | Card surface | +| parchment | `#dacbc2` | Named cream, primary mark and text | +| moonstone | `#ebe7e4` | Lighter cream, lit facet | +| driftwood | `#5c5752` | Warm mid-gray, shadow facet | +| terracotta | `#844f3b` | Warm bronze accent | +| terracotta-light | `#b86b52` | Accent highlight, status dots | +| sunkissed | `#e1b06e` | Warm gold accent | +| sage | `#a3a473` | Sage accent | +| accent-blue | `#6a9fcc` | Links, focus rings | + +Regenerate assets after a token edit: + +```bash +uv run --with fonttools python scripts/build-icon.py # assets/icon, mark +uv run --with fonttools python scripts/build-screenshot.py # docs/assets/screenshot +``` + ## Documentation | Doc | What it covers | diff --git a/assets/icon-premium.jpg b/assets/icon-premium.jpg new file mode 100644 index 0000000..3e1d497 Binary files /dev/null and b/assets/icon-premium.jpg differ diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000..1bc6600 Binary files /dev/null and b/assets/icon.png differ diff --git a/assets/icon.svg b/assets/icon.svg new file mode 100644 index 0000000..f86a3dc --- /dev/null +++ b/assets/icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/mark.png b/assets/mark.png new file mode 100644 index 0000000..2c5b599 Binary files /dev/null and b/assets/mark.png differ diff --git a/assets/mark.svg b/assets/mark.svg new file mode 100644 index 0000000..7101342 --- /dev/null +++ b/assets/mark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/docs/assets/banner.png b/docs/assets/banner.png index 0a9c32f..4328c9a 100644 Binary files a/docs/assets/banner.png and b/docs/assets/banner.png differ diff --git a/docs/assets/screenshot.png b/docs/assets/screenshot.png new file mode 100644 index 0000000..c23b4cf Binary files /dev/null and b/docs/assets/screenshot.png differ diff --git a/docs/assets/screenshot.svg b/docs/assets/screenshot.svg new file mode 100644 index 0000000..7ec5d0f --- /dev/null +++ b/docs/assets/screenshot.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pidex + + + + + + + diff --git a/scripts/_design.py b/scripts/_design.py new file mode 100644 index 0000000..fe0b2cc --- /dev/null +++ b/scripts/_design.py @@ -0,0 +1,73 @@ +"""Design tokens for pidex, sourced from the Pi coding agent website +(pi.dev/style.css) so the workbench visually aligns with the Pi runtime +it drives. + +Provenance: https://pi.dev/style.css, lines ~85-130 (the `:root` token +block). Update these hexes if pi.dev revs its palette. + +Token groups: + - Warm darks (window bg, panel surfaces) + - Creams (text, marks, lit facet) + - Terracotta / sunkissed (warm accents, the fluid-crest peak) + - Sage / accent-blue (secondary accents) +""" + +from __future__ import annotations + +from fontTools.ttLib import TTFont +from fontTools.pens.svgPathPen import SVGPathPen + +# --- Warm darks --- +# pi.dev: --color-warm-black +WARM_BLACK = "#13110f" +# pi.dev: --panel-base +PANEL = "#212730" +# pi.dev: --panel-soft-base +PANEL_SOFT = "#252f3d" +# pi.dev: --bg-deep, --bg-canvas +BG_DEEP = "#0d1116" +BG_CANVAS = "#161d27" + +# --- Creams --- +# pi.dev: --color-parchment (the named "cream") +PARCHMENT = "#dacbc2" +# pi.dev: --color-moonstone (lighter cream / near-white) +MOONSTONE = "#ebe7e4" +# pi.dev: --color-driftwood (warm mid-gray, shadow tone) +DRIFTWOOD = "#5c5752" + +# --- Warm accents (the fluid bronze / terracotta aesthetic) --- +# pi.dev: --color-terracotta +TERRACOTTA = "#844f3b" +# pi.dev: --color-terracotta-light +TERRACOTTA_LIGHT = "#b86b52" +# pi.dev: --color-sunkissed +SUNKISSED = "#e1b06e" + +# --- Secondary accents --- +# pi.dev: --color-sage +SAGE = "#a3a473" +# pi.dev: --color-accent-blue / --accent +ACCENT_BLUE = "#6a9fcc" +# pi.dev: --color-tidal-blue / --thread-blue +TIDAL_BLUE = "#4b607c" + +# --- Text --- +# pi.dev: --text +TEXT = "#ebe7e4" +# pi.dev: --muted +MUTED = "#9fa4ab" + +DEFAULT_FONT = "/System/Library/Fonts/Supplemental/Georgia Italic.ttf" + + +def pi_path(font_path: str = DEFAULT_FONT) -> tuple[str, tuple[int, int, int, int], int]: + """Return (svg_path_d, bbox, unitsPerEm) for the π glyph (U+03C0).""" + font = TTFont(font_path) + cmap = font.getBestCmap() + gname = cmap[ord("π")] + pen = SVGPathPen(font.getGlyphSet()) + font.getGlyphSet()[gname].draw(pen) + glyf = font["glyf"][gname] + bbox = (glyf.xMin, glyf.yMin, glyf.xMax, glyf.yMax) + return pen.getCommands(), bbox, font["head"].unitsPerEm \ No newline at end of file diff --git a/scripts/build-icon.py b/scripts/build-icon.py new file mode 100644 index 0000000..6b02b89 --- /dev/null +++ b/scripts/build-icon.py @@ -0,0 +1,109 @@ +"""Build the pidex app icon and mark SVGs from Georgia Italic, using the +Pi (pi.dev) design tokens so the workbench visually aligns with the Pi +runtime. + +Outputs: + assets/icon.svg - flat 2D app icon (warm-black rounded square + cream faceted pi) + assets/mark.svg - flat pi mark on transparent, for in-UI use + assets/icon.png - rasterized icon at 1024x1024 (for Electron build/icon.png) + assets/mark.png - rasterized mark at 1024x1024 +""" + +from __future__ import annotations + +import shutil +import subprocess +import sys +from pathlib import Path + +REPO = Path(__file__).resolve().parent.parent +ASSETS = REPO / "assets" +SCRIPTS = Path(__file__).resolve().parent + +sys.path.insert(0, str(SCRIPTS)) +from _design import ( # noqa: E402 + WARM_BLACK, + PARCHMENT, + MOONSTONE, + pi_path, +) + +# Icon geometry (1024x1024 canvas, macOS-style squircle radius ~22.37%). +ICON_SIZE = 1024 +ICON_RADIUS = 229 + +# π placement: glyph centered, height ~56% of icon. +SCALE = 0.56 + +# Screen upper-left triangle (defined in glyph coords; the group transform flips Y). +LIT_CLIP = "-22,980 1295,980 -22,-19" + + +def build_icon_svg(d: str, bbox: tuple[int, int, int, int]) -> str: + gx = (bbox[0] + bbox[2]) / 2 + gy = (bbox[1] + bbox[3]) / 2 + cx = cy = ICON_SIZE / 2 + return f""" + + + + + + + + + + + +""" + + +def build_mark_svg(d: str, bbox: tuple[int, int, int, int]) -> str: + gx = (bbox[0] + bbox[2]) / 2 + gy = (bbox[1] + bbox[3]) / 2 + size = ICON_SIZE + cx = cy = size / 2 + return f""" + + + + +""" + + +def render_png(svg: Path, png: Path, width: int) -> None: + if not shutil.which("inkscape"): + sys.exit("inkscape not found on PATH; install via brew install inkscape") + subprocess.run( + [ + "inkscape", + str(svg), + "--export-type=png", + "--export-filename", + str(png), + "--export-width", + str(width), + ], + check=True, + ) + + +def main() -> None: + ASSETS.mkdir(exist_ok=True) + d, bbox, _ = pi_path() + + icon_svg = ASSETS / "icon.svg" + mark_svg = ASSETS / "mark.svg" + icon_svg.write_text(build_icon_svg(d, bbox)) + mark_svg.write_text(build_mark_svg(d, bbox)) + print(f"wrote {icon_svg}") + print(f"wrote {mark_svg}") + + render_png(icon_svg, ASSETS / "icon.png", ICON_SIZE) + render_png(mark_svg, ASSETS / "mark.png", ICON_SIZE) + print(f"wrote {ASSETS / 'icon.png'}") + print(f"wrote {ASSETS / 'mark.png'}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/build-screenshot.py b/scripts/build-screenshot.py new file mode 100644 index 0000000..04de711 --- /dev/null +++ b/scripts/build-screenshot.py @@ -0,0 +1,180 @@ +"""Build a faithful pidex dashboard mockup SVG (and PNG) using the Pi +(pi.dev) design tokens. Layout mirrors what the renderer ships: warm-black +window with a panel-toned workspace sidebar on the left, session pane on +the right, input pill at the bottom. Apple-style soft drop shadow under +the window. + +Output: + docs/assets/screenshot.svg + docs/assets/screenshot.png (1440x900) +""" + +from __future__ import annotations + +import shutil +import subprocess +import sys +from pathlib import Path + +REPO = Path(__file__).resolve().parent.parent +OUT = REPO / "docs" / "assets" + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from _design import ( # noqa: E402 + WARM_BLACK, + PANEL, + PANEL_SOFT, + PARCHMENT, + MOONSTONE, + TERRACOTTA_LIGHT, + pi_path, +) + +# Canvas +W, H = 1440, 900 + +# Window geometry (centered) +WW, WH = 1200, 760 +WX = (W - WW) // 2 +WY = (H - WH) // 2 +WIN_R = 14 + +# Sidebar +SIDE_W = 300 +SIDE_BG = PANEL +SIDE_PAD = 22 + +# Warm neutral ground that complements the warm-black window +BG = "#E2D8CC" + + +def svg() -> str: + d, bbox, _ = pi_path() + gx = (bbox[0] + bbox[2]) / 2 + gy = (bbox[1] + bbox[3]) / 2 + + # Sidebar π mark + pidex wordmark + mark_cx = WX + 36 + mark_cy = WY + 44 + mark_scale = 0.036 # ~37px tall + mark_transform = ( + f"translate({mark_cx} {mark_cy}) " + f"scale({mark_scale} -{mark_scale}) " + f"translate({-gx} {-gy})" + ) + + # Workspace cards + cards = [] + card_y = WY + 100 + for i in range(4): + cy = card_y + i * 72 + cards.append( + f'' + f'' + f'' + f'' + f'' + ) + + pane_x = WX + SIDE_W + pane_w = WW - SIDE_W + header_y = WY + 22 + cards_y = WY + 110 + card_h = 132 + + header = ( + f'' + f'' + f'' + f'' + f'' + f'' + ) + + main_cards = [] + for i in range(2): + cy = cards_y + i * (card_h + 18) + main_cards.append( + f'' + f'' + f'' + f'' + f'' + f'' + f'' + ) + + pill_y = WY + WH - 64 + pill = ( + f'' + f'' + f'' + f'' + ) + + return f""" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pidex + + {''.join(cards)} + + {header} + {''.join(main_cards)} + {pill} + +""" + + +def render_png(svg: Path, png: Path, width: int) -> None: + if not shutil.which("inkscape"): + sys.exit("inkscape not found on PATH; install via brew install inkscape") + subprocess.run( + ["inkscape", str(svg), "--export-type=png", "--export-filename", str(png), "--export-width", str(width)], + check=True, + ) + + +def main() -> None: + OUT.mkdir(parents=True, exist_ok=True) + svg_path = OUT / "screenshot.svg" + png_path = OUT / "screenshot.png" + svg_path.write_text(svg()) + render_png(svg_path, png_path, W) + print(f"wrote {svg_path}") + print(f"wrote {png_path}") + + +if __name__ == "__main__": + main() \ No newline at end of file