@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 +62 -0
- package/extension.mjs +107 -0
- package/index.html +2622 -0
- package/package.json +35 -0
- package/server.mjs +1398 -0
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
|
+
}
|