docs: add static website and docs

This commit is contained in:
Federico Jaramillo Martinez
2026-05-15 23:35:48 +02:00
parent aab9ffb8e2
commit c66d834c88
11 changed files with 1421 additions and 1 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@jmfederico/pi-web": patch
---
Add a static Pi Web website with installation docs, troubleshooting FAQ, and GitHub Pages deployment.
+41
View File
@@ -0,0 +1,41 @@
name: Deploy static site
on:
push:
branches: [main]
paths:
- docs/**
- .github/workflows/pages.yml
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: github-pages
cancel-in-progress: false
jobs:
deploy:
name: Deploy GitHub Pages
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Configure Pages
uses: actions/configure-pages@v6
- name: Upload static site
uses: actions/upload-pages-artifact@v4
with:
path: docs
- name: Deploy
id: deployment
uses: actions/deploy-pages@v4
+4
View File
@@ -6,6 +6,8 @@
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Pi Coding Agent](https://img.shields.io/badge/Pi-Coding%20Agent-6f42c1)](https://github.com/earendil-works/pi/tree/main/packages/coding-agent)
Website: <https://jmfederico.github.io/pi-web/>
![Pi Web](docs/assets/pi-web-banner.png)
**Run AI coding agents on your own machine or server, keep them alive in real workspaces, and control everything from a browser.**
@@ -175,6 +177,8 @@ npm run dev
Open the Vite URL, usually <http://localhost:8505>.
During development, the static marketing/docs site is also served by the Vite dev server at <http://localhost:8505/site/>.
For the recommended split development setup, run these in separate terminals:
```bash
View File
+202
View File
@@ -0,0 +1,202 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Pi Web FAQ</title>
<meta name="description" content="Answers for common Pi Web install and runtime issues." />
<meta property="og:title" content="Pi Web FAQ" />
<meta property="og:image" content="assets/pi-web-banner.png" />
<link rel="icon" href="data:," />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=IBM+Plex+Sans:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<header class="site-header">
<nav class="container nav" aria-label="Main navigation">
<a class="brand" href="./" aria-label="Pi Web home">Pi Web</a>
<div class="nav-links">
<a href="./">Home</a>
<a href="install.html">Install</a>
<a href="faq.html" aria-current="page">FAQ</a>
<a href="https://github.com/jmfederico/pi-web">GitHub</a>
</div>
</nav>
</header>
<main>
<section class="page-hero">
<div class="container">
<p class="eyebrow"><span class="pulse"></span> FAQ & troubleshooting</p>
<h1>Fix the things that usually go wrong first.</h1>
<p>
Most Pi Web install issues are environment issues: systemd availability, PATH setup, Node version managers,
or remote access expectations.
</p>
</div>
</section>
<section class="section compact">
<div class="container doc-layout">
<aside class="toc" aria-label="FAQ contents">
<strong>Questions</strong>
<a href="#is-this-linux-only">Is this Linux only?</a>
<a href="#tools-are-not-found">Tools are failing / node not found</a>
<a href="#doctor-fails">What does doctor check?</a>
<a href="#nvm-fnm-asdf">nvm, fnm, or asdf issues</a>
<a href="#systemd-not-found">systemctl is not available</a>
<a href="#cannot-open">I cannot open the web UI</a>
<a href="#public-internet">Can I expose this publicly?</a>
<a href="#sessions-stop">Sessions stop unexpectedly</a>
<a href="#logs">Where are logs?</a>
</aside>
<div class="faq-list">
<article id="is-this-linux-only" class="faq-item">
<h2>Is this Linux only?</h2>
<p>
No. Pi Web can run anywhere its runtime dependencies work, including macOS and Windows through WSL. The
automatic <code>pi-web install</code> command is the Linux-specific part because it writes and manages
<code>systemctl --user</code> services.
</p>
<ul>
<li><strong>Linux with systemd:</strong> use <code>npm install -g @jmfederico/pi-web</code> and <code>pi-web install</code>.</li>
<li><strong>macOS:</strong> install the package, then run <code>pi-web-sessiond</code> and <code>pi-web-server</code> manually.</li>
<li><strong>Windows:</strong> use WSL. If your WSL distro has systemd enabled, the installer may work; otherwise use the manual run path.</li>
</ul>
<p>Native Windows outside WSL is not the recommended path today.</p>
</article>
<article id="tools-are-not-found" class="faq-item">
<h2>Tools are failing, node is not found, or Pi cannot find commands</h2>
<p>
The shell environment needs to be set up so login shells have the required PATH entries for Pi Web, Pi,
and any tools your agents need. Pi Web services run commands through a non-interactive login shell, so
an interactive terminal can work while services fail.
</p>
<div class="code-card">
<div class="copy-row">
<strong>Run diagnostics</strong>
<button class="copy-button" data-copy="#doctor-command">Copy</button>
</div>
<pre id="doctor-command"><code><span class="prompt">$</span> pi-web doctor</code></pre>
</div>
<ul>
<li><strong>bash:</strong> put PATH setup in <code>~/.bash_profile</code> or <code>~/.profile</code>. If <code>~/.bash_profile</code> exists, source <code>~/.profile</code> from it if needed.</li>
<li><strong>zsh:</strong> put PATH setup in <code>~/.zprofile</code>, not only <code>~/.zshrc</code>.</li>
<li><strong>fish:</strong> prefer universal PATH setup, for example <code>fish_add_path -U ...</code>.</li>
</ul>
<p>Avoid relying only on prompt hooks or interactive-only shell files for tools needed by services.</p>
</article>
<article id="doctor-fails" class="faq-item">
<h2>What does <code>pi-web doctor</code> check?</h2>
<p>
It checks whether the service shell and systemd user environment can find Node 22+, npm, Pi, and the Pi
Web binaries. It also reports whether user service lingering is enabled for server-style installs.
</p>
<p>
If something works in your terminal but fails in doctor, treat that as a login-shell PATH mismatch and
move the setup earlier in your shell startup chain.
</p>
</article>
<article id="nvm-fnm-asdf" class="faq-item">
<h2>I installed Node with nvm, fnm, asdf, or another version manager</h2>
<p>
Version managers often initialize in interactive shells only. Pi Web needs Node and npm to be available to
login shells and systemd user services.
</p>
<ul>
<li>Move the version manager initialization to your login shell file.</li>
<li>Make sure <code>node --version</code> is at least <code>v22</code> from <code>bash -lc</code>, <code>zsh -lc</code>, or your detected shell.</li>
<li>Run <code>pi-web doctor</code> again after changing shell files.</li>
</ul>
</article>
<article id="systemd-not-found" class="faq-item">
<h2><code>systemctl --user</code> is not available</h2>
<p>
The automatic installer needs Linux user systemd. On macOS, WSL without systemd, containers without a
user systemd manager, or minimal Linux environments, run the two processes manually:
</p>
<div class="code-card">
<div class="copy-row">
<strong>Manual run</strong>
<button class="copy-button" data-copy="#manual-faq">Copy</button>
</div>
<pre id="manual-faq"><code><span class="prompt">$</span> pi-web-sessiond
<span class="comment"># in another terminal</span>
<span class="prompt">$</span> PI_WEB_PORT=8504 pi-web-server</code></pre>
</div>
</article>
<article id="cannot-open" class="faq-item">
<h2>I cannot open the web UI</h2>
<ul>
<li>Check <code>pi-web status</code>.</li>
<li>Check logs with <code>pi-web logs</code>.</li>
<li>Make sure port <code>8504</code> is not already in use.</li>
<li>On a remote server, use an SSH tunnel: <code>ssh -L 8504:127.0.0.1:8504 user@server</code>.</li>
</ul>
</article>
<article id="public-internet" class="faq-item">
<h2>Can I expose Pi Web to the public internet?</h2>
<p>
Do not expose it directly. Pi Web assumes trusted users and trusted server paths. Keep the default
localhost bind and use SSH tunneling, a VPN, or a reverse proxy with authentication and network policy.
</p>
</article>
<article id="sessions-stop" class="faq-item">
<h2>Sessions stop unexpectedly</h2>
<p>
Active agent runtimes are owned by the session daemon. Browser disconnects and web/API restarts should
not stop them. If they stop anyway, check whether the session daemon service restarted or crashed.
</p>
<div class="code-card">
<div class="copy-row">
<strong>Session daemon logs</strong>
<button class="copy-button" data-copy="#session-logs">Copy</button>
</div>
<pre id="session-logs"><code><span class="prompt">$</span> systemctl --user status pi-web-sessiond.service
<span class="prompt">$</span> journalctl --user -u pi-web-sessiond.service -f</code></pre>
</div>
</article>
<article id="logs" class="faq-item">
<h2>Where are logs?</h2>
<p>For the Linux systemd install, use:</p>
<div class="code-card">
<div class="copy-row">
<strong>Logs</strong>
<button class="copy-button" data-copy="#logs-command">Copy</button>
</div>
<pre id="logs-command"><code><span class="prompt">$</span> pi-web logs
<span class="prompt">$</span> journalctl --user -u pi-web-sessiond.service -u pi-web.service -f</code></pre>
</div>
</article>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<span>Pi Web FAQ</span>
<div class="footer-links">
<a href="./">Home</a>
<a href="install.html">Install</a>
<a href="https://github.com/jmfederico/pi-web/issues">Issues</a>
</div>
</div>
</footer>
<script src="site.js"></script>
</body>
</html>
+189
View File
@@ -0,0 +1,189 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Pi Web — persistent AI coding agents in your browser</title>
<meta
name="description"
content="Pi Web is a browser control plane for persistent Pi Coding Agent sessions running on your own machine or server."
/>
<meta property="og:title" content="Pi Web" />
<meta
property="og:description"
content="Run AI coding agents in real workspaces, keep them alive, and control everything from a browser."
/>
<meta property="og:image" content="assets/pi-web-banner.png" />
<link rel="icon" href="data:," />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=IBM+Plex+Sans:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<header class="site-header">
<nav class="container nav" aria-label="Main navigation">
<a class="brand" href="./" aria-label="Pi Web home">Pi Web</a>
<div class="nav-links">
<a href="./" aria-current="page">Home</a>
<a href="install.html">Install</a>
<a href="faq.html">FAQ</a>
<a href="https://github.com/jmfederico/pi-web">GitHub</a>
</div>
</nav>
</header>
<main>
<section class="hero">
<div class="container hero-grid">
<div>
<p class="eyebrow"><span class="pulse"></span> A cockpit for agentic development</p>
<h1>Your agents keep working. <span class="gradient-text">You just need a browser.</span></h1>
<p class="hero-lede">
Pi Web runs Pi Coding Agent sessions in real server-side workspaces, keeps them alive when your browser
leaves, and gives you a fast web surface to supervise, redirect, and review the work.
</p>
<div class="hero-actions">
<a class="button primary" href="#quick-install">Install in minutes</a>
<a class="button" href="install.html">Read the docs</a>
</div>
<div class="hero-stats" aria-label="Highlights">
<div class="stat"><strong>Persistent</strong><span>sessions survive browser disconnects</span></div>
<div class="stat"><strong>Parallel</strong><span>projects, worktrees, and agents</span></div>
<div class="stat"><strong>Yours</strong><span>runs on your machine or server</span></div>
</div>
</div>
<aside class="terminal" aria-label="Pi Web install preview">
<div class="terminal-top">
<div class="dots"><span></span><span></span><span></span></div>
<span>agent-hub.local</span>
</div>
<pre><code><span class="prompt">$</span> pi-web status
✓ session daemon running
✓ web server listening on 127.0.0.1:8504
<span class="prompt">$</span> open http://127.0.0.1:8504
# laptop, phone, tablet — same live sessions
<span class="prompt">$</span> pi-web doctor
✓ login shell can find node >= 22
✓ systemd user shell can find pi
✓ ready for persistent agent work</code></pre>
</aside>
</div>
</section>
<section class="section compact">
<div class="container">
<div class="demo-frame">
<div class="demo-caption">
<strong>Workspaces, sessions, transcripts, terminals — one agent control plane.</strong>
<span>Bring your own repositories.</span>
</div>
<img src="assets/pi-web-demo.gif" alt="Pi Web browser UI demo" />
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="section-head">
<p class="eyebrow"><span class="pulse"></span> Why it feels different</p>
<h2>Not another tab. A persistent place for agent work.</h2>
</div>
<div class="cards">
<article class="card">
<div class="card-icon"></div>
<h3>Come back later</h3>
<p>
Active sessions live in a long-running daemon, not in your browser tab. Close the lid, reconnect from a
phone, and continue supervising the same work.
</p>
</article>
<article class="card">
<div class="card-icon"></div>
<h3>Use real workspaces</h3>
<p>
Add projects once, discover git worktrees automatically, and start agents where the code actually lives.
</p>
</article>
<article class="card">
<div class="card-icon"></div>
<h3>Keep control</h3>
<p>
Watch transcript history, status, shell activity, context usage, and costs while redirecting the agent in
realtime.
</p>
</article>
</div>
</div>
</section>
<section id="quick-install" class="section">
<div class="container install-panel">
<div class="install-card">
<p class="eyebrow"><span class="pulse"></span> Quick install</p>
<h2>Linux servers get the smooth path.</h2>
<ol>
<li>Install the package globally with npm.</li>
<li>Run the installer to create user-level systemd services.</li>
<li>Open the local URL, or tunnel it from a remote machine.</li>
</ol>
<div class="doc-actions">
<a class="button primary" href="install.html">Complete installation guide</a>
<a class="button" href="faq.html">Troubleshooting FAQ</a>
</div>
</div>
<div class="terminal quick-install">
<div class="copy-row">
<strong>Recommended Linux install</strong>
<button class="copy-button" data-copy="#home-install">Copy</button>
</div>
<pre id="home-install"><code><span class="prompt">$</span> npm install -g @jmfederico/pi-web
<span class="prompt">$</span> pi-web install
<span class="prompt">$</span> pi-web doctor
<span class="comment"># Open http://127.0.0.1:8504</span></code></pre>
</div>
</div>
</section>
<section class="section compact">
<div class="container doc-grid">
<article class="doc-card">
<h3>macOS and WSL?</h3>
<p>
Pi Web itself is not Linux-only. The one-command service installer targets Linux systemd. macOS and WSL can
run Pi Web manually; WSL with systemd can use the installer too.
</p>
<a href="faq.html#is-this-linux-only">Read compatibility notes →</a>
</article>
<article class="doc-card">
<h3>Node or tools not found?</h3>
<p>
Services run login shells. If tools work in your interactive terminal but not in Pi Web, fix PATH in your
login shell startup files and run the doctor command.
</p>
<a href="faq.html#tools-are-not-found">Fix PATH issues →</a>
</article>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<span>Pi Web · remote control for persistent Pi Coding Agent sessions.</span>
<div class="footer-links">
<a href="install.html">Install</a>
<a href="faq.html">FAQ</a>
<a href="https://www.npmjs.com/package/@jmfederico/pi-web">npm</a>
</div>
</div>
</footer>
<script src="site.js"></script>
</body>
</html>
+229
View File
@@ -0,0 +1,229 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Install Pi Web</title>
<meta name="description" content="Complete installation guide for Pi Web on Linux, macOS, and Windows WSL." />
<meta property="og:title" content="Install Pi Web" />
<meta property="og:image" content="assets/pi-web-banner.png" />
<link rel="icon" href="data:," />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=IBM+Plex+Sans:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<header class="site-header">
<nav class="container nav" aria-label="Main navigation">
<a class="brand" href="./" aria-label="Pi Web home">Pi Web</a>
<div class="nav-links">
<a href="./">Home</a>
<a href="install.html" aria-current="page">Install</a>
<a href="faq.html">FAQ</a>
<a href="https://github.com/jmfederico/pi-web">GitHub</a>
</div>
</nav>
</header>
<main>
<section class="page-hero">
<div class="container">
<p class="eyebrow"><span class="pulse"></span> Installation guide</p>
<h1>Get Pi Web running where your agents work.</h1>
<p>
The best production-style setup is a Linux machine with user-level systemd services. macOS and Windows WSL
are useful too: run the two Pi Web processes manually, or use the installer in WSL when systemd is enabled.
</p>
</div>
</section>
<section class="section compact">
<div class="container doc-layout">
<aside class="toc" aria-label="Install page contents">
<strong>On this page</strong>
<a href="#requirements">Requirements</a>
<a href="#linux-systemd">Linux systemd install</a>
<a href="#one-line">One-line install</a>
<a href="#pi-package">Install through Pi</a>
<a href="#manual-run">macOS / WSL manual run</a>
<a href="#remote-access">Remote access</a>
<a href="#manage-services">Manage services</a>
<a href="#configure">Configure</a>
<a href="#uninstall">Uninstall</a>
</aside>
<div class="doc-content">
<section id="requirements">
<h2>Requirements</h2>
<ul>
<li><strong>Node.js 22 or newer</strong> and npm.</li>
<li><strong>Pi Coding Agent</strong> installed/configured so the <code>pi</code> command works for your user.</li>
<li>A shell login environment that exposes Node, npm, Pi, git, and any tools your agents need.</li>
<li>For the automatic installer: Linux with <code>systemctl --user</code>.</li>
</ul>
<div class="callout warning">
<strong>Important PATH detail:</strong>
Pi Web services run through your login shell with <code>-lc</code>. Setup that only lives in interactive shell
files or prompt hooks may not be visible to services. Run <code>pi-web doctor</code> after installing.
</div>
</section>
<section id="linux-systemd">
<h2>Recommended: Linux systemd install</h2>
<p>
This creates two user services: one long-lived session daemon and one web/API service. It is the easiest
way to keep sessions available after SSH disconnects or browser restarts.
</p>
<div class="code-card">
<div class="copy-row">
<strong>Linux install</strong>
<button class="copy-button" data-copy="#linux-install">Copy</button>
</div>
<pre id="linux-install"><code><span class="prompt">$</span> npm install -g @jmfederico/pi-web
<span class="comment"># Recommended on servers so user services survive logout/reboot:</span>
<span class="prompt">$</span> sudo loginctl enable-linger "$USER"
<span class="prompt">$</span> pi-web install
<span class="prompt">$</span> pi-web doctor</code></pre>
</div>
<p>Then open <a href="http://127.0.0.1:8504">http://127.0.0.1:8504</a>.</p>
</section>
<section id="one-line">
<h2>One-line install</h2>
<p>If you prefer a curl pipe, use the repository installer:</p>
<div class="code-card">
<div class="copy-row">
<strong>One-liner</strong>
<button class="copy-button" data-copy="#one-line-install">Copy</button>
</div>
<pre id="one-line-install"><code><span class="prompt">$</span> curl -fsSL https://raw.githubusercontent.com/jmfederico/pi-web/main/install.sh | sh</code></pre>
</div>
</section>
<section id="pi-package">
<h2>Install through Pi</h2>
<p>Pi Web is also published as a Pi package. This exposes a <code>/pi-web</code> command inside Pi.</p>
<div class="code-card">
<div class="copy-row">
<strong>Pi package path</strong>
<button class="copy-button" data-copy="#pi-package-install">Copy</button>
</div>
<pre id="pi-package-install"><code><span class="prompt">$</span> pi install npm:@jmfederico/pi-web
<span class="comment"># Then inside Pi:</span>
/pi-web install
/pi-web status
/pi-web logs
/pi-web doctor</code></pre>
</div>
</section>
<section id="manual-run">
<h2>macOS / WSL manual run</h2>
<p>
Pi Web is not Linux-only, but the <code>pi-web install</code> service setup is Linux/systemd-specific. On macOS
or WSL without systemd, install the package and run the daemon and web server yourself.
</p>
<div class="code-card">
<div class="copy-row">
<strong>Manual processes</strong>
<button class="copy-button" data-copy="#manual-install">Copy</button>
</div>
<pre id="manual-install"><code><span class="prompt">$</span> npm install -g @jmfederico/pi-web
<span class="comment"># Terminal 1</span>
<span class="prompt">$</span> pi-web-sessiond
<span class="comment"># Terminal 2</span>
<span class="prompt">$</span> PI_WEB_PORT=8504 pi-web-server</code></pre>
</div>
<p>
On modern WSL distributions with systemd enabled, the Linux installer can work. Without systemd, use the
manual run approach above.
</p>
</section>
<section id="remote-access">
<h2>Remote access</h2>
<p>
Pi Web binds to <code>127.0.0.1:8504</code> by default. For a remote server, keep that safe default and use an
SSH tunnel:
</p>
<div class="code-card">
<div class="copy-row">
<strong>SSH tunnel</strong>
<button class="copy-button" data-copy="#ssh-tunnel">Copy</button>
</div>
<pre id="ssh-tunnel"><code><span class="prompt">$</span> ssh -L 8504:127.0.0.1:8504 user@your-server
<span class="comment"># Open http://127.0.0.1:8504 on your local machine</span></code></pre>
</div>
<div class="callout danger">
Pi Web is designed for trusted users and trusted server paths. Do not expose it directly to the public
internet without adding your own network controls, authentication, or reverse proxy policy.
</div>
</section>
<section id="manage-services">
<h2>Manage services</h2>
<div class="code-card">
<div class="copy-row">
<strong>Useful commands</strong>
<button class="copy-button" data-copy="#manage-commands">Copy</button>
</div>
<pre id="manage-commands"><code><span class="prompt">$</span> pi-web status
<span class="prompt">$</span> pi-web logs
<span class="prompt">$</span> pi-web restart
<span class="prompt">$</span> pi-web doctor
<span class="prompt">$</span> systemctl --user status pi-web-sessiond.service pi-web.service
<span class="prompt">$</span> journalctl --user -u pi-web-sessiond.service -u pi-web.service -f</code></pre>
</div>
</section>
<section id="configure">
<h2>Configure</h2>
<p>
The installer writes a config file under your user config directory. The web server defaults to
<code>127.0.0.1:8504</code> and stores Pi Web state in <code>~/.pi-web</code>.
</p>
<ul>
<li><code>PI_WEB_PORT</code> or <code>PORT</code>: web server port.</li>
<li><code>PI_WEB_HOST</code>: web server bind host. Keep <code>127.0.0.1</code> unless you know why not.</li>
<li><code>PI_WEB_DATA_DIR</code>: data directory, default <code>~/.pi-web</code>.</li>
<li><code>PI_WEB_SESSIOND_SOCKET</code>: Unix socket path for daemon communication.</li>
</ul>
</section>
<section id="uninstall">
<h2>Uninstall</h2>
<p>Remove the user services, then remove the npm package if you installed it globally.</p>
<div class="code-card">
<div class="copy-row">
<strong>Uninstall</strong>
<button class="copy-button" data-copy="#uninstall-commands">Copy</button>
</div>
<pre id="uninstall-commands"><code><span class="prompt">$</span> pi-web uninstall
<span class="prompt">$</span> npm uninstall -g @jmfederico/pi-web</code></pre>
</div>
</section>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<span>Pi Web docs</span>
<div class="footer-links">
<a href="./">Home</a>
<a href="faq.html">FAQ</a>
<a href="https://github.com/jmfederico/pi-web">GitHub</a>
</div>
</div>
</footer>
<script src="site.js"></script>
</body>
</html>
+21
View File
@@ -0,0 +1,21 @@
const copyButtons = document.querySelectorAll("[data-copy]");
for (const button of copyButtons) {
button.addEventListener("click", async () => {
const targetSelector = button.getAttribute("data-copy");
const target = targetSelector === null ? null : document.querySelector(targetSelector);
const text = target?.textContent?.replace(/^\s*\$ /gm, "").trim();
if (!text) return;
try {
await navigator.clipboard.writeText(text);
const original = button.textContent;
button.textContent = "Copied";
window.setTimeout(() => {
button.textContent = original;
}, 1400);
} catch {
button.textContent = "Select code";
}
});
}
+646
View File
@@ -0,0 +1,646 @@
:root {
color-scheme: dark;
--bg: #070912;
--bg-soft: #0b0f1d;
--panel: #101527;
--panel-strong: #151b31;
--line: #26304f;
--line-bright: #3d4a78;
--text: #f7f4ff;
--muted: #aaa4bd;
--muted-2: #817a99;
--brand: #7c3cff;
--brand-2: #00f0d8;
--brand-3: #ffb000;
--danger: #ff4f7b;
--radius: 0;
font-family:
"IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
min-height: 100vh;
background: linear-gradient(180deg, #070912 0%, #080b15 44%, #0a0d18 100%);
color: var(--text);
}
body::after {
pointer-events: none;
content: "";
position: fixed;
top: 0;
right: 0;
left: 0;
height: 3px;
background: linear-gradient(90deg, var(--brand), var(--brand-2), var(--brand-3));
}
a {
color: inherit;
text-decoration: none;
}
a:hover {
color: var(--brand-2);
}
img,
video {
display: block;
max-width: 100%;
}
.container {
width: min(1120px, calc(100% - 40px));
margin: 0 auto;
}
.site-header {
position: sticky;
top: 0;
z-index: 10;
border-bottom: 1px solid var(--line);
background: rgba(7, 9, 18, 0.94);
backdrop-filter: blur(10px);
}
.nav {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
min-height: 72px;
}
.brand {
display: inline-flex;
align-items: center;
gap: 12px;
color: var(--text);
font-size: 1.05rem;
font-family: "IBM Plex Mono", "SFMono-Regular", Consolas, "Liberation Mono", monospace;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.brand::before {
content: "";
width: 18px;
height: 3px;
background: var(--brand-2);
}
.nav-links {
display: flex;
align-items: center;
gap: 6px;
color: var(--muted);
font-size: 0.95rem;
}
.nav-links a {
padding: 10px 12px;
border: 1px solid transparent;
}
.nav-links a[aria-current="page"],
.nav-links a:hover {
border-color: var(--line-bright);
background: var(--panel);
color: var(--text);
}
.button,
.copy-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 10px;
border: 1px solid var(--line-bright);
border-radius: var(--radius);
background: var(--panel);
color: var(--text);
font: inherit;
font-family: "IBM Plex Mono", "SFMono-Regular", Consolas, "Liberation Mono", monospace;
font-weight: 600;
cursor: pointer;
transition: transform 140ms ease, border-color 140ms ease, background 140ms ease, color 140ms ease;
}
.button {
padding: 13px 18px;
}
.button.primary {
border-color: var(--brand-2);
background: var(--brand-2);
color: #02050a;
box-shadow: 7px 7px 0 var(--brand);
}
.button:hover,
.copy-button:hover {
transform: translate(-1px, -1px);
border-color: var(--brand-2);
background: var(--panel-strong);
color: var(--text);
}
.button.primary:hover {
border-color: var(--brand-3);
background: var(--brand-3);
color: #02050a;
}
.hero {
position: relative;
overflow: hidden;
padding: 84px 0 44px;
}
.hero-grid {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(340px, 0.82fr);
gap: 44px;
align-items: center;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 10px;
margin: 0 0 18px;
padding: 8px 12px;
border: 1px solid var(--line);
background: var(--panel);
color: var(--muted);
font-family: "IBM Plex Mono", "SFMono-Regular", Consolas, "Liberation Mono", monospace;
font-size: 0.86rem;
}
.pulse {
width: 9px;
height: 9px;
background: var(--brand-2);
box-shadow: 0 0 0 5px rgba(0, 240, 216, 0.12);
}
h1,
h2,
h3 {
margin: 0;
letter-spacing: -0.055em;
}
h1 {
max-width: 760px;
font-size: clamp(3.2rem, 8vw, 6.8rem);
line-height: 0.9;
}
.gradient-text {
background: linear-gradient(90deg, #ffffff 0%, var(--brand-2) 48%, var(--brand-3) 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.hero-lede {
max-width: 660px;
margin: 24px 0 0;
color: var(--muted);
font-size: clamp(1.12rem, 2vw, 1.36rem);
line-height: 1.62;
}
.hero-actions,
.doc-actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 30px;
}
.hero-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-top: 36px;
}
.stat,
.card,
.install-card,
.doc-card,
.faq-item,
.callout,
.toc,
.code-card,
.demo-frame {
border: 1px solid var(--line);
background: var(--panel);
}
.stat {
padding: 18px;
border-top: 3px solid var(--brand);
}
.stat strong {
display: block;
font-size: 1.4rem;
}
.stat span {
color: var(--muted-2);
font-size: 0.9rem;
}
.terminal {
overflow: hidden;
border: 1px solid var(--line-bright);
border-top: 4px solid var(--brand-2);
border-radius: var(--radius);
background: #050710;
}
.terminal-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 15px 16px;
border-bottom: 1px solid var(--line);
background: #0c1020;
color: var(--muted-2);
font-size: 0.86rem;
}
.dots {
display: flex;
gap: 7px;
}
.dots span {
width: 10px;
height: 10px;
background: var(--brand-2);
}
.dots span:first-child {
background: var(--danger);
}
.dots span:nth-child(2) {
background: var(--brand-3);
}
pre,
code {
font-family: "IBM Plex Mono", "SFMono-Regular", Consolas, "Liberation Mono", monospace;
}
pre {
margin: 0;
overflow-x: auto;
padding: 22px;
color: #e9e5ff;
font-size: 0.96rem;
line-height: 1.7;
}
code .prompt,
.prompt {
color: var(--brand-2);
}
code .comment,
.comment {
color: #928cab;
}
.quick-install {
margin-top: 18px;
}
.copy-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 14px 18px 0;
}
.copy-row strong {
font-size: 0.95rem;
}
.copy-button {
padding: 8px 11px;
font-size: 0.82rem;
}
.section {
padding: 74px 0;
}
.section.compact {
padding-top: 40px;
}
.section-head {
max-width: 740px;
margin-bottom: 28px;
}
.section h2,
.page-hero h1 {
font-size: clamp(2.35rem, 5vw, 4.4rem);
line-height: 0.95;
}
.section p,
.page-hero p,
.doc-content p,
.doc-content li,
.faq-item p,
.faq-item li {
color: var(--muted);
line-height: 1.75;
}
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 18px;
}
.card,
.install-card,
.doc-card,
.faq-item,
.callout,
.toc,
.code-card {
border-radius: var(--radius);
}
.card,
.doc-card,
.faq-item,
.callout,
.toc {
padding: 24px;
}
.card:hover,
.doc-card:hover {
border-color: var(--line-bright);
}
.card-icon {
display: grid;
width: 46px;
height: 46px;
margin-bottom: 20px;
place-items: center;
border: 1px solid var(--line-bright);
border-left: 4px solid var(--brand-2);
background: var(--panel-strong);
color: var(--brand-2);
font-size: 1.3rem;
}
.card h3,
.doc-card h3,
.faq-item h2,
.faq-item h3 {
margin-bottom: 10px;
font-size: 1.24rem;
letter-spacing: -0.035em;
}
.demo-frame {
overflow: hidden;
border-radius: var(--radius);
border-color: var(--line-bright);
}
.demo-caption {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 15px 18px;
border-bottom: 1px solid var(--line);
background: #0c1020;
color: var(--muted);
}
.install-panel {
display: grid;
grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
gap: 22px;
align-items: stretch;
}
.install-card {
padding: 28px;
border-left: 4px solid var(--brand-2);
}
.install-card ol {
margin: 20px 0 0;
padding-left: 20px;
}
.install-card li {
margin-bottom: 14px;
color: var(--muted);
line-height: 1.65;
}
.doc-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 18px;
}
.doc-card {
min-height: 190px;
}
.doc-card a {
color: var(--brand-2);
font-weight: 760;
}
.page-hero {
padding: 76px 0 26px;
}
.doc-layout {
display: grid;
grid-template-columns: 260px minmax(0, 1fr);
gap: 28px;
align-items: start;
}
.toc {
position: sticky;
top: 96px;
box-shadow: none;
}
.toc strong {
display: block;
margin-bottom: 12px;
}
.toc a {
display: block;
padding: 9px 0;
border-left: 2px solid transparent;
color: var(--muted);
font-size: 0.95rem;
}
.toc a:hover {
border-left-color: var(--brand-2);
padding-left: 8px;
}
.doc-content {
min-width: 0;
}
.doc-content section {
scroll-margin-top: 100px;
margin-bottom: 26px;
}
.doc-content h2 {
margin: 28px 0 12px;
font-size: clamp(1.75rem, 3vw, 2.7rem);
}
.doc-content h3 {
margin: 22px 0 8px;
font-size: 1.25rem;
}
.code-card {
overflow: hidden;
margin: 16px 0;
background: #050710;
}
.callout {
margin: 18px 0;
border-left: 4px solid var(--brand-2);
}
.callout.warning {
border-color: var(--brand-3);
background: #16140d;
}
.callout.danger {
border-color: var(--danger);
background: #170d15;
}
.faq-list {
display: grid;
gap: 18px;
}
.faq-item ul,
.doc-content ul {
padding-left: 21px;
}
.kbd {
display: inline-block;
padding: 2px 7px;
border: 1px solid var(--line-bright);
border-radius: var(--radius);
background: #050710;
color: var(--text);
font-size: 0.88em;
}
.site-footer {
padding: 44px 0 58px;
border-top: 1px solid var(--line);
color: var(--muted-2);
}
.footer-inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.footer-links {
display: flex;
gap: 14px;
}
@media (max-width: 920px) {
.hero-grid,
.install-panel,
.doc-layout {
grid-template-columns: 1fr;
}
.toc {
position: static;
}
.cards,
.hero-stats,
.doc-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.container {
width: min(100% - 28px, 1120px);
}
.nav {
align-items: flex-start;
flex-direction: column;
padding: 14px 0;
}
.nav-links {
flex-wrap: wrap;
}
.hero {
padding-top: 52px;
}
h1 {
font-size: clamp(3rem, 15vw, 4.2rem);
}
.footer-inner {
align-items: flex-start;
flex-direction: column;
}
}
+1 -1
View File
@@ -91,7 +91,7 @@
"bugs": {
"url": "https://github.com/jmfederico/pi-web/issues"
},
"homepage": "https://github.com/jmfederico/pi-web#readme",
"homepage": "https://jmfederico.github.io/pi-web/",
"packageManager": "[email protected]",
"peerDependencies": {
"@earendil-works/pi-coding-agent": ">=0.74.0 <1",
+83
View File
@@ -1,10 +1,93 @@
import { createReadStream } from "node:fs";
import { stat } from "node:fs/promises";
import type { IncomingMessage, ServerResponse } from "node:http";
import { extname, join, resolve, sep } from "node:path";
import type { Plugin } from "vite";
import { defineConfig } from "vite";
import { effectivePiWebConfig } from "./src/config";
const { config } = effectivePiWebConfig();
const apiPort = config.port ?? 8504;
const docsRoot = resolve("docs");
const docsPrefix = "/site";
const contentTypes: Record<string, string> = {
".css": "text/css; charset=utf-8",
".gif": "image/gif",
".html": "text/html; charset=utf-8",
".js": "text/javascript; charset=utf-8",
".md": "text/markdown; charset=utf-8",
".png": "image/png",
".svg": "image/svg+xml",
".webm": "video/webm",
};
type MiddlewareNext = (error?: unknown) => void;
async function serveDevDocs(request: IncomingMessage, response: ServerResponse, next: MiddlewareNext): Promise<void> {
const requestUrl = request.url;
if (requestUrl === undefined) {
next();
return;
}
const url = new URL(requestUrl, "http://localhost");
if (url.pathname === docsPrefix) {
response.statusCode = 302;
response.setHeader("Location", `${docsPrefix}/`);
response.end();
return;
}
if (!url.pathname.startsWith(`${docsPrefix}/`)) {
next();
return;
}
const relativePath = decodeURIComponent(url.pathname.slice(docsPrefix.length + 1)) || "index.html";
const requestedPath = relativePath.endsWith("/") ? join(relativePath, "index.html") : relativePath;
const filePath = resolve(docsRoot, requestedPath);
if (filePath !== docsRoot && !filePath.startsWith(`${docsRoot}${sep}`)) {
response.statusCode = 403;
response.end("Forbidden");
return;
}
try {
const fileStat = await stat(filePath);
if (!fileStat.isFile()) {
response.statusCode = 404;
response.end("Not found");
return;
}
response.statusCode = 200;
response.setHeader("Content-Type", contentTypes[extname(filePath)] ?? "application/octet-stream");
response.setHeader("Cache-Control", "no-store");
createReadStream(filePath).pipe(response);
} catch (error) {
const code = error instanceof Error && "code" in error ? error.code : undefined;
if (code === "ENOENT") {
response.statusCode = 404;
response.end("Not found");
return;
}
next(error);
}
}
function devDocsPlugin(): Plugin {
return {
name: "pi-web-dev-docs",
apply: "serve",
configureServer(server) {
server.middlewares.use((request, response, next) => {
void serveDevDocs(request, response, next);
});
},
};
}
export default defineConfig({
plugins: [devDocsPlugin()],
root: "src/client",
build: {
outDir: "../../dist/client",