@rotriz/pi-web-ui 1.1.0 → 1.2.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/extension.mjs +71 -9
- package/package.json +1 -1
- package/server.mjs +13 -5
package/extension.mjs
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
// pi-web-ui extension entry point for the Pi coding agent.
|
|
2
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
3
|
|
|
8
4
|
import { homedir } from "node:os";
|
|
9
5
|
import { dirname, join } from "node:path";
|
|
@@ -33,6 +29,58 @@ function openBrowser(url) {
|
|
|
33
29
|
exec(`${cmd} ${JSON.stringify(url)}`, () => {});
|
|
34
30
|
}
|
|
35
31
|
|
|
32
|
+
// Resolve the pi-coding-agent module path from within Pi's process context
|
|
33
|
+
function findPiModulePath() {
|
|
34
|
+
try {
|
|
35
|
+
// When running inside Pi, the module is already loaded — find it via require.resolve or import.meta
|
|
36
|
+
const candidates = [];
|
|
37
|
+
// Check if pi's entry script reveals the path
|
|
38
|
+
const entry = process.argv[1] || "";
|
|
39
|
+
if (entry) {
|
|
40
|
+
const dir = dirname(entry);
|
|
41
|
+
candidates.push(join(dir, "..", "dist", "index.js"));
|
|
42
|
+
candidates.push(join(dir, "index.js"));
|
|
43
|
+
// Go up to find the package
|
|
44
|
+
let d = dir;
|
|
45
|
+
for (let i = 0; i < 5; i++) {
|
|
46
|
+
const candidate = join(d, "dist", "index.js");
|
|
47
|
+
if (existsSync(candidate)) candidates.push(candidate);
|
|
48
|
+
const pkgCandidate = join(d, "node_modules", "@earendil-works", "pi-coding-agent", "dist", "index.js");
|
|
49
|
+
if (existsSync(pkgCandidate)) candidates.push(pkgCandidate);
|
|
50
|
+
d = dirname(d);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Check common global locations
|
|
54
|
+
const globalDirs = [
|
|
55
|
+
join(homedir(), ".local", "share", "fnm"),
|
|
56
|
+
"/usr/local/lib/node_modules",
|
|
57
|
+
"/usr/lib/node_modules",
|
|
58
|
+
join(homedir(), ".nvm", "versions", "node"),
|
|
59
|
+
join(homedir(), ".volta", "tools", "image", "packages"),
|
|
60
|
+
];
|
|
61
|
+
for (const base of globalDirs) {
|
|
62
|
+
if (!existsSync(base)) continue;
|
|
63
|
+
// Recursively look for the package
|
|
64
|
+
const piPkg = "@earendil-works/pi-coding-agent/dist/index.js";
|
|
65
|
+
// Check direct path
|
|
66
|
+
const direct = join(base, piPkg);
|
|
67
|
+
if (existsSync(direct)) { candidates.push(direct); continue; }
|
|
68
|
+
// For version managers, search one level deep
|
|
69
|
+
}
|
|
70
|
+
// Also check NODE_PATH
|
|
71
|
+
if (process.env.NODE_PATH) {
|
|
72
|
+
for (const p of process.env.NODE_PATH.split(":")) {
|
|
73
|
+
const candidate = join(p, "@earendil-works", "pi-coding-agent", "dist", "index.js");
|
|
74
|
+
if (existsSync(candidate)) candidates.push(candidate);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const c of candidates) {
|
|
78
|
+
if (existsSync(c)) return c;
|
|
79
|
+
}
|
|
80
|
+
} catch {}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
36
84
|
export default function (pi) {
|
|
37
85
|
let serverProcess = null;
|
|
38
86
|
let origin = `http://localhost:${PORT}`;
|
|
@@ -46,7 +94,6 @@ export default function (pi) {
|
|
|
46
94
|
})();
|
|
47
95
|
|
|
48
96
|
function findNode() {
|
|
49
|
-
// Use the same Node binary that's running pi
|
|
50
97
|
return process.execPath;
|
|
51
98
|
}
|
|
52
99
|
|
|
@@ -54,7 +101,7 @@ export default function (pi) {
|
|
|
54
101
|
// Check if server is already running (from a previous session or manual start)
|
|
55
102
|
try {
|
|
56
103
|
const res = await httpGet(`${origin}/api/tabs`);
|
|
57
|
-
if (res.ok) return;
|
|
104
|
+
if (res.ok) return;
|
|
58
105
|
} catch {}
|
|
59
106
|
|
|
60
107
|
if (serverProcess && !serverProcess.killed) return;
|
|
@@ -62,14 +109,30 @@ export default function (pi) {
|
|
|
62
109
|
if (!existsSync(serverPath)) {
|
|
63
110
|
throw new Error(`pi-web-ui server not found at ${serverPath}`);
|
|
64
111
|
}
|
|
112
|
+
|
|
113
|
+
// Resolve the pi module path and pass it to the server
|
|
114
|
+
const piModulePath = findPiModulePath();
|
|
115
|
+
const env = { ...process.env, PORT: String(PORT) };
|
|
116
|
+
if (piModulePath) env.PI_WEB_PI_MODULE = piModulePath;
|
|
117
|
+
|
|
118
|
+
// Also pass NODE_PATH so the subprocess can find global modules
|
|
119
|
+
const nodePath = [];
|
|
120
|
+
if (process.env.NODE_PATH) nodePath.push(process.env.NODE_PATH);
|
|
121
|
+
// Add the global node_modules from the current node binary
|
|
122
|
+
const nodeDir = dirname(dirname(process.execPath));
|
|
123
|
+
const globalNM = join(nodeDir, "lib", "node_modules");
|
|
124
|
+
if (existsSync(globalNM)) nodePath.push(globalNM);
|
|
125
|
+
if (nodePath.length) env.NODE_PATH = nodePath.join(":");
|
|
126
|
+
|
|
65
127
|
serverProcess = execFile(findNode(), [serverPath], {
|
|
66
128
|
cwd: process.cwd(),
|
|
67
|
-
env
|
|
129
|
+
env,
|
|
68
130
|
stdio: "ignore",
|
|
69
131
|
});
|
|
70
132
|
serverProcess.unref();
|
|
71
133
|
serverProcess.on("exit", () => { serverProcess = null; });
|
|
72
|
-
|
|
134
|
+
|
|
135
|
+
// Wait for the server to be ready (up to 30 seconds)
|
|
73
136
|
for (let i = 0; i < 60; i++) {
|
|
74
137
|
await new Promise((r) => setTimeout(r, 500));
|
|
75
138
|
try {
|
|
@@ -89,7 +152,6 @@ export default function (pi) {
|
|
|
89
152
|
|
|
90
153
|
// ── Lifecycle ────────────────────────────────────────────────────────
|
|
91
154
|
pi.on("session_start", async (_event, ctx) => {
|
|
92
|
-
// Start server in background — don't block Pi's session startup
|
|
93
155
|
ensureServer().catch((err) => {
|
|
94
156
|
ctx.ui?.notify?.(`pi-web-ui: ${err?.message ?? err}`, "warning");
|
|
95
157
|
});
|
package/package.json
CHANGED
package/server.mjs
CHANGED
|
@@ -19,7 +19,7 @@ const PORT = process.env.PORT || 3123;
|
|
|
19
19
|
const CWD = process.cwd();
|
|
20
20
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
21
21
|
|
|
22
|
-
// Resolve the pi-coding-agent module: env override → bare specifier →
|
|
22
|
+
// Resolve the pi-coding-agent module: env override → bare specifier → NODE_PATH → common global paths
|
|
23
23
|
async function resolvePiModule() {
|
|
24
24
|
const attempts = [];
|
|
25
25
|
if (process.env.PI_WEB_PI_MODULE) attempts.push(process.env.PI_WEB_PI_MODULE);
|
|
@@ -30,13 +30,21 @@ async function resolvePiModule() {
|
|
|
30
30
|
attempts.push(join(dir, "..", "dist", "index.js"));
|
|
31
31
|
attempts.push(join(dir, "index.js"));
|
|
32
32
|
}
|
|
33
|
-
// Try
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
// Try NODE_PATH entries
|
|
34
|
+
if (process.env.NODE_PATH) {
|
|
35
|
+
for (const p of process.env.NODE_PATH.split(":")) {
|
|
36
|
+
attempts.push(join(p, "@earendil-works", "pi-coding-agent", "dist", "index.js"));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Common global install locations
|
|
40
|
+
const nodeDir = dirname(dirname(process.execPath));
|
|
41
|
+
attempts.push(join(nodeDir, "lib", "node_modules", "@earendil-works", "pi-coding-agent", "dist", "index.js"));
|
|
42
|
+
// npm global prefix
|
|
43
|
+
attempts.push(join(homedir(), ".local", "lib", "node_modules", "@earendil-works", "pi-coding-agent", "dist", "index.js"));
|
|
36
44
|
for (const attempt of attempts) {
|
|
37
45
|
try { return await import(attempt); } catch {}
|
|
38
46
|
}
|
|
39
|
-
throw new Error("Could not resolve @earendil-works/pi-coding-agent. Install it globally or set PI_WEB_PI_MODULE.");
|
|
47
|
+
throw new Error("Could not resolve @earendil-works/pi-coding-agent. Install it globally or set PI_WEB_PI_MODULE env var.");
|
|
40
48
|
}
|
|
41
49
|
const pi = await resolvePiModule();
|
|
42
50
|
|