@rotriz/pi-web-ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # pi-web-ui
2
+
3
+ A browser-based Web UI for the [Pi coding agent](https://github.com/earendil-works/pi-coding-agent). Provides a full-featured interface for managing sessions, workspaces, git operations, and monitoring agent task progress.
4
+
5
+ ## Features
6
+
7
+ - **Multi-session management** — Browse, switch, and delete sessions grouped by workspace/project
8
+ - **Parallel tabs** — Run multiple independent agent sessions simultaneously
9
+ - **Git integration** — Branch management, selective staging, commit, push/pull, and diff viewing
10
+ - **Task tracking** — Automatically extracts task plans from agent output and shows completion progress
11
+ - **Workspace switching** — Change working directories with automatic session context switching
12
+ - **Real-time streaming** — SSE-based live updates for agent responses, tool calls, and thinking
13
+ - **Dark/light theme** — System-aware with manual override
14
+ - **Responsive design** — Collapsible sidebar, floating panels
15
+
16
+ ## Installation
17
+
18
+ ### As a Pi extension (recommended)
19
+
20
+ ```bash
21
+ pi install npm:pi-web-ui
22
+ ```
23
+
24
+ Or manually clone into your extensions directory:
25
+
26
+ ```bash
27
+ git clone https://github.com/your-repo/pi-web-ui ~/.pi/agent/extensions/pi-web-ui
28
+ cd ~/.pi/agent/extensions/pi-web-ui && npm install
29
+ ```
30
+
31
+ ### Standalone
32
+
33
+ ```bash
34
+ git clone https://github.com/your-repo/pi-web-ui
35
+ cd pi-web-ui && npm install
36
+ node server.mjs
37
+ ```
38
+
39
+ Open `http://localhost:3123` in your browser.
40
+
41
+ ## Configuration
42
+
43
+ | Environment Variable | Default | Description |
44
+ |---------------------|---------|-------------|
45
+ | `PORT` | `3123` | HTTP server port |
46
+ | `PI_WEB_PI_MODULE` | (auto-detected) | Path to `@earendil-works/pi-coding-agent` if auto-detection fails |
47
+
48
+ ## Requirements
49
+
50
+ - Node.js 20+
51
+ - `@earendil-works/pi-coding-agent` installed (globally or locally)
52
+ - Git (for git integration features)
53
+
54
+ ## Architecture
55
+
56
+ - `server.mjs` — Node.js HTTP/SSE server that wraps the Pi SDK runtime
57
+ - `index.html` — Single-file frontend (HTML + CSS + JS, no build step)
58
+ - Dependencies: `marked` (markdown rendering), `dompurify` (HTML sanitization)
59
+
60
+ ## License
61
+
62
+ MIT
package/extension.mjs ADDED
@@ -0,0 +1,107 @@
1
+ // pi-web-ui extension entry point for the Pi coding agent.
2
+ // Registers the /web command and starts the HTTP/SSE gateway.
3
+ //
4
+ // When loaded as a Pi extension (via pi.extensions in package.json),
5
+ // this module receives the Pi ExtensionAPI and bridges the host session
6
+ // into the Web UI server.
7
+
8
+ import { homedir } from "node:os";
9
+ import { dirname, join } from "node:path";
10
+ import { exec, execFile } from "node:child_process";
11
+ import { fileURLToPath } from "node:url";
12
+ import { existsSync } from "node:fs";
13
+
14
+ const PORT = Number(process.env.PI_WEB_PORT || process.env.PORT || 3123);
15
+
16
+ function openBrowser(url) {
17
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
18
+ exec(`${cmd} ${JSON.stringify(url)}`, () => {});
19
+ }
20
+
21
+ export default function (pi) {
22
+ let serverProcess = null;
23
+ let origin = `http://localhost:${PORT}`;
24
+
25
+ const extDir = (() => {
26
+ try {
27
+ return dirname(fileURLToPath(import.meta.url));
28
+ } catch {
29
+ return join(homedir(), ".pi", "agent", "extensions", "pi-web-ui");
30
+ }
31
+ })();
32
+
33
+ function findNode() {
34
+ // Use the same Node binary that's running pi
35
+ return process.execPath;
36
+ }
37
+
38
+ async function ensureServer() {
39
+ if (serverProcess && !serverProcess.killed) return;
40
+ const serverPath = join(extDir, "server.mjs");
41
+ if (!existsSync(serverPath)) {
42
+ throw new Error(`pi-web-ui server not found at ${serverPath}`);
43
+ }
44
+ serverProcess = execFile(findNode(), [serverPath], {
45
+ cwd: process.cwd(),
46
+ env: { ...process.env, PORT: String(PORT) },
47
+ stdio: "ignore",
48
+ });
49
+ serverProcess.unref();
50
+ serverProcess.on("exit", () => { serverProcess = null; });
51
+ // Wait for the server to be ready
52
+ for (let i = 0; i < 30; i++) {
53
+ await new Promise((r) => setTimeout(r, 200));
54
+ try {
55
+ const res = await fetch(`${origin}/api/tabs`);
56
+ if (res.ok) return;
57
+ } catch {}
58
+ }
59
+ throw new Error("pi-web-ui server did not start in time");
60
+ }
61
+
62
+ async function stopServer() {
63
+ if (serverProcess && !serverProcess.killed) {
64
+ serverProcess.kill();
65
+ serverProcess = null;
66
+ }
67
+ }
68
+
69
+ // ── Lifecycle ────────────────────────────────────────────────────────
70
+ pi.on("session_start", async (_event, ctx) => {
71
+ try {
72
+ await ensureServer();
73
+ } catch (err) {
74
+ ctx.ui?.notify?.(`pi-web-ui could not start: ${err?.message ?? err}`, "error");
75
+ }
76
+ });
77
+
78
+ pi.on("session_shutdown", async (event) => {
79
+ if (event.reason === "reload" || event.reason === "quit") {
80
+ await stopServer();
81
+ }
82
+ });
83
+
84
+ // ── Commands ─────────────────────────────────────────────────────────
85
+ pi.registerCommand("web", {
86
+ description: "Open the Pi Web UI (pass `stop` to shut down)",
87
+ getArgumentCompletions: (prefix) => {
88
+ const items = ["stop"].map((value) => ({ value, label: value }));
89
+ return items.some((item) => item.value.startsWith(prefix)) ? items : null;
90
+ },
91
+ handler: async (args, ctx) => {
92
+ const arg = String(args ?? "").trim().toLowerCase();
93
+ if (arg === "stop") {
94
+ await stopServer();
95
+ ctx.ui?.notify?.("pi-web-ui stopped", "info");
96
+ return;
97
+ }
98
+ try {
99
+ await ensureServer();
100
+ openBrowser(origin);
101
+ ctx.ui?.notify?.(`pi-web-ui → ${origin}`, "info");
102
+ } catch (err) {
103
+ ctx.ui?.notify?.(`pi-web-ui failed to start: ${err?.message ?? err}`, "error");
104
+ }
105
+ },
106
+ });
107
+ }