Merge remote-tracking branch 'origin/main' into investigate/issue-12-session-dir

# Conflicts:
#	src/client/src/api.ts
#	src/client/src/api/clients.ts
#	src/client/src/api/federatedRouteContract.test.ts
#	src/server/sessions/piSessionService.ts
#	src/server/sessions/sessionRoutes.ts
This commit is contained in:
Federico Jaramillo Martinez
2026-06-10 20:43:10 +02:00
133 changed files with 4848 additions and 968 deletions
+23 -17
View File
@@ -28,23 +28,28 @@
<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="/remote-first.html">Remote-first</a>
<a href="/install.html">Install</a>
<a href="/plugins.html">Plugins</a>
<a href="/faq.html">FAQ</a>
<a class="github-link" href="https://github.com/jmfederico/pi-web" aria-label="PI WEB on GitHub">
<svg class="github-icon" viewBox="0 0 16 16" aria-hidden="true">
<path
fill="currentColor"
d="M8 0C3.58 0 0 3.67 0 8.2c0 3.63 2.29 6.7 5.47 7.79.4.08.55-.18.55-.4 0-.2-.01-.85-.01-1.55-2.01.38-2.53-.5-2.69-.96-.09-.24-.48-.96-.82-1.16-.28-.16-.68-.56-.01-.57.63-.01 1.08.59 1.23.84.72 1.24 1.87.89 2.33.68.07-.53.28-.89.51-1.09-1.78-.21-3.64-.91-3.64-4.04 0-.89.31-1.62.82-2.2-.08-.2-.36-1.03.08-2.16 0 0 .67-.22 2.2.84A7.4 7.4 0 0 1 8 3.94c.68 0 1.36.09 2 .28 1.53-1.06 2.2-.84 2.2-.84.44 1.13.16 1.96.08 2.16.51.58.82 1.31.82 2.2 0 3.14-1.87 3.83-3.65 4.04.29.26.54.76.54 1.53 0 1.1-.01 1.99-.01 2.27 0 .22.15.49.55.4A8.12 8.12 0 0 0 16 8.2C16 3.67 12.42 0 8 0Z"
/>
</svg>
<span>GitHub</span>
</a>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle light and dark theme">
<span data-theme-icon aria-hidden="true"></span>
<span data-theme-label>Theme</span>
</button>
<div class="nav-pages">
<a href="/remote-first.html">Remote-first</a>
<a href="/machines.html">Fleet</a>
<a href="/install.html">Install</a>
<a href="/plugins.html">Plugins</a>
<a href="/faq.html">FAQ</a>
</div>
<div class="nav-actions">
<a class="github-link" href="https://github.com/jmfederico/pi-web" aria-label="PI WEB on GitHub">
<svg class="github-icon" viewBox="0 0 16 16" aria-hidden="true">
<path
fill="currentColor"
d="M8 0C3.58 0 0 3.67 0 8.2c0 3.63 2.29 6.7 5.47 7.79.4.08.55-.18.55-.4 0-.2-.01-.85-.01-1.55-2.01.38-2.53-.5-2.69-.96-.09-.24-.48-.96-.82-1.16-.28-.16-.68-.56-.01-.57.63-.01 1.08.59 1.23.84.72 1.24 1.87.89 2.33.68.07-.53.28-.89.51-1.09-1.78-.21-3.64-.91-3.64-4.04 0-.89.31-1.62.82-2.2-.08-.2-.36-1.03.08-2.16 0 0 .67-.22 2.2.84A7.4 7.4 0 0 1 8 3.94c.68 0 1.36.09 2 .28 1.53-1.06 2.2-.84 2.2-.84.44 1.13.16 1.96.08 2.16.51.58.82 1.31.82 2.2 0 3.14-1.87 3.83-3.65 4.04.29.26.54.76.54 1.53 0 1.1-.01 1.99-.01 2.27 0 .22.15.49.55.4A8.12 8.12 0 0 0 16 8.2C16 3.67 12.42 0 8 0Z"
/>
</svg>
<span>GitHub</span>
</a>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle light and dark theme">
<span data-theme-icon aria-hidden="true"></span>
<span data-theme-label>Theme</span>
</button>
</div>
</div>
</nav>
</header>
@@ -75,6 +80,7 @@
<div class="container footer-inner">
<span>PI WEB · remote control for persistent Pi Coding Agent sessions.</span>
<div class="footer-links">
<a href="/machines.html">Fleet</a>
<a href="/install.html">Install</a>
<a href="/plugins.html">Plugins</a>
<a href="/faq.html">FAQ</a>
+41 -17
View File
@@ -27,23 +27,28 @@
<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="remote-first.html">Remote-first</a>
<a href="install.html">Install</a>
<a href="plugins.html">Plugins</a>
<a href="faq.html" aria-current="page">FAQ</a>
<a class="github-link" href="https://github.com/jmfederico/pi-web" aria-label="PI WEB on GitHub">
<svg class="github-icon" viewBox="0 0 16 16" aria-hidden="true">
<path
fill="currentColor"
d="M8 0C3.58 0 0 3.67 0 8.2c0 3.63 2.29 6.7 5.47 7.79.4.08.55-.18.55-.4 0-.2-.01-.85-.01-1.55-2.01.38-2.53-.5-2.69-.96-.09-.24-.48-.96-.82-1.16-.28-.16-.68-.56-.01-.57.63-.01 1.08.59 1.23.84.72 1.24 1.87.89 2.33.68.07-.53.28-.89.51-1.09-1.78-.21-3.64-.91-3.64-4.04 0-.89.31-1.62.82-2.2-.08-.2-.36-1.03.08-2.16 0 0 .67-.22 2.2.84A7.4 7.4 0 0 1 8 3.94c.68 0 1.36.09 2 .28 1.53-1.06 2.2-.84 2.2-.84.44 1.13.16 1.96.08 2.16.51.58.82 1.31.82 2.2 0 3.14-1.87 3.83-3.65 4.04.29.26.54.76.54 1.53 0 1.1-.01 1.99-.01 2.27 0 .22.15.49.55.4A8.12 8.12 0 0 0 16 8.2C16 3.67 12.42 0 8 0Z"
/>
</svg>
<span>GitHub</span>
</a>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle light and dark theme">
<span data-theme-icon aria-hidden="true"></span>
<span data-theme-label>Theme</span>
</button>
<div class="nav-pages">
<a href="remote-first.html">Remote-first</a>
<a href="machines.html">Fleet</a>
<a href="install.html">Install</a>
<a href="plugins.html">Plugins</a>
<a href="faq.html" aria-current="page">FAQ</a>
</div>
<div class="nav-actions">
<a class="github-link" href="https://github.com/jmfederico/pi-web" aria-label="PI WEB on GitHub">
<svg class="github-icon" viewBox="0 0 16 16" aria-hidden="true">
<path
fill="currentColor"
d="M8 0C3.58 0 0 3.67 0 8.2c0 3.63 2.29 6.7 5.47 7.79.4.08.55-.18.55-.4 0-.2-.01-.85-.01-1.55-2.01.38-2.53-.5-2.69-.96-.09-.24-.48-.96-.82-1.16-.28-.16-.68-.56-.01-.57.63-.01 1.08.59 1.23.84.72 1.24 1.87.89 2.33.68.07-.53.28-.89.51-1.09-1.78-.21-3.64-.91-3.64-4.04 0-.89.31-1.62.82-2.2-.08-.2-.36-1.03.08-2.16 0 0 .67-.22 2.2.84A7.4 7.4 0 0 1 8 3.94c.68 0 1.36.09 2 .28 1.53-1.06 2.2-.84 2.2-.84.44 1.13.16 1.96.08 2.16.51.58.82 1.31.82 2.2 0 3.14-1.87 3.83-3.65 4.04.29.26.54.76.54 1.53 0 1.1-.01 1.99-.01 2.27 0 .22.15.49.55.4A8.12 8.12 0 0 0 16 8.2C16 3.67 12.42 0 8 0Z"
/>
</svg>
<span>GitHub</span>
</a>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle light and dark theme">
<span data-theme-icon aria-hidden="true"></span>
<span data-theme-label>Theme</span>
</button>
</div>
</div>
</nav>
</header>
@@ -71,6 +76,7 @@
<a href="#systemd-not-found">User service manager is unavailable</a>
<a href="#cannot-open">I cannot open the web UI</a>
<a href="#public-internet">Can I expose this publicly?</a>
<a href="#remote-machines">How do remote machines work?</a>
<a href="#laptop-or-server">Laptop or server?</a>
<a href="#plugins">Can I use local plugins?</a>
<a href="#sessions-stop">Sessions stop unexpectedly</a>
@@ -189,6 +195,23 @@
</p>
</article>
<article id="remote-machines" class="faq-item">
<h2>How do I connect another PI WEB machine?</h2>
<p>
Install and run PI WEB on the target machine, make its PI WEB URL reachable from the gateway through a
trusted path, then open the gateway and choose <strong>Actions → Add Machine</strong>. The browser keeps
talking to the gateway while projects, sessions, files, and terminals run on the selected remote runtime.
</p>
<ul>
<li>Use NetBird, Tailscale, WireGuard, private LAN, SSH tunnel, or an authenticated reverse proxy.</li>
<li>Remote Pi auth, OAuth state, sessions, terminals, repositories, and plugins stay on the target machine.</li>
<li>API-key provider configuration can be proxied, but OAuth login should be completed by opening the remote PI WEB directly.</li>
<li>Remote plugins are trusted browser code and only appear while that machine is selected.</li>
<li>If the remote reports offline, check that the gateway server can reach the remote URL and try <strong>Actions → Refresh Selected Machine</strong>.</li>
</ul>
<p><a href="machines.html">Read the fleet guide →</a></p>
</article>
<article id="laptop-or-server" class="faq-item">
<h2>Should I run PI WEB on my laptop or a server?</h2>
<p>
@@ -251,6 +274,7 @@
<div class="footer-links">
<a href="./">Home</a>
<a href="remote-first.html">Remote-first</a>
<a href="machines.html">Fleet</a>
<a href="install.html">Install</a>
<a href="plugins.html">Plugins</a>
<a href="https://github.com/jmfederico/pi-web/issues">Issues</a>
+23 -17
View File
@@ -34,23 +34,28 @@
<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="remote-first.html">Remote-first</a>
<a href="install.html">Install</a>
<a href="plugins.html">Plugins</a>
<a href="faq.html">FAQ</a>
<a class="github-link" href="https://github.com/jmfederico/pi-web" aria-label="PI WEB on GitHub">
<svg class="github-icon" viewBox="0 0 16 16" aria-hidden="true">
<path
fill="currentColor"
d="M8 0C3.58 0 0 3.67 0 8.2c0 3.63 2.29 6.7 5.47 7.79.4.08.55-.18.55-.4 0-.2-.01-.85-.01-1.55-2.01.38-2.53-.5-2.69-.96-.09-.24-.48-.96-.82-1.16-.28-.16-.68-.56-.01-.57.63-.01 1.08.59 1.23.84.72 1.24 1.87.89 2.33.68.07-.53.28-.89.51-1.09-1.78-.21-3.64-.91-3.64-4.04 0-.89.31-1.62.82-2.2-.08-.2-.36-1.03.08-2.16 0 0 .67-.22 2.2.84A7.4 7.4 0 0 1 8 3.94c.68 0 1.36.09 2 .28 1.53-1.06 2.2-.84 2.2-.84.44 1.13.16 1.96.08 2.16.51.58.82 1.31.82 2.2 0 3.14-1.87 3.83-3.65 4.04.29.26.54.76.54 1.53 0 1.1-.01 1.99-.01 2.27 0 .22.15.49.55.4A8.12 8.12 0 0 0 16 8.2C16 3.67 12.42 0 8 0Z"
/>
</svg>
<span>GitHub</span>
</a>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle light and dark theme">
<span data-theme-icon aria-hidden="true"></span>
<span data-theme-label>Theme</span>
</button>
<div class="nav-pages">
<a href="remote-first.html">Remote-first</a>
<a href="machines.html">Fleet</a>
<a href="install.html">Install</a>
<a href="plugins.html">Plugins</a>
<a href="faq.html">FAQ</a>
</div>
<div class="nav-actions">
<a class="github-link" href="https://github.com/jmfederico/pi-web" aria-label="PI WEB on GitHub">
<svg class="github-icon" viewBox="0 0 16 16" aria-hidden="true">
<path
fill="currentColor"
d="M8 0C3.58 0 0 3.67 0 8.2c0 3.63 2.29 6.7 5.47 7.79.4.08.55-.18.55-.4 0-.2-.01-.85-.01-1.55-2.01.38-2.53-.5-2.69-.96-.09-.24-.48-.96-.82-1.16-.28-.16-.68-.56-.01-.57.63-.01 1.08.59 1.23.84.72 1.24 1.87.89 2.33.68.07-.53.28-.89.51-1.09-1.78-.21-3.64-.91-3.64-4.04 0-.89.31-1.62.82-2.2-.08-.2-.36-1.03.08-2.16 0 0 .67-.22 2.2.84A7.4 7.4 0 0 1 8 3.94c.68 0 1.36.09 2 .28 1.53-1.06 2.2-.84 2.2-.84.44 1.13.16 1.96.08 2.16.51.58.82 1.31.82 2.2 0 3.14-1.87 3.83-3.65 4.04.29.26.54.76.54 1.53 0 1.1-.01 1.99-.01 2.27 0 .22.15.49.55.4A8.12 8.12 0 0 0 16 8.2C16 3.67 12.42 0 8 0Z"
/>
</svg>
<span>GitHub</span>
</a>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle light and dark theme">
<span data-theme-icon aria-hidden="true"></span>
<span data-theme-label>Theme</span>
</button>
</div>
</div>
</nav>
</header>
@@ -298,6 +303,7 @@
<span>PI WEB · remote control for persistent Pi Coding Agent sessions.</span>
<div class="footer-links">
<a href="remote-first.html">Remote-first</a>
<a href="machines.html">Fleet</a>
<a href="install.html">Install</a>
<a href="plugins.html">Plugins</a>
<a href="faq.html">FAQ</a>
+42 -18
View File
@@ -27,23 +27,28 @@
<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="remote-first.html">Remote-first</a>
<a href="install.html" aria-current="page">Install</a>
<a href="plugins.html">Plugins</a>
<a href="faq.html">FAQ</a>
<a class="github-link" href="https://github.com/jmfederico/pi-web" aria-label="PI WEB on GitHub">
<svg class="github-icon" viewBox="0 0 16 16" aria-hidden="true">
<path
fill="currentColor"
d="M8 0C3.58 0 0 3.67 0 8.2c0 3.63 2.29 6.7 5.47 7.79.4.08.55-.18.55-.4 0-.2-.01-.85-.01-1.55-2.01.38-2.53-.5-2.69-.96-.09-.24-.48-.96-.82-1.16-.28-.16-.68-.56-.01-.57.63-.01 1.08.59 1.23.84.72 1.24 1.87.89 2.33.68.07-.53.28-.89.51-1.09-1.78-.21-3.64-.91-3.64-4.04 0-.89.31-1.62.82-2.2-.08-.2-.36-1.03.08-2.16 0 0 .67-.22 2.2.84A7.4 7.4 0 0 1 8 3.94c.68 0 1.36.09 2 .28 1.53-1.06 2.2-.84 2.2-.84.44 1.13.16 1.96.08 2.16.51.58.82 1.31.82 2.2 0 3.14-1.87 3.83-3.65 4.04.29.26.54.76.54 1.53 0 1.1-.01 1.99-.01 2.27 0 .22.15.49.55.4A8.12 8.12 0 0 0 16 8.2C16 3.67 12.42 0 8 0Z"
/>
</svg>
<span>GitHub</span>
</a>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle light and dark theme">
<span data-theme-icon aria-hidden="true"></span>
<span data-theme-label>Theme</span>
</button>
<div class="nav-pages">
<a href="remote-first.html">Remote-first</a>
<a href="machines.html">Fleet</a>
<a href="install.html" aria-current="page">Install</a>
<a href="plugins.html">Plugins</a>
<a href="faq.html">FAQ</a>
</div>
<div class="nav-actions">
<a class="github-link" href="https://github.com/jmfederico/pi-web" aria-label="PI WEB on GitHub">
<svg class="github-icon" viewBox="0 0 16 16" aria-hidden="true">
<path
fill="currentColor"
d="M8 0C3.58 0 0 3.67 0 8.2c0 3.63 2.29 6.7 5.47 7.79.4.08.55-.18.55-.4 0-.2-.01-.85-.01-1.55-2.01.38-2.53-.5-2.69-.96-.09-.24-.48-.96-.82-1.16-.28-.16-.68-.56-.01-.57.63-.01 1.08.59 1.23.84.72 1.24 1.87.89 2.33.68.07-.53.28-.89.51-1.09-1.78-.21-3.64-.91-3.64-4.04 0-.89.31-1.62.82-2.2-.08-.2-.36-1.03.08-2.16 0 0 .67-.22 2.2.84A7.4 7.4 0 0 1 8 3.94c.68 0 1.36.09 2 .28 1.53-1.06 2.2-.84 2.2-.84.44 1.13.16 1.96.08 2.16.51.58.82 1.31.82 2.2 0 3.14-1.87 3.83-3.65 4.04.29.26.54.76.54 1.53 0 1.1-.01 1.99-.01 2.27 0 .22.15.49.55.4A8.12 8.12 0 0 0 16 8.2C16 3.67 12.42 0 8 0Z"
/>
</svg>
<span>GitHub</span>
</a>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle light and dark theme">
<span data-theme-icon aria-hidden="true"></span>
<span data-theme-label>Theme</span>
</button>
</div>
</div>
</nav>
</header>
@@ -70,6 +75,7 @@
<a href="#pi-package">Install through Pi</a>
<a href="#manual-run">WSL / manual run</a>
<a href="#remote-access">Remote access</a>
<a href="#federated-machines">Federated machines</a>
<a href="#manage-services">Manage services</a>
<a href="#configure">Configure</a>
<a href="#uninstall">Uninstall</a>
@@ -189,11 +195,28 @@
<div class="callout danger">
PI WEB is designed for trusted users and trusted server paths. Do not expose it directly to the public
internet. If you use a VPN or private network, bind PI WEB to the server's VPN/private IP, such as a
Tailscale/WireGuard/LAN address, and make sure that network policy limits access. Avoid
NetBird/Tailscale/WireGuard/LAN address, and make sure that network policy limits access. Avoid
<code>0.0.0.0</code> unless a firewall, VPN, or authenticated reverse proxy strictly controls the port.
</div>
</section>
<section id="federated-machines">
<h2>Federated machines</h2>
<p>
To use one PI WEB instance as a gateway to others, install and run PI WEB on each target machine first.
Make each target reachable from the gateway through a trusted path, then open the gateway and use
<strong>Actions → Add Machine</strong> with the target PI WEB base URL.
</p>
<p>
The remote URL can be reachable through NetBird, Tailscale, WireGuard, LAN, an SSH tunnel, or an authenticated reverse proxy. The
browser keeps using the gateway origin while projects, sessions, files, and terminals run on the selected
remote runtime.
</p>
<div class="doc-actions">
<a class="button" href="machines.html">Read the fleet guide</a>
</div>
</section>
<section id="manage-services">
<h2>Manage services</h2>
<p><code>pi-web version</code> compares the installed package version with the versions reported by the running Web/UI and session daemon services.</p>
@@ -291,6 +314,7 @@
<div class="footer-links">
<a href="./">Home</a>
<a href="remote-first.html">Remote-first</a>
<a href="machines.html">Fleet</a>
<a href="plugins.html">Plugins</a>
<a href="faq.html">FAQ</a>
<a href="https://github.com/jmfederico/pi-web">GitHub</a>
+284
View File
@@ -0,0 +1,284 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>PI WEB fleet</title>
<meta
name="description"
content="Connect trusted PI WEB runtimes through machine federation so one browser control plane can supervise local and remote projects, sessions, files, git state, terminals, and plugins."
/>
<meta property="og:title" content="PI WEB fleet" />
<meta property="og:image" content="assets/pi-web-banner.png" />
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg" />
<script>
(() => {
const theme = window.localStorage.getItem("pi-web-theme");
if (theme === "light" || theme === "dark") document.documentElement.dataset.theme = theme;
})();
</script>
<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">
<div class="nav-pages">
<a href="remote-first.html">Remote-first</a>
<a href="machines.html" aria-current="page">Fleet</a>
<a href="install.html">Install</a>
<a href="plugins.html">Plugins</a>
<a href="faq.html">FAQ</a>
</div>
<div class="nav-actions">
<a class="github-link" href="https://github.com/jmfederico/pi-web" aria-label="PI WEB on GitHub">
<svg class="github-icon" viewBox="0 0 16 16" aria-hidden="true">
<path
fill="currentColor"
d="M8 0C3.58 0 0 3.67 0 8.2c0 3.63 2.29 6.7 5.47 7.79.4.08.55-.18.55-.4 0-.2-.01-.85-.01-1.55-2.01.38-2.53-.5-2.69-.96-.09-.24-.48-.96-.82-1.16-.28-.16-.68-.56-.01-.57.63-.01 1.08.59 1.23.84.72 1.24 1.87.89 2.33.68.07-.53.28-.89.51-1.09-1.78-.21-3.64-.91-3.64-4.04 0-.89.31-1.62.82-2.2-.08-.2-.36-1.03.08-2.16 0 0 .67-.22 2.2.84A7.4 7.4 0 0 1 8 3.94c.68 0 1.36.09 2 .28 1.53-1.06 2.2-.84 2.2-.84.44 1.13.16 1.96.08 2.16.51.58.82 1.31.82 2.2 0 3.14-1.87 3.83-3.65 4.04.29.26.54.76.54 1.53 0 1.1-.01 1.99-.01 2.27 0 .22.15.49.55.4A8.12 8.12 0 0 0 16 8.2C16 3.67 12.42 0 8 0Z"
/>
</svg>
<span>GitHub</span>
</a>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle light and dark theme">
<span data-theme-icon aria-hidden="true"></span>
<span data-theme-label>Theme</span>
</button>
</div>
</div>
</nav>
</header>
<main>
<section class="page-hero">
<div class="container">
<p class="eyebrow"><span class="pulse"></span> PI WEB fleet</p>
<h1>Connect trusted PI WEB runtimes when one machine is not enough.</h1>
<p>
Most PI WEB setups only need one runtime. When you do have more than one, machine federation lets the PI WEB
instance you opened act as a gateway to other trusted runtimes while each machine keeps its own repositories,
credentials, sessions, and plugins.
</p>
</div>
</section>
<section class="section compact">
<div class="container doc-layout">
<aside class="toc" aria-label="Machines page contents">
<strong>On this page</strong>
<a href="#what-it-is">What Fleet does</a>
<a href="#when-to-use">When to use it</a>
<a href="#prepare-machines">Set up another runtime</a>
<a href="#add-machine">Add a machine</a>
<a href="#what-is-proxied">What works remotely</a>
<a href="#auth-credentials">Credentials</a>
<a href="#plugins">Plugins</a>
<a href="#trust-model">Trust boundaries</a>
<a href="#storage-troubleshooting">Troubleshooting</a>
</aside>
<div class="doc-content">
<section id="what-it-is">
<h2>What Fleet does</h2>
<p>
A machine is a PI WEB runtime endpoint. The local machine is synthesized automatically. Remote machines
are opt-in PI WEB runtimes that you register with a base URL and, optionally, a bearer token.
</p>
<p>
After registration, the browser keeps talking to the current PI WEB origin. The gateway contacts the
selected remote PI WEB server and routes that machine's projects, workspaces, sessions, files, git state,
activity, and terminals to the browser UI.
</p>
<div class="code-card">
<div class="copy-row">
<strong>Fleet shape</strong>
<button class="copy-button" data-copy="#federated-shape">Copy</button>
</div>
<pre id="federated-shape"><code>Browser
PI WEB gateway you opened
├─ [ Local runtime ]
├─ [ Remote PI WEB runtime A ]
│ ↓ selected machine
│ projects, workspaces, sessions, terminals, plugins
└─ [ Remote PI WEB runtime B ]</code></pre>
</div>
</section>
<section id="when-to-use">
<h2>When to use it</h2>
<p>
Federation is useful when you have more than one place where agents should work, but you want one stable
browser entrypoint.
</p>
<div class="cards">
<article class="card">
<div class="card-icon"></div>
<h3>Many dev boxes</h3>
<p>Register a workstation, home server, cloud VM, or client-specific host without moving repositories.</p>
</article>
<article class="card">
<div class="card-icon"></div>
<h3>One gateway</h3>
<p>Open one trusted PI WEB URL and switch machines instead of juggling browser tabs and tunnels.</p>
</article>
<article class="card">
<div class="card-icon"></div>
<h3>Local ownership</h3>
<p>Each target machine keeps its own Pi auth, sessions, worktrees, terminal state, and plugins.</p>
</article>
</div>
</section>
<section id="prepare-machines">
<h2>Set up another runtime</h2>
<p>
Install and run PI WEB on every machine you want to register. The remote URL must be reachable from the
gateway server, not just from your browser.
</p>
<div class="code-card">
<div class="copy-row">
<strong>Install on each target</strong>
<button class="copy-button" data-copy="#target-install">Copy</button>
</div>
<pre id="target-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</code></pre>
</div>
<p>
Prefer a private path such as NetBird, Tailscale, WireGuard, private LAN, SSH tunnel, or an authenticated reverse
proxy. If the remote is behind a path prefix, include that prefix in the machine URL, for example
<code>https://devbox.example.test/pi-web</code>.
</p>
<div class="callout danger">
Do not expose PI WEB directly to the public internet. Register machines only over trusted network paths
and only when you trust the endpoint.
</div>
</section>
<section id="add-machine">
<h2>Add a machine</h2>
<ol>
<li>Open the PI WEB instance you want to use as the gateway.</li>
<li>Open <strong>Actions → Add Machine</strong>.</li>
<li>Enter the remote PI WEB base URL, including <code>http://</code> or <code>https://</code>.</li>
<li>Accept the suggested name or enter a friendlier sidebar label.</li>
<li>Paste an optional bearer token if the remote endpoint requires one.</li>
</ol>
<p>
The Machines section appears when there is more than one machine. Select a machine, then add projects,
workspaces, and sessions on that selected machine.
</p>
<div class="callout">
Removing a remote machine only removes it from this gateway's registry. It does not stop the remote PI WEB
service or delete projects, workspaces, sessions, or credentials on the target machine.
</div>
</section>
<section id="what-is-proxied">
<h2>What works remotely</h2>
<p>
After you select a machine, the rest of the app works in that machine's scope. The gateway routes the
selected-machine work to the target PI WEB runtime.
</p>
<ul>
<li>Projects and workspaces.</li>
<li>Files, previews, git status, and diffs.</li>
<li>Pi sessions, transcripts, prompts, model controls, and commands.</li>
<li>Activity indicators and realtime updates.</li>
<li>Terminals and terminal command runs.</li>
<li>Remote plugins from the selected machine.</li>
</ul>
</section>
<section id="auth-credentials">
<h2>Credentials stay on the target machine</h2>
<p>
Model-provider credentials, Pi configuration, OAuth state, repositories, and active session runtimes stay
on the selected target machine. The gateway does not copy them into its own Pi configuration.
</p>
<ul>
<li>API-key provider configuration can be proxied through the gateway.</li>
<li>OAuth login should be completed by opening the remote PI WEB directly.</li>
<li>The optional machine bearer token is stored by the gateway and sent to the remote while proxying requests.</li>
</ul>
<p>
Use <strong>Actions → Open Selected Machine PI WEB</strong> when you need to authenticate directly on a
remote machine or inspect it outside the gateway.
</p>
</section>
<section id="plugins">
<h2>Plugins follow the selected machine</h2>
<p>
When you select a remote machine, PI WEB tries to load that machine's discovered plugins through the
gateway. Remote plugin actions, workspace panels, and workspace labels only appear while that machine is
selected, and documented file and terminal helpers run against that machine.
</p>
<p>
Remote plugins are still trusted browser-side code. Only federate machines whose PI WEB plugins you are
comfortable loading in the browser.
</p>
<p><a href="plugins.html#remote-machine-plugins">Read the remote machine plugin notes →</a></p>
</section>
<section id="trust-model">
<h2>Trust boundaries</h2>
<p>
Machine federation is for trusted users, trusted PI WEB servers, and trusted network paths. Adding a
machine gives the gateway permission to contact that URL and forward user-initiated PI WEB traffic to it.
</p>
<ul>
<li>Use private networking, an SSH tunnel, or an authenticated reverse proxy.</li>
<li>Do not include credentials in the machine URL itself.</li>
<li>Use bearer tokens or proxy authentication when a remote endpoint needs an extra gate.</li>
</ul>
</section>
<section id="storage-troubleshooting">
<h2>Troubleshooting</h2>
<p>
Remote machine records are stored on the gateway in <code>~/.pi-web/machines.json</code>. If
<code>PI_WEB_DATA_DIR</code> is set, they live under <code>$PI_WEB_DATA_DIR/machines.json</code>.
</p>
<ul>
<li>Use <strong>Actions → Refresh Selected Machine</strong> to re-check health.</li>
<li>Make sure the remote URL is reachable from the gateway server.</li>
<li>Open the remote PI WEB directly to verify it is running and to complete provider login flows.</li>
<li>Check gateway logs with <code>pi-web logs</code> for proxy timeouts or upstream errors.</li>
</ul>
<div class="doc-actions">
<a class="button primary" href="install.html#remote-access">Review remote access setup</a>
<a class="button" href="faq.html#remote-machines">Read remote machine FAQ</a>
</div>
</section>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<span>PI WEB fleet</span>
<div class="footer-links">
<a href="./">Home</a>
<a href="remote-first.html">Remote-first</a>
<a href="install.html">Install</a>
<a href="plugins.html">Plugins</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>
+43 -27
View File
@@ -27,23 +27,28 @@
<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="remote-first.html">Remote-first</a>
<a href="install.html">Install</a>
<a href="plugins.html" aria-current="page">Plugins</a>
<a href="faq.html">FAQ</a>
<a class="github-link" href="https://github.com/jmfederico/pi-web" aria-label="PI WEB on GitHub">
<svg class="github-icon" viewBox="0 0 16 16" aria-hidden="true">
<path
fill="currentColor"
d="M8 0C3.58 0 0 3.67 0 8.2c0 3.63 2.29 6.7 5.47 7.79.4.08.55-.18.55-.4 0-.2-.01-.85-.01-1.55-2.01.38-2.53-.5-2.69-.96-.09-.24-.48-.96-.82-1.16-.28-.16-.68-.56-.01-.57.63-.01 1.08.59 1.23.84.72 1.24 1.87.89 2.33.68.07-.53.28-.89.51-1.09-1.78-.21-3.64-.91-3.64-4.04 0-.89.31-1.62.82-2.2-.08-.2-.36-1.03.08-2.16 0 0 .67-.22 2.2.84A7.4 7.4 0 0 1 8 3.94c.68 0 1.36.09 2 .28 1.53-1.06 2.2-.84 2.2-.84.44 1.13.16 1.96.08 2.16.51.58.82 1.31.82 2.2 0 3.14-1.87 3.83-3.65 4.04.29.26.54.76.54 1.53 0 1.1-.01 1.99-.01 2.27 0 .22.15.49.55.4A8.12 8.12 0 0 0 16 8.2C16 3.67 12.42 0 8 0Z"
/>
</svg>
<span>GitHub</span>
</a>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle light and dark theme">
<span data-theme-icon aria-hidden="true"></span>
<span data-theme-label>Theme</span>
</button>
<div class="nav-pages">
<a href="remote-first.html">Remote-first</a>
<a href="machines.html">Fleet</a>
<a href="install.html">Install</a>
<a href="plugins.html" aria-current="page">Plugins</a>
<a href="faq.html">FAQ</a>
</div>
<div class="nav-actions">
<a class="github-link" href="https://github.com/jmfederico/pi-web" aria-label="PI WEB on GitHub">
<svg class="github-icon" viewBox="0 0 16 16" aria-hidden="true">
<path
fill="currentColor"
d="M8 0C3.58 0 0 3.67 0 8.2c0 3.63 2.29 6.7 5.47 7.79.4.08.55-.18.55-.4 0-.2-.01-.85-.01-1.55-2.01.38-2.53-.5-2.69-.96-.09-.24-.48-.96-.82-1.16-.28-.16-.68-.56-.01-.57.63-.01 1.08.59 1.23.84.72 1.24 1.87.89 2.33.68.07-.53.28-.89.51-1.09-1.78-.21-3.64-.91-3.64-4.04 0-.89.31-1.62.82-2.2-.08-.2-.36-1.03.08-2.16 0 0 .67-.22 2.2.84A7.4 7.4 0 0 1 8 3.94c.68 0 1.36.09 2 .28 1.53-1.06 2.2-.84 2.2-.84.44 1.13.16 1.96.08 2.16.51.58.82 1.31.82 2.2 0 3.14-1.87 3.83-3.65 4.04.29.26.54.76.54 1.53 0 1.1-.01 1.99-.01 2.27 0 .22.15.49.55.4A8.12 8.12 0 0 0 16 8.2C16 3.67 12.42 0 8 0Z"
/>
</svg>
<span>GitHub</span>
</a>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle light and dark theme">
<span data-theme-icon aria-hidden="true"></span>
<span data-theme-label>Theme</span>
</button>
</div>
</div>
</nav>
</header>
@@ -89,7 +94,9 @@
</ul>
<p>
Plugins cannot extend the session daemon or add server-side hooks. They run in the browser UI only.
PI WEB's internal API routes are not plugin API; use documented context helpers instead.
Use documented context helpers for the stable API surface. Daring plugins can reach into PI WEB
internals, but those private surfaces are experimental: they may graduate into stable helpers, change
shape, or disappear.
</p>
<div class="callout">
The plugin API is intentionally limited and actively developed. Feedback is appreciated: if an extension
@@ -184,7 +191,8 @@ After editing, check the manifest endpoint and browser-console failure cases.</c
<h3>Updates</h3>
<p>
<strong>Updates</strong> adds a conditional <strong>Updates</strong> workspace tab with PI WEB update,
restart, and installed-service guidance. It is built into PI WEB and enabled by default.
restart, and installed-service guidance. It is built into PI WEB, enabled by default, and uses the
selected machine's plugin copy when machine federation is active.
</p>
<ul>
<li>Plugin id: <code>updates</code></li>
@@ -295,14 +303,14 @@ After editing, check the manifest endpoint and browser-console failure cases.</c
<section id="remote-machine-plugins">
<h2>Remote machine plugins</h2>
<p>
With machine federation, PI WEB also loads discovered plugins from the selected remote machine. Remote
plugins are trusted browser-side code like local plugins, but their actions, workspace panels, and
workspace labels only appear while that machine is selected.
With <a href="machines.html">machine federation</a>, PI WEB also loads discovered plugins from the selected
remote machine. Remote plugins are trusted browser-side code like local plugins, but their actions,
workspace panels, and workspace labels only appear while that machine is selected.
</p>
<ul>
<li>File and terminal helpers run against the selected remote machine.</li>
<li>Remote plugin code is loaded best-effort through the current gateway and cached for the page lifetime.</li>
<li>If the gateway already has an enabled plugin with the same original id, the gateway plugin wins and the remote duplicate stays hidden.</li>
<li>If the gateway and remote machine both have an enabled plugin with the same original id, <code>machineSpecific</code> metadata decides whether the gateway copy is reused or only the selected-machine copy can appear.</li>
<li>Remote theme contributions are ignored for now because themes are app-wide.</li>
<li>Mixed PI WEB versions across federated machines are best-effort and not guaranteed compatible.</li>
</ul>
@@ -310,6 +318,11 @@ After editing, check the manifest endpoint and browser-console failure cases.</c
Remote plugin enablement is controlled by the remote machine's PI WEB plugin config. To edit or disable
one, open that machine directly or update its config file.
</p>
<p>
Plugin package metadata can set <code>machineSpecific: true</code>. Use it for plugins like Updates whose
UI should come from the selected PI WEB instance; on remote machines, the gateway copy is hidden unless
the remote machine exposes its own copy.
</p>
<p>
For portable plugin assets, prefer URLs relative to the plugin module, such as
<code>new URL("./asset.json", import.meta.url)</code>. If a remote plugin constructs absolute asset URLs,
@@ -347,8 +360,8 @@ After editing, check the manifest endpoint and browser-console failure cases.</c
</p>
<p>
If you want to understand the API yourself, ask an agent to read that file and explain the relevant
extension points for the plugin you want. The Markdown reference also marks which context fields are
stable and which PI WEB internals should be avoided unless necessary.
public extension points for the plugin you want. Private PI WEB internals exist for the daring; treat
them as experimental surfaces that may graduate into stable helpers, change shape, or disappear.
</p>
</section>
@@ -376,8 +389,10 @@ After editing, check the manifest endpoint and browser-console failure cases.</c
<section id="trust">
<h2>Trust model</h2>
<p>
Plugins are not sandboxed. They run as JavaScript in the browser, can call PI WEB APIs available to the
current session, and can render arbitrary UI. Install only plugins you trust.
Plugins are not sandboxed. They run as JavaScript in the browser, can use browser APIs, call documented
PI WEB helpers, and render arbitrary UI. If a plugin reaches beyond those helpers into private PI WEB
internals, treat that dependency as experimental: it may become stable, change shape, or disappear.
Install only plugins you trust.
</p>
<p>
Plugins do not run inside the session daemon and do not provide server-side hooks.
@@ -394,6 +409,7 @@ After editing, check the manifest endpoint and browser-console failure cases.</c
<div class="footer-links">
<a href="./">Home</a>
<a href="remote-first.html">Remote-first</a>
<a href="machines.html">Fleet</a>
<a href="install.html">Install</a>
<a href="faq.html">FAQ</a>
<a href="https://github.com/jmfederico/pi-web">GitHub</a>
+27 -29
View File
@@ -22,7 +22,7 @@ Plugins run as JavaScript in the browser app. Treat them as trusted code:
- they can render arbitrary Lit templates/custom elements in plugin contribution areas;
- they should not be installed from untrusted sources.
PI WEB's `/api/...` HTTP and WebSocket endpoints are internal implementation details. Plugin code should not fetch PI WEB API endpoints directly; use the documented context helpers instead.
PI WEB's `/api/...` HTTP and WebSocket endpoints are internal implementation details. Plugin code should use the documented context helpers instead. Daring plugins can still reach private routes or runtime objects because they run in the browser, but those private surfaces are experimental: they may graduate into stable helpers, change shape, or disappear.
## What to ask AI to build
@@ -131,17 +131,22 @@ Reload the PI WEB browser tab. PI WEB serves plugin modules with an mtime-based
## Remote machine plugins
When machine federation is enabled, PI WEB also loads discovered plugins from the selected remote machine. Remote plugins are trusted browser-side code like local plugins, but their contributions are machine-scoped:
When [machine federation](https://pi-web.dev/machines.html) is enabled, PI WEB also loads discovered plugins from the selected remote machine. Remote plugins are trusted browser-side code like local plugins, but their contributions are machine-scoped:
- actions, workspace panels, and workspace labels only appear while that machine is selected;
- plugin file and terminal helpers run against that machine;
- plugin code is loaded best-effort through the current gateway and cached for the browser page lifetime;
- if the gateway already has an enabled plugin with the same original id, the gateway plugin wins and the remote duplicate stays hidden;
- if the gateway and remote machine both have an enabled plugin with the same original id, `machineSpecific` metadata decides whether the gateway copy is reused or only the selected machine's copy can appear;
- remote theme contributions are ignored for now because themes are app-wide;
- mixed PI WEB versions across federated machines are best-effort and not guaranteed compatible.
Remote plugin enablement is controlled by the remote machine's PI WEB plugin config. To edit or disable a remote machine plugin, open that machine directly or update its config file.
Plugin package metadata may set `machineSpecific: true` when the plugin's meaning is tied to the selected PI WEB machine:
- Omitted or `false`: use the gateway copy when the same plugin id is also present on a remote machine. This is best for portable UI plugins whose helpers already route through the selected machine.
- `true`: the gateway copy only appears for the local machine. When a remote machine is selected, only that remote machine's copy can appear; if the remote machine does not expose the plugin, the plugin is hidden. This is best for plugins that report machine-local PI WEB status or depend on machine-local plugin code.
For portable plugin assets, prefer URLs relative to the plugin module, for example:
```js
@@ -182,10 +187,10 @@ Built-in plugins can be managed from **Settings → Plugins** or with the top-le
### Updates
**Plugin id:** `updates`
**Plugin id:** `updates`
**What it does:** adds a conditional **Updates** workspace tab with PI WEB update, restart, and installed-service guidance.
Updates is enabled by default. To hide it, disable `updates` in **Settings → Plugins** or set:
Updates is enabled by default. It declares `machineSpecific: true` so the gateway Updates tab only appears for the local machine; while a remote machine is selected, that remote machine's Updates plugin is used if available. To hide it, disable `updates` in **Settings → Plugins** or set:
```json
{
@@ -197,8 +202,8 @@ Updates is enabled by default. To hide it, disable `updates` in **Settings → P
### Workspace Tasks
**Plugin id:** `workspace-tasks`
**Config file:** `.pi-web/tasks.json`
**Plugin id:** `workspace-tasks`
**Config file:** `.pi-web/tasks.json`
**What it does:** adds a **Tasks** workspace tab for running configured shell commands in dedicated PI WEB terminals.
Workspace Tasks is enabled by default. To hide it, disable `workspace-tasks` in **Settings → Plugins** or set:
@@ -286,7 +291,7 @@ A package can expose one or more PI WEB plugin modules. There is exactly one sup
"piWeb": {
"plugins": [
{ "id": "review", "module": "dist/review.js" },
{ "id": "dashboard", "module": "dist/dashboard.js" }
{ "id": "dashboard", "module": "dist/dashboard.js", "machineSpecific": true }
]
}
}
@@ -298,6 +303,7 @@ Rules:
- Each entry must have an explicit `id` and `module`.
- `id` must match `^[a-z][a-z0-9.-]*$`.
- `module` must be a safe relative path inside the plugin package root.
- `machineSpecific` is optional and must be a boolean; omit it for the default portable gateway behavior.
- Duplicate plugin ids are not auto-renamed; later duplicates are skipped.
- Legacy shortcuts such as `piWeb.plugin`, string entries in `piWeb.plugins`, `piWeb.id` fallback ids, and no-`package.json` fallbacks are not supported.
@@ -312,13 +318,14 @@ The manifest contains each discovered plugin module:
"id": "my-plugin",
"module": "/pi-web-plugins/my-plugin/pi-web-plugin.js?v=1234567890",
"source": "local",
"scope": "local"
"scope": "local",
"machineSpecific": false
}
]
}
```
`source` describes where the plugin came from (`bundled`, `local`, or the Pi package source). `scope` is `bundled`, `local`, `user`, or `project`.
`source` describes where the plugin came from (`bundled`, `local`, or the Pi package source). `scope` is `bundled`, `local`, `user`, or `project`. `machineSpecific` controls whether the gateway copy is valid for remote machines or only each selected machine's own copy can appear.
A plugin can fetch its own static assets with URLs under:
@@ -450,12 +457,12 @@ interface PluginRuntimeContext {
Notes:
- `state` is a snapshot of current UI state when actions are built.
- The stable state fields are `state.selectedWorkspace`, `state.selectedSession`, and `state.piWebStatus`.
- Other `state` fields may exist at runtime, but they are PI WEB internals and can change quickly.
- The stable state fields are `state.selectedWorkspace`, `state.selectedSession`, and `state.piWebStatus`. `state.piWebStatus` describes the currently selected machine's PI WEB runtime, or the gateway/local runtime when the local machine is selected.
- Other `state` fields may exist at runtime, but they are private PI WEB internals that may graduate into stable helpers, change shape, or disappear.
- `enabled` is evaluated when the action palette asks for actions.
- `selectWorkspaceTool()` expects a qualified panel id such as `my-plugin:workspace.info`.
- `openTerminal()` switches to the built-in terminal panel. Pass `{ terminalId }` to deep-link to a specific terminal.
- Only fields documented here and declared in `plugin-api.d.ts` are stable public plugin API. Unstable runtime fields are intentionally omitted from these types; if a plugin author chooses to depend on them, they must explicitly import unstable types from `@jmfederico/pi-web/plugin-api/unstable` and type-assert the context in their own code.
- Only fields documented here and declared in `plugin-api.d.ts` are stable public plugin API. Anything else is experimental: it may become public API later, change shape, or disappear.
#### Keyboard shortcuts
@@ -534,6 +541,8 @@ interface WorkspacePanelContext {
`machine`, `workspace`, `files`, `terminal`, and `host` are documented as stable for panel callbacks. Use `terminal.open()` to switch to the built-in terminal panel; pass `{ terminalId }` to deep-link to a specific terminal. Call `host.requestRender()` when async plugin-owned state changes should make PI WEB re-evaluate panel callbacks such as `badge`, `visible`, or `render`.
For compatibility, PI WEB still provides the old `context.openTerminal()` workspace-panel helper at runtime. It is deprecated, intentionally omitted from the public TypeScript declarations, and planned for removal in v2. Existing JavaScript plugins keep working, while typed plugins should migrate to `context.terminal.open()`.
Useful workspace and machine shapes:
```ts
@@ -761,22 +770,11 @@ render: ({ terminal }) => html`
Review command strings carefully. They are trusted shell commands executed in the workspace terminal.
## Internal PI WEB APIs and explicit unstable opt-in
## Private and experimental PI WEB APIs
PI WEB's `/api/...` HTTP and WebSocket routes are private implementation details. Plugin code should not fetch PI WEB API endpoints directly because those URLs, response shapes, and machine-federation routing rules can change.
PI WEB's `/api/...` HTTP and WebSocket routes and runtime-only fields are private implementation details. They exist because plugins are trusted browser code, and because some capabilities may be evaluated there before they are designed as stable helpers.
If a plugin author deliberately chooses to depend on an unstable runtime field while a public helper is still being designed, make that decision explicit in code with a type-only unstable import and a local type assertion:
```ts
import type { WorkspacePanelContext } from "@jmfederico/pi-web/plugin-api";
import type { UnstableWorkspacePanelContext } from "@jmfederico/pi-web/plugin-api/unstable";
function unstableContext(context: WorkspacePanelContext) {
return context as WorkspacePanelContext & UnstableWorkspacePanelContext;
}
```
Unstable APIs are not covered by the v1 compatibility promise. Prefer documented helpers whenever they exist.
That is allowed, but outside the v1 compatibility promise: URLs, response shapes, runtime fields, and machine-federation routing may graduate into stable APIs, change shape, or disappear. The stable public plugin API is only the documented helpers and declarations in `plugin-api.d.ts`. Prefer those whenever they exist; if you rely on private surfaces, keep the dependency local to the plugin and expect to revisit it after PI WEB upgrades.
## Async data and caching
@@ -793,7 +791,7 @@ PI WEB does not provide a plugin cache/invalidation framework. Keep host callbac
If you are an AI agent building or editing a PI WEB plugin, follow this checklist:
1. Create or update a plugin folder with `package.json` and a JavaScript module such as `pi-web-plugin.js`.
2. Use the single supported package metadata shape: `piWeb.plugins` array with `{ id, module }` entries.
2. Use the single supported package metadata shape: `piWeb.plugins` array with `{ id, module, machineSpecific? }` entries.
3. Default-export `{ apiVersion: 1, name, activate }` from the module.
4. Return `{ contributions: { actions, workspacePanels, workspaceLabels } }` from `activate()`.
5. Use ids matching `^[a-z][a-z0-9.-]*$`.
@@ -804,7 +802,7 @@ If you are an AI agent building or editing a PI WEB plugin, follow this checklis
10. Add workspace labels for compact inline metadata.
11. Return arrays from workspace label `items()`; return an empty array to render nothing.
12. Use documented context helpers first: `files`, `terminal`, `host.requestRender`, `workspace`, `machine`, `state.selectedWorkspace`, `state.selectedSession`, and `state.piWebStatus`.
13. Do not fetch PI WEB `/api/...` endpoints directly. If an unstable runtime field is intentionally required, import the type from `@jmfederico/pi-web/plugin-api/unstable` and type-assert locally.
13. Do not fetch PI WEB `/api/...` endpoints directly unless you intentionally accept private API churn; prefer documented helpers.
14. Treat plugins as trusted code and avoid reading or displaying secrets unless intentional.
15. After local edits, tell the user to hard reload the browser and check the console for plugin errors.
+23 -17
View File
@@ -30,23 +30,28 @@
<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="remote-first.html" aria-current="page">Remote-first</a>
<a href="install.html">Install</a>
<a href="plugins.html">Plugins</a>
<a href="faq.html">FAQ</a>
<a class="github-link" href="https://github.com/jmfederico/pi-web" aria-label="PI WEB on GitHub">
<svg class="github-icon" viewBox="0 0 16 16" aria-hidden="true">
<path
fill="currentColor"
d="M8 0C3.58 0 0 3.67 0 8.2c0 3.63 2.29 6.7 5.47 7.79.4.08.55-.18.55-.4 0-.2-.01-.85-.01-1.55-2.01.38-2.53-.5-2.69-.96-.09-.24-.48-.96-.82-1.16-.28-.16-.68-.56-.01-.57.63-.01 1.08.59 1.23.84.72 1.24 1.87.89 2.33.68.07-.53.28-.89.51-1.09-1.78-.21-3.64-.91-3.64-4.04 0-.89.31-1.62.82-2.2-.08-.2-.36-1.03.08-2.16 0 0 .67-.22 2.2.84A7.4 7.4 0 0 1 8 3.94c.68 0 1.36.09 2 .28 1.53-1.06 2.2-.84 2.2-.84.44 1.13.16 1.96.08 2.16.51.58.82 1.31.82 2.2 0 3.14-1.87 3.83-3.65 4.04.29.26.54.76.54 1.53 0 1.1-.01 1.99-.01 2.27 0 .22.15.49.55.4A8.12 8.12 0 0 0 16 8.2C16 3.67 12.42 0 8 0Z"
/>
</svg>
<span>GitHub</span>
</a>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle light and dark theme">
<span data-theme-icon aria-hidden="true"></span>
<span data-theme-label>Theme</span>
</button>
<div class="nav-pages">
<a href="remote-first.html" aria-current="page">Remote-first</a>
<a href="machines.html">Fleet</a>
<a href="install.html">Install</a>
<a href="plugins.html">Plugins</a>
<a href="faq.html">FAQ</a>
</div>
<div class="nav-actions">
<a class="github-link" href="https://github.com/jmfederico/pi-web" aria-label="PI WEB on GitHub">
<svg class="github-icon" viewBox="0 0 16 16" aria-hidden="true">
<path
fill="currentColor"
d="M8 0C3.58 0 0 3.67 0 8.2c0 3.63 2.29 6.7 5.47 7.79.4.08.55-.18.55-.4 0-.2-.01-.85-.01-1.55-2.01.38-2.53-.5-2.69-.96-.09-.24-.48-.96-.82-1.16-.28-.16-.68-.56-.01-.57.63-.01 1.08.59 1.23.84.72 1.24 1.87.89 2.33.68.07-.53.28-.89.51-1.09-1.78-.21-3.64-.91-3.64-4.04 0-.89.31-1.62.82-2.2-.08-.2-.36-1.03.08-2.16 0 0 .67-.22 2.2.84A7.4 7.4 0 0 1 8 3.94c.68 0 1.36.09 2 .28 1.53-1.06 2.2-.84 2.2-.84.44 1.13.16 1.96.08 2.16.51.58.82 1.31.82 2.2 0 3.14-1.87 3.83-3.65 4.04.29.26.54.76.54 1.53 0 1.1-.01 1.99-.01 2.27 0 .22.15.49.55.4A8.12 8.12 0 0 0 16 8.2C16 3.67 12.42 0 8 0Z"
/>
</svg>
<span>GitHub</span>
</a>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle light and dark theme">
<span data-theme-icon aria-hidden="true"></span>
<span data-theme-label>Theme</span>
</button>
</div>
</div>
</nav>
</header>
@@ -182,6 +187,7 @@
<span>PI WEB · remote-first control for persistent AI agents.</span>
<div class="footer-links">
<a href="./">Home</a>
<a href="machines.html">Fleet</a>
<a href="install.html">Install</a>
<a href="plugins.html">Plugins</a>
<a href="faq.html">FAQ</a>
+43 -23
View File
@@ -197,12 +197,27 @@ video {
background: var(--brand-2);
}
.nav-links {
.nav-links,
.nav-pages,
.nav-actions {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.nav-links {
color: var(--muted);
font-size: 0.95rem;
white-space: nowrap;
}
.nav-pages {
flex: 0 1 auto;
}
.nav-actions {
flex: 0 0 auto;
}
.nav-links a,
@@ -856,6 +871,7 @@ code .comment,
.footer-links {
display: flex;
flex-wrap: wrap;
gap: 14px;
}
@@ -1062,11 +1078,7 @@ html[data-theme="light"] .comment {
}
}
@media (max-width: 680px) {
.container {
width: min(100% - 28px, 1120px);
}
@media (max-width: 820px) {
.nav {
position: relative;
align-items: stretch;
@@ -1082,17 +1094,23 @@ html[data-theme="light"] .comment {
}
.nav-links {
flex-wrap: nowrap;
overflow-x: auto;
overflow: visible;
width: 100%;
scrollbar-width: none;
}
.nav-links a:not(.github-link) {
.nav-pages {
flex: 1 1 auto;
overflow-x: auto;
padding-bottom: 2px;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
}
.nav-pages a {
flex: 0 0 auto;
}
.nav-links::-webkit-scrollbar {
.nav-pages::-webkit-scrollbar {
display: none;
}
@@ -1102,28 +1120,30 @@ html[data-theme="light"] .comment {
padding: 0 10px;
}
.nav-links .github-link,
.nav-links .theme-toggle {
.nav-actions {
position: absolute;
top: 14px;
right: 0;
}
.nav-actions .github-link,
.nav-actions .theme-toggle {
justify-content: center;
width: 38px;
min-width: 38px;
padding: 0;
}
.nav-links .github-link {
right: 44px;
}
.nav-links .theme-toggle {
right: 0;
}
.nav-links .github-link span,
.nav-links .theme-toggle [data-theme-label] {
.nav-actions .github-link span,
.nav-actions .theme-toggle [data-theme-label] {
display: none;
}
}
@media (max-width: 680px) {
.container {
width: min(100% - 28px, 1120px);
}
.hero {
padding-top: 52px;