@firstpick/pi-package-webui 0.4.8 → 0.5.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 +118 -2
- package/bin/pi-webui.mjs +94 -8
- package/images/Webui_AppRunner_v0.4.8.png +0 -0
- package/images/Webui_BTW_v0.4.8.png +0 -0
- package/images/Webui_CWDpicker_v0.4.8.png +0 -0
- package/images/Webui_CodexUsage_v0.4.8.png +0 -0
- package/images/Webui_ControlPanel_v0.4.8.png +0 -0
- package/images/Webui_Effort_v0.4.8.png +0 -0
- package/images/Webui_GitBranches_v0.4.8.png +0 -0
- package/images/Webui_GitDiff_v0.4.8.png +0 -0
- package/images/Webui_GitWorkflow_v0.4.8.png +0 -0
- package/images/Webui_OptionalFeatures_v0.4.8.png +0 -0
- package/images/Webui_Pistats_v0.4.8.png +0 -0
- package/images/Webui_Queues_v0.4.8.png +0 -0
- package/images/Webui_ScopedModels_v0.4.8.png +0 -0
- package/images/Webui_SkillSetup_v0.4.8.png +0 -0
- package/images/Webui_ToolsSetup_v0.4.8.png +0 -0
- package/images/Webui_Workspace_v0.4.8.png +0 -0
- package/package.json +2 -2
- package/public/app.js +880 -71
- package/public/index.html +6 -2
- package/public/styles.css +458 -46
- package/tests/http-endpoints-harness.test.mjs +55 -0
- package/tests/mobile-static.test.mjs +27 -10
- package/tests/native-parity.test.mjs +5 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Local browser UI for [Pi coding agent](https://www.npmjs.com/package/@earendil-works/pi-coding-agent).
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|
|
|
7
7
|
Pi Web UI gives you a local browser companion for Pi: multi-tab chat, streaming output, model controls, uploads, slash-command helpers, workspace navigation, and optional extension widgets.
|
|
8
8
|
|
|
@@ -138,12 +138,128 @@ Environment variables:
|
|
|
138
138
|
- Persistent context-window meter with manual compact and auto-compaction controls near the composer.
|
|
139
139
|
- Side-panel theme picker backed by optional `@firstpick/pi-themes-bundle` themes when loaded.
|
|
140
140
|
- Per-tab cwd changes, a clickable footer cwd picker, saved path fast picks, server-persisted fast picks, and restart-safe restoration of open tabs.
|
|
141
|
-
- Detected app runner dropdown for the active tab cwd, including Cargo, Bun, npm/npx/pnpm, Python/uv, Go/Golang, Zig, C/C++, Docker Compose, root/dev/scripts shell scripts, and other common project runners with live output pinned at the top of the terminal. Projects can add browseable custom runners in `.pi-webui-runners.json` with a command (default `./`) plus a relative path to the file to run.
|
|
141
|
+
- Detected app runner dropdown for the active tab cwd, including Cargo, Bun, npm/npx/pnpm, Python/uv, Go/Golang, Zig, C/C++, Docker Compose, root/dev/scripts shell scripts, and other common project runners with live output pinned at the top of the terminal. Running app runners expose line-oriented stdin in the widget for interactive scripts. Projects can add browseable custom runners in `.pi-webui-runners.json` with a command (default `./`) plus a relative path to the file to run.
|
|
142
142
|
- Browser support for Pi extension UI prompts, widgets, status updates, `/btw` side-question output widgets with optional context transfer/live steering, browser notifications when a tab needs an extension UI response, and an optional side-panel toggle for agent-done notifications.
|
|
143
143
|
- Localhost-only Pi/Web UI update checks with a top-right update notification and confirmed restart actions: **Update Pi & restart** runs `pi update` for Pi-only updates, while **Update Pi + Packages & Restart** runs `pi update --all` for Pi plus configured packages.
|
|
144
144
|
- Feedback reactions (`👍`, `👎`, `?`) on final assistant output plus tool/bash action cards, which can ask Pi to create or update a LEARNING.
|
|
145
145
|
- Mobile-friendly layout and PWA install support where the browser allows it.
|
|
146
146
|
|
|
147
|
+
## v0.4.8 feature gallery
|
|
148
|
+
|
|
149
|
+
These screenshots show the v0.4.8 Web UI surfaces. Unless noted otherwise, actions apply to the active tab and its current working directory.
|
|
150
|
+
|
|
151
|
+
### Workspace dashboard
|
|
152
|
+
|
|
153
|
+

|
|
154
|
+
|
|
155
|
+
- **What it is:** The project home base for an active Web UI tab, combining cwd, model, context, git, queue, session, and activity status.
|
|
156
|
+
- **What you can do:** Start or resume work, verify the tab is pointed at the right project, jump into common session/workspace actions, and spot queued or active work before prompting.
|
|
157
|
+
|
|
158
|
+
### Control panel
|
|
159
|
+
|
|
160
|
+

|
|
161
|
+
|
|
162
|
+
- **What it is:** The side rail for Web UI state and settings, including model, thinking effort, session/workspace controls, theme, optional companions, Remote WebUI, updates, notifications, and usage widgets.
|
|
163
|
+
- **What you can do:** Change model or effort, compact/manage sessions, toggle notifications, check or install optional packages, run confirmed updates/restarts, and manage remote/PIN controls when the remote companion is loaded.
|
|
164
|
+
|
|
165
|
+
### Working-directory picker
|
|
166
|
+
|
|
167
|
+

|
|
168
|
+
|
|
169
|
+
- **What it is:** A browser-native cwd chooser used at first launch and for per-tab working-directory changes.
|
|
170
|
+
- **What you can do:** Search and browse project paths, choose recent or saved directories, create a new directory, and start or move a Pi tab into the selected workspace.
|
|
171
|
+
|
|
172
|
+
### App runners
|
|
173
|
+
|
|
174
|
+

|
|
175
|
+
|
|
176
|
+
- **What it is:** A project runner detector for common stacks plus browseable custom runners from `.pi-webui-runners.json`.
|
|
177
|
+
- **What you can do:** Launch dev servers, tests, builds, scripts, and custom commands from the active cwd, pass arguments, watch pinned live output, and send line-oriented stdin to interactive runners.
|
|
178
|
+
|
|
179
|
+
### Queue manager
|
|
180
|
+
|
|
181
|
+

|
|
182
|
+
|
|
183
|
+
- **What it is:** The queue surface for follow-up prompts, steering messages, user bash work, and loaded prompt lists while a tab is busy or ready.
|
|
184
|
+
- **What you can do:** Create or load prompt lists, run batches when supported, see pending queued messages, and decide whether prompts sent during an active run should steer the current agent or wait as follow-ups.
|
|
185
|
+
|
|
186
|
+
### Thinking effort picker
|
|
187
|
+
|
|
188
|
+

|
|
189
|
+
|
|
190
|
+
- **What it is:** A browser picker for Pi's model thinking/reasoning effort setting.
|
|
191
|
+
- **What you can do:** Switch between `off`, `minimal`, `low`, `medium`, `high`, and `xhigh`, confirm the effective effort in the footer, and tune speed/cost/quality before sending a prompt.
|
|
192
|
+
|
|
193
|
+
### Scoped models
|
|
194
|
+
|
|
195
|
+

|
|
196
|
+
|
|
197
|
+
- **What it is:** A Web UI editor for `/scoped-models`, project/global model scope rules, and model cycling order.
|
|
198
|
+
- **What you can do:** Search available models, enable or disable scoped entries, inspect the effective model source, and save model choices so future prompts and tabs use the intended provider/model.
|
|
199
|
+
|
|
200
|
+
### Tools setup
|
|
201
|
+
|
|
202
|
+

|
|
203
|
+
|
|
204
|
+
- **What it is:** A browser-native `/tools` setup dialog for active and available Pi tools.
|
|
205
|
+
- **What you can do:** Search tools, inspect descriptions and availability, enable or disable tool access for the active session, and adjust capability exposure without leaving the browser.
|
|
206
|
+
|
|
207
|
+
### Skills setup
|
|
208
|
+
|
|
209
|
+

|
|
210
|
+
|
|
211
|
+
- **What it is:** A browser-native `/skills` setup dialog for installed Pi skills.
|
|
212
|
+
- **What you can do:** Find skills by name or description, review what each skill is for, enable or disable skills for the active session, and make skill activation more transparent before asking Pi to work.
|
|
213
|
+
|
|
214
|
+
### Optional features
|
|
215
|
+
|
|
216
|
+

|
|
217
|
+
|
|
218
|
+
- **What it is:** A companion-package manager for Web UI-aware extensions, prompts, themes, and optional dashboards.
|
|
219
|
+
- **What you can do:** See whether each companion is enabled, disabled, installed-but-not-loaded, missing, or updateable; install/update known packages from localhost; and reload affected tabs when a feature becomes available.
|
|
220
|
+
|
|
221
|
+
### `/btw` side questions
|
|
222
|
+
|
|
223
|
+

|
|
224
|
+
|
|
225
|
+
- **What it is:** A Web UI widget for the optional `/btw` side-question extension, keeping quick questions separate from the main agent flow.
|
|
226
|
+
- **What you can do:** Ask short side questions without derailing the main chat, inspect live output, steer or stop the side thread, and transfer useful context back into the main prompt when needed.
|
|
227
|
+
|
|
228
|
+
### Guided Git workflow
|
|
229
|
+
|
|
230
|
+

|
|
231
|
+
|
|
232
|
+
- **What it is:** A guided browser workflow for staging changes, generating commit messages, committing, pushing, and optionally creating a pull request.
|
|
233
|
+
- **What you can do:** Run the stage/message/commit/push steps, choose generated short or long commit messages, type a manual message, create or confirm PR branch names, review generated PR text, and push only after confirmation.
|
|
234
|
+
|
|
235
|
+
### Git branch picker
|
|
236
|
+
|
|
237
|
+

|
|
238
|
+
|
|
239
|
+
- **What it is:** A footer branch picker backed by the active tab's current Git repository.
|
|
240
|
+
- **What you can do:** View the current branch/repo, switch local branches, create and switch to a new branch, and get warnings when a branch change could affect active agent work.
|
|
241
|
+
|
|
242
|
+
### Git diff viewer
|
|
243
|
+
|
|
244
|
+

|
|
245
|
+
|
|
246
|
+
- **What it is:** A browser diff dialog for current Git changes in the active workspace.
|
|
247
|
+
- **What you can do:** Review staged, unstaged, untracked, and incoming changes; jump between files; see additions/deletions with line numbers; and inspect text previews before asking Pi to edit, commit, or create a PR.
|
|
248
|
+
|
|
249
|
+
### Codex usage
|
|
250
|
+
|
|
251
|
+

|
|
252
|
+
|
|
253
|
+
- **What it is:** A side-panel usage widget for Codex-family subscription-backed models.
|
|
254
|
+
- **What you can do:** Refresh usage, monitor short-window and weekly limits, see reset timing, and decide whether to switch models or delay large prompts.
|
|
255
|
+
|
|
256
|
+
### Pi stats dashboard
|
|
257
|
+
|
|
258
|
+

|
|
259
|
+
|
|
260
|
+
- **What it is:** The browser overlay from the optional stats companion, summarizing token, cost, cache, prompt/context, model, session, and command usage.
|
|
261
|
+
- **What you can do:** Filter by time range, refresh analytics, review daily/model/session breakdowns, inspect cost and cache behavior, and calibrate prompt estimates for more accurate local usage visibility.
|
|
262
|
+
|
|
147
263
|
Useful browser endpoints exposed by the local server include:
|
|
148
264
|
|
|
149
265
|
- `GET /api/path-suggestions?tab=<tabId>&query=<path>` for `@` file/path references with live suggestions.
|
package/bin/pi-webui.mjs
CHANGED
|
@@ -177,6 +177,7 @@ const APP_RUNNER_DETECTION_TIMEOUT_MS = 1_200;
|
|
|
177
177
|
const APP_RUNNER_COMMAND_CACHE_TTL_MS = 30_000;
|
|
178
178
|
const APP_RUNNER_OUTPUT_LINE_LIMIT = 1_000;
|
|
179
179
|
const APP_RUNNER_OUTPUT_MAX_CHARS = 240_000;
|
|
180
|
+
const APP_RUNNER_INPUT_MAX_CHARS = 16_000;
|
|
180
181
|
const APP_RUNNER_STOP_GRACE_MS = 2_500;
|
|
181
182
|
const APP_RUNNER_PYTHON_ENTRIES = ["Main.py", "main.py", "src/main.py", "src/Main.py", "app.py", "src/app.py"];
|
|
182
183
|
const APP_RUNNER_JS_ENTRIES = ["main.js", "src/main.js", "index.js", "src/index.js", "server.js", "src/server.js", "app.js", "src/app.js"];
|
|
@@ -838,10 +839,18 @@ function safeDownloadFileName(name, fallback = "pi-export") {
|
|
|
838
839
|
return (text || fallback).slice(0, 180);
|
|
839
840
|
}
|
|
840
841
|
|
|
841
|
-
function
|
|
842
|
+
function contentDispositionHeader(fileName, disposition = "attachment") {
|
|
842
843
|
const safeName = safeDownloadFileName(fileName);
|
|
843
844
|
const asciiName = safeName.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_");
|
|
844
|
-
return
|
|
845
|
+
return `${disposition}; filename="${asciiName}"; filename*=UTF-8''${encodeURIComponent(safeName)}`;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function contentDispositionAttachment(fileName) {
|
|
849
|
+
return contentDispositionHeader(fileName, "attachment");
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function contentDispositionInline(fileName) {
|
|
853
|
+
return contentDispositionHeader(fileName, "inline");
|
|
845
854
|
}
|
|
846
855
|
|
|
847
856
|
function registerNativeDownload(filePath, { fileName, contentType, command = "native" } = {}) {
|
|
@@ -856,15 +865,17 @@ function registerNativeDownload(filePath, { fileName, contentType, command = "na
|
|
|
856
865
|
expiresAt,
|
|
857
866
|
};
|
|
858
867
|
nativeDownloadTokens.set(token, record);
|
|
868
|
+
const url = `/api/native-download/${encodeURIComponent(token)}`;
|
|
859
869
|
return {
|
|
860
|
-
url
|
|
870
|
+
url,
|
|
871
|
+
openUrl: record.contentType === MIME_TYPES.get(".html") ? `${url}?disposition=inline` : undefined,
|
|
861
872
|
fileName: record.fileName,
|
|
862
873
|
contentType: record.contentType,
|
|
863
874
|
expiresAt: new Date(expiresAt).toISOString(),
|
|
864
875
|
};
|
|
865
876
|
}
|
|
866
877
|
|
|
867
|
-
async function sendNativeDownload(res, token) {
|
|
878
|
+
async function sendNativeDownload(res, token, { inline = false } = {}) {
|
|
868
879
|
pruneNativeDownloadTokens();
|
|
869
880
|
const item = nativeDownloadTokens.get(token);
|
|
870
881
|
if (!item) throw makeHttpError(404, "Download token expired or not found");
|
|
@@ -873,10 +884,11 @@ async function sendNativeDownload(res, token) {
|
|
|
873
884
|
nativeDownloadTokens.delete(token);
|
|
874
885
|
throw makeHttpError(404, "Download file expired or not found");
|
|
875
886
|
}
|
|
887
|
+
const canRenderInline = inline === true && item.contentType === MIME_TYPES.get(".html");
|
|
876
888
|
res.writeHead(200, {
|
|
877
889
|
"content-type": item.contentType,
|
|
878
890
|
"content-length": String(fileStats.size),
|
|
879
|
-
"content-disposition": contentDispositionAttachment(item.fileName),
|
|
891
|
+
"content-disposition": canRenderInline ? contentDispositionInline(item.fileName) : contentDispositionAttachment(item.fileName),
|
|
880
892
|
"cache-control": "no-store",
|
|
881
893
|
"x-content-type-options": "nosniff",
|
|
882
894
|
});
|
|
@@ -2274,6 +2286,11 @@ async function detectAppRunners(tab) {
|
|
|
2274
2286
|
.map(publicAppRunner);
|
|
2275
2287
|
}
|
|
2276
2288
|
|
|
2289
|
+
function appRunnerPendingLine(run) {
|
|
2290
|
+
if (!run || run.status !== "running") return "";
|
|
2291
|
+
return [run.stdoutRemainder, run.stderrRemainder].map((part) => String(part || "")).filter(Boolean).join("");
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2277
2294
|
function publicAppRunnerState(run) {
|
|
2278
2295
|
if (!run) return null;
|
|
2279
2296
|
return {
|
|
@@ -2295,6 +2312,11 @@ function publicAppRunnerState(run) {
|
|
|
2295
2312
|
truncated: run.truncated === true,
|
|
2296
2313
|
lineCount: run.lineCount || run.lines?.length || 0,
|
|
2297
2314
|
lines: Array.isArray(run.lines) ? [...run.lines] : [],
|
|
2315
|
+
pendingLine: appRunnerPendingLine(run),
|
|
2316
|
+
stdinClosed: run.stdinClosed === true,
|
|
2317
|
+
stdinError: run.stdinError || "",
|
|
2318
|
+
stdinWrites: run.stdinWrites || 0,
|
|
2319
|
+
lastStdinAt: run.lastStdinAt || "",
|
|
2298
2320
|
};
|
|
2299
2321
|
}
|
|
2300
2322
|
|
|
@@ -2398,6 +2420,7 @@ function finishAppRunner(tab, run, patch = {}) {
|
|
|
2398
2420
|
run.error = patch.error;
|
|
2399
2421
|
run.status = patch.error ? "error" : patch.exitCode === 0 ? "done" : "failed";
|
|
2400
2422
|
run.child = null;
|
|
2423
|
+
run.stdinClosed = true;
|
|
2401
2424
|
run.stopping = false;
|
|
2402
2425
|
appendAppRunnerLine(run, `# ${appRunnerStatusLabel(run)} after ${Math.max(0, Math.round((Date.parse(run.endedAt) - Date.parse(run.startedAt)) / 1000))}s`);
|
|
2403
2426
|
if (patch.error) appendAppRunnerLine(run, `# ${patch.error}`);
|
|
@@ -2407,6 +2430,45 @@ function finishAppRunner(tab, run, patch = {}) {
|
|
|
2407
2430
|
broadcastAppRunnerState(tab);
|
|
2408
2431
|
}
|
|
2409
2432
|
|
|
2433
|
+
function normalizeAppRunnerInputText(value) {
|
|
2434
|
+
const text = String(value ?? "");
|
|
2435
|
+
if (text.includes("\0")) throw makeHttpError(400, "App runner input cannot contain null bytes");
|
|
2436
|
+
if (text.length > APP_RUNNER_INPUT_MAX_CHARS) throw makeHttpError(413, `App runner input is too long; limit is ${APP_RUNNER_INPUT_MAX_CHARS} characters`);
|
|
2437
|
+
return text;
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
function sendAppRunnerInput(tab, value, { appendNewline = true, closeStdin = false } = {}) {
|
|
2441
|
+
const run = tab?.appRunner;
|
|
2442
|
+
if (!run || run.status !== "running") throw makeHttpError(409, "No app runner is running in this tab");
|
|
2443
|
+
const stdin = run.child?.stdin;
|
|
2444
|
+
if (!stdin || stdin.destroyed || stdin.writableEnded || run.stdinClosed === true) throw makeHttpError(409, "App runner stdin is closed");
|
|
2445
|
+
const text = normalizeAppRunnerInputText(value);
|
|
2446
|
+
const chunk = `${text}${appendNewline === false ? "" : "\n"}`;
|
|
2447
|
+
if (!chunk && !closeStdin) throw makeHttpError(400, "App runner input is empty");
|
|
2448
|
+
let buffered = false;
|
|
2449
|
+
try {
|
|
2450
|
+
if (closeStdin) {
|
|
2451
|
+
if (chunk) stdin.end(chunk, "utf8");
|
|
2452
|
+
else stdin.end();
|
|
2453
|
+
run.stdinClosed = true;
|
|
2454
|
+
} else {
|
|
2455
|
+
buffered = stdin.write(chunk, "utf8") === false;
|
|
2456
|
+
}
|
|
2457
|
+
} catch (error) {
|
|
2458
|
+
run.stdinClosed = true;
|
|
2459
|
+
run.stdinError = sanitizeError(error);
|
|
2460
|
+
throw makeHttpError(409, `App runner stdin write failed: ${run.stdinError}`);
|
|
2461
|
+
}
|
|
2462
|
+
run.stdinWrites = (run.stdinWrites || 0) + 1;
|
|
2463
|
+
run.lastStdinAt = new Date().toISOString();
|
|
2464
|
+
const closeSuffix = closeStdin ? " and closed" : "";
|
|
2465
|
+
if (chunk) appendAppRunnerLine(run, text ? `# stdin sent (${text.length} char${text.length === 1 ? "" : "s"})${closeSuffix}` : `# stdin sent (Enter)${closeSuffix}`);
|
|
2466
|
+
else appendAppRunnerLine(run, "# stdin closed (EOF)");
|
|
2467
|
+
recordEvent({ type: "webui_app_runner_stdin", tabId: tab.id, tabTitle: tab.title, command: run.displayCommand, chars: text.length, newline: appendNewline !== false, closed: closeStdin === true });
|
|
2468
|
+
scheduleAppRunnerBroadcast(tab);
|
|
2469
|
+
return { cwd: tab.cwd, activeRun: publicAppRunnerState(run), inputBuffered: buffered };
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2410
2472
|
async function startAppRunner(tab, runnerId) {
|
|
2411
2473
|
if (tab.appRunner?.status === "running") throw makeHttpError(409, `App runner already running: ${tab.appRunner.displayCommand}`);
|
|
2412
2474
|
const runners = await detectAppRunners(tab);
|
|
@@ -2427,12 +2489,14 @@ async function startAppRunner(tab, runnerId) {
|
|
|
2427
2489
|
lines: [],
|
|
2428
2490
|
lineCount: 0,
|
|
2429
2491
|
outputChars: 0,
|
|
2492
|
+
stdinClosed: false,
|
|
2493
|
+
stdinWrites: 0,
|
|
2430
2494
|
};
|
|
2431
2495
|
appendAppRunnerLine(run, `$ ${run.displayCommand}`);
|
|
2432
2496
|
const child = spawn(run.command, run.args, {
|
|
2433
2497
|
cwd: run.cwd,
|
|
2434
2498
|
env: process.env,
|
|
2435
|
-
stdio: ["
|
|
2499
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2436
2500
|
windowsHide: true,
|
|
2437
2501
|
detached: process.platform !== "win32",
|
|
2438
2502
|
});
|
|
@@ -2440,6 +2504,18 @@ async function startAppRunner(tab, runnerId) {
|
|
|
2440
2504
|
run.pid = child.pid;
|
|
2441
2505
|
tab.appRunner = run;
|
|
2442
2506
|
|
|
2507
|
+
child.stdin?.on("error", (error) => {
|
|
2508
|
+
run.stdinClosed = true;
|
|
2509
|
+
run.stdinError = sanitizeError(error);
|
|
2510
|
+
if (run.status === "running") {
|
|
2511
|
+
appendAppRunnerLine(run, `# stdin error: ${run.stdinError}`);
|
|
2512
|
+
scheduleAppRunnerBroadcast(tab);
|
|
2513
|
+
}
|
|
2514
|
+
});
|
|
2515
|
+
child.stdin?.on("close", () => {
|
|
2516
|
+
run.stdinClosed = true;
|
|
2517
|
+
if (run.status === "running") scheduleAppRunnerBroadcast(tab);
|
|
2518
|
+
});
|
|
2443
2519
|
child.stdout.on("data", (chunk) => appendAppRunnerChunk(tab, run, chunk, "stdout"));
|
|
2444
2520
|
child.stderr.on("data", (chunk) => appendAppRunnerChunk(tab, run, chunk, "stderr"));
|
|
2445
2521
|
child.on("error", (error) => finishAppRunner(tab, run, { error: sanitizeError(error) }));
|
|
@@ -6544,7 +6620,7 @@ async function handleNativeExportCommand(tab, args, req) {
|
|
|
6544
6620
|
return respondNative("export", {
|
|
6545
6621
|
status: "succeeded",
|
|
6546
6622
|
level: "info",
|
|
6547
|
-
message: `Exported current session to HTML.\nDownload: ${download.fileName}\nLink expires: ${download.expiresAt}`,
|
|
6623
|
+
message: `Exported current session to HTML.\nDownload: ${download.fileName}\nOpen it in your browser when prompted.\nOpen URL: ${download.openUrl || download.url}\nDownload URL: ${download.url}\nLink expires: ${download.expiresAt}`,
|
|
6548
6624
|
download,
|
|
6549
6625
|
result: response.data,
|
|
6550
6626
|
refresh: ["state"],
|
|
@@ -7304,7 +7380,9 @@ const server = createServer(async (req, res) => {
|
|
|
7304
7380
|
}
|
|
7305
7381
|
|
|
7306
7382
|
if (url.pathname.startsWith("/api/native-download/") && req.method === "GET") {
|
|
7307
|
-
await sendNativeDownload(res, decodeURIComponent(url.pathname.slice("/api/native-download/".length))
|
|
7383
|
+
await sendNativeDownload(res, decodeURIComponent(url.pathname.slice("/api/native-download/".length)), {
|
|
7384
|
+
inline: url.searchParams.get("disposition") === "inline",
|
|
7385
|
+
});
|
|
7308
7386
|
return;
|
|
7309
7387
|
}
|
|
7310
7388
|
|
|
@@ -7399,6 +7477,14 @@ const server = createServer(async (req, res) => {
|
|
|
7399
7477
|
return;
|
|
7400
7478
|
}
|
|
7401
7479
|
|
|
7480
|
+
if (url.pathname === "/api/app-runner/input" && req.method === "POST") {
|
|
7481
|
+
const body = await readJsonBody(req);
|
|
7482
|
+
const tab = getRequestedTab(req, url, body);
|
|
7483
|
+
const text = Object.prototype.hasOwnProperty.call(body, "text") ? body.text : body.input;
|
|
7484
|
+
sendJson(res, 200, { ok: true, data: sendAppRunnerInput(tab, text, { appendNewline: body.newline !== false, closeStdin: body.closeStdin === true || body.close === true }) });
|
|
7485
|
+
return;
|
|
7486
|
+
}
|
|
7487
|
+
|
|
7402
7488
|
if (url.pathname === "/api/app-runner/stop" && req.method === "POST") {
|
|
7403
7489
|
const body = await readJsonBody(req);
|
|
7404
7490
|
const tab = getRequestedTab(req, url, body);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firstpick/pi-package-webui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Pi Web UI companion package with a local browser UI CLI plus /webui-start and /webui-status commands.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/Firstp1ck/npm-packages/tree/main/pi-package-webui#readme",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"test": "node tests/run-all.mjs"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@earendil-works/pi-coding-agent": "^0.79.
|
|
56
|
+
"@earendil-works/pi-coding-agent": "^0.79.8"
|
|
57
57
|
},
|
|
58
58
|
"optionalDependencies": {
|
|
59
59
|
"@firstpick/pi-extension-btw": "^0.1.0",
|