Archived
docs: add static website and docs
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@jmfederico/pi-web": patch
|
||||
---
|
||||
|
||||
Add a static Pi Web website with installation docs, troubleshooting FAQ, and GitHub Pages deployment.
|
||||
@@ -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
|
||||
@@ -6,6 +6,8 @@
|
||||
[](LICENSE)
|
||||
[](https://github.com/earendil-works/pi/tree/main/packages/coding-agent)
|
||||
|
||||
Website: <https://jmfederico.github.io/pi-web/>
|
||||
|
||||

|
||||
|
||||
**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
|
||||
|
||||
+202
@@ -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
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user