@firstpick/pi-package-webui 0.1.4 → 0.1.6
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 +34 -11
- package/bin/pi-webui.mjs +608 -26
- package/index.ts +82 -10
- package/package.json +34 -4
- package/public/app.js +3118 -211
- package/public/catppuccin-mocha-background.png +0 -0
- package/public/index.html +152 -52
- package/public/matrix-background.webp +0 -0
- package/public/service-worker.js +3 -1
- package/public/styles.css +772 -17
- package/tests/mobile-static.test.mjs +231 -36
package/index.ts
CHANGED
|
@@ -11,6 +11,8 @@ const webuiBin = path.join(packageRoot, "bin", "pi-webui.mjs");
|
|
|
11
11
|
const DEFAULT_HOST = "127.0.0.1";
|
|
12
12
|
const DEFAULT_PORT = 31415;
|
|
13
13
|
const START_TIMEOUT_MS = 12_000;
|
|
14
|
+
const START_TIMEOUT_PER_RESTORED_TAB_MS = 4_000;
|
|
15
|
+
const START_TIMEOUT_MAX_MS = 60_000;
|
|
14
16
|
|
|
15
17
|
type WebuiAddress = {
|
|
16
18
|
host: string;
|
|
@@ -292,7 +294,18 @@ async function fetchRestorableTabs(url: string, existing: ExistingWebui, options
|
|
|
292
294
|
const baseUrl = url.replace(/\/$/, "");
|
|
293
295
|
const detailed = await fetchJsonWithTimeout(`${baseUrl}/api/webui-status?detailed=1&events=0`, {}, 7_000);
|
|
294
296
|
const statusData = detailed?.ok && detailed.body?.ok === true ? detailed.body.data : undefined;
|
|
295
|
-
|
|
297
|
+
|
|
298
|
+
// Restart should preserve the tabs that are currently open in the Web UI.
|
|
299
|
+
// Older servers may expose recently closed tabs through `restorableTabs`, so
|
|
300
|
+
// prefer explicit live tab lists whenever the running server provides them.
|
|
301
|
+
const openTabSources: unknown[] = [];
|
|
302
|
+
const detailedTabs = statusData?.tabs;
|
|
303
|
+
if (Array.isArray(detailedTabs)) openTabSources.push(detailedTabs);
|
|
304
|
+
if (Array.isArray(existing.tabs)) openTabSources.push(existing.tabs);
|
|
305
|
+
if (openTabSources.length > 0) return mergeRestorableTabsFromStatusSources(openTabSources, options);
|
|
306
|
+
|
|
307
|
+
// Legacy fallback for servers that predate `tabs` in status/health payloads.
|
|
308
|
+
return mergeRestorableTabsFromStatusSources([statusData?.restorableTabs, existing.restorableTabs], options);
|
|
296
309
|
}
|
|
297
310
|
|
|
298
311
|
function sleep(ms: number): Promise<void> {
|
|
@@ -449,7 +462,12 @@ function terminateFailedChild(child: WebuiChild): void {
|
|
|
449
462
|
child.stderr.destroy();
|
|
450
463
|
}
|
|
451
464
|
|
|
452
|
-
function
|
|
465
|
+
function startupTimeoutMs(restoreTabCount: number): number {
|
|
466
|
+
const extraTabs = Math.max(0, restoreTabCount - 1);
|
|
467
|
+
return Math.min(START_TIMEOUT_MAX_MS, START_TIMEOUT_MS + extraTabs * START_TIMEOUT_PER_RESTORED_TAB_MS);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function waitForWebuiUrl(child: WebuiChild, timeoutMs = START_TIMEOUT_MS): Promise<string> {
|
|
453
471
|
return new Promise((resolve, reject) => {
|
|
454
472
|
let settled = false;
|
|
455
473
|
let output = "";
|
|
@@ -472,8 +490,8 @@ function waitForWebuiUrl(child: WebuiChild): Promise<string> {
|
|
|
472
490
|
};
|
|
473
491
|
|
|
474
492
|
const timeout = setTimeout(() => {
|
|
475
|
-
finish(new Error(`Timed out waiting for Pi Web UI to start. Output:\n${output.trim() || "(no output)"}`));
|
|
476
|
-
},
|
|
493
|
+
finish(new Error(`Timed out after ${Math.round(timeoutMs / 1000)}s waiting for Pi Web UI to start. Output:\n${output.trim() || "(no output)"}`));
|
|
494
|
+
}, timeoutMs);
|
|
477
495
|
|
|
478
496
|
child.stdout.on("data", inspect);
|
|
479
497
|
child.stderr.on("data", inspect);
|
|
@@ -501,7 +519,7 @@ async function startWebui(options: StartWebuiOptions, ctx: ExtensionCommandConte
|
|
|
501
519
|
windowsHide: true,
|
|
502
520
|
});
|
|
503
521
|
|
|
504
|
-
return waitForWebuiUrl(child);
|
|
522
|
+
return waitForWebuiUrl(child, startupTimeoutMs(restoreTabs.length));
|
|
505
523
|
}
|
|
506
524
|
|
|
507
525
|
type WebuiStatusFetchResult = {
|
|
@@ -741,6 +759,34 @@ function statusUsage(): string {
|
|
|
741
759
|
].join("\n");
|
|
742
760
|
}
|
|
743
761
|
|
|
762
|
+
type WebuiTreeNavigateArgs = {
|
|
763
|
+
entryId: string;
|
|
764
|
+
summarize?: boolean;
|
|
765
|
+
customInstructions?: string;
|
|
766
|
+
replaceInstructions?: boolean;
|
|
767
|
+
label?: string;
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
function parseWebuiTreeNavigateArgs(args: string): WebuiTreeNavigateArgs {
|
|
771
|
+
let parsed: unknown;
|
|
772
|
+
try {
|
|
773
|
+
parsed = JSON.parse(args || "{}");
|
|
774
|
+
} catch (error) {
|
|
775
|
+
throw new Error(`Invalid Web UI tree navigation payload: ${error instanceof Error ? error.message : String(error)}`);
|
|
776
|
+
}
|
|
777
|
+
if (!parsed || typeof parsed !== "object") throw new Error("Web UI tree navigation payload must be an object");
|
|
778
|
+
const payload = parsed as Record<string, unknown>;
|
|
779
|
+
const entryId = typeof payload.entryId === "string" ? payload.entryId.trim() : "";
|
|
780
|
+
if (!entryId) throw new Error("Web UI tree navigation requires entryId");
|
|
781
|
+
return {
|
|
782
|
+
entryId,
|
|
783
|
+
summarize: payload.summarize === true,
|
|
784
|
+
customInstructions: typeof payload.customInstructions === "string" ? payload.customInstructions : undefined,
|
|
785
|
+
replaceInstructions: payload.replaceInstructions === true,
|
|
786
|
+
label: typeof payload.label === "string" ? payload.label : undefined,
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
|
|
744
790
|
export default function (pi: ExtensionAPI) {
|
|
745
791
|
const startWebuiHandler = async (args: string, ctx: ExtensionCommandContext) => {
|
|
746
792
|
let options: StartWebuiOptions;
|
|
@@ -780,11 +826,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
780
826
|
handler: startWebuiHandler,
|
|
781
827
|
});
|
|
782
828
|
|
|
783
|
-
pi.registerCommand("start-webui", {
|
|
784
|
-
description: "Alias for /webui-start",
|
|
785
|
-
handler: startWebuiHandler,
|
|
786
|
-
});
|
|
787
|
-
|
|
788
829
|
pi.registerCommand("webui-status", {
|
|
789
830
|
description: "Show Pi Web UI URL, online state, network exposure, and optional detailed runtime info",
|
|
790
831
|
handler: async (args, ctx) => {
|
|
@@ -807,4 +848,35 @@ export default function (pi: ExtensionAPI) {
|
|
|
807
848
|
}
|
|
808
849
|
},
|
|
809
850
|
});
|
|
851
|
+
|
|
852
|
+
pi.registerCommand("webui-tree-navigate", {
|
|
853
|
+
description: "Internal Web UI helper for browser session-tree navigation",
|
|
854
|
+
handler: async (args, ctx) => {
|
|
855
|
+
let payload: WebuiTreeNavigateArgs;
|
|
856
|
+
try {
|
|
857
|
+
payload = parseWebuiTreeNavigateArgs(args);
|
|
858
|
+
} catch (error) {
|
|
859
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
try {
|
|
864
|
+
await ctx.waitForIdle();
|
|
865
|
+
const result = (await ctx.navigateTree(payload.entryId, {
|
|
866
|
+
summarize: payload.summarize,
|
|
867
|
+
customInstructions: payload.customInstructions,
|
|
868
|
+
replaceInstructions: payload.replaceInstructions,
|
|
869
|
+
label: payload.label,
|
|
870
|
+
})) as { cancelled: boolean; editorText?: string };
|
|
871
|
+
if (result.cancelled) {
|
|
872
|
+
ctx.ui.notify("Web UI tree navigation cancelled.", "warning");
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
if (typeof result.editorText === "string") ctx.ui.setEditorText(result.editorText);
|
|
876
|
+
ctx.ui.notify("Web UI navigated the session tree.", "info");
|
|
877
|
+
} catch (error) {
|
|
878
|
+
ctx.ui.notify(`Web UI tree navigation failed:\n${error instanceof Error ? error.message : String(error)}`, "error");
|
|
879
|
+
}
|
|
880
|
+
},
|
|
881
|
+
});
|
|
810
882
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firstpick/pi-package-webui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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
|
"type": "module",
|
|
@@ -15,7 +15,29 @@
|
|
|
15
15
|
],
|
|
16
16
|
"pi": {
|
|
17
17
|
"extensions": [
|
|
18
|
-
"./index.ts"
|
|
18
|
+
"./index.ts",
|
|
19
|
+
"../pi-extension-git-footer-status/index.ts",
|
|
20
|
+
"../pi-extension-release-aur/index.ts",
|
|
21
|
+
"../pi-extension-release-npm/index.ts",
|
|
22
|
+
"../pi-extension-stats/index.ts",
|
|
23
|
+
"../pi-extension-todo-progress/index.ts",
|
|
24
|
+
"node_modules/@firstpick/pi-extension-git-footer-status/index.ts",
|
|
25
|
+
"node_modules/@firstpick/pi-extension-release-aur/index.ts",
|
|
26
|
+
"node_modules/@firstpick/pi-extension-release-npm/index.ts",
|
|
27
|
+
"node_modules/@firstpick/pi-extension-stats/index.ts",
|
|
28
|
+
"node_modules/@firstpick/pi-extension-todo-progress/index.ts"
|
|
29
|
+
],
|
|
30
|
+
"skills": [
|
|
31
|
+
"../pi-extension-release-aur/skills",
|
|
32
|
+
"node_modules/@firstpick/pi-extension-release-aur/skills"
|
|
33
|
+
],
|
|
34
|
+
"prompts": [
|
|
35
|
+
"../pi-package-prompts-git-pr/prompts",
|
|
36
|
+
"node_modules/@firstpick/pi-prompts-git-pr/prompts"
|
|
37
|
+
],
|
|
38
|
+
"themes": [
|
|
39
|
+
"../pi-package-themes-bundle/themes",
|
|
40
|
+
"node_modules/@firstpick/pi-themes-bundle/themes"
|
|
19
41
|
]
|
|
20
42
|
},
|
|
21
43
|
"bin": {
|
|
@@ -26,8 +48,16 @@
|
|
|
26
48
|
"test": "node tests/mobile-static.test.mjs"
|
|
27
49
|
},
|
|
28
50
|
"dependencies": {
|
|
29
|
-
"@earendil-works/pi-coding-agent": "^0.78.0"
|
|
30
|
-
|
|
51
|
+
"@earendil-works/pi-coding-agent": "^0.78.0"
|
|
52
|
+
},
|
|
53
|
+
"optionalDependencies": {
|
|
54
|
+
"@firstpick/pi-extension-git-footer-status": "^0.2.1",
|
|
55
|
+
"@firstpick/pi-extension-release-aur": "^0.1.3",
|
|
56
|
+
"@firstpick/pi-extension-release-npm": "^0.3.3",
|
|
57
|
+
"@firstpick/pi-extension-stats": "^0.2.0",
|
|
58
|
+
"@firstpick/pi-extension-todo-progress": "^0.1.7",
|
|
59
|
+
"@firstpick/pi-prompts-git-pr": "^0.1.0",
|
|
60
|
+
"@firstpick/pi-themes-bundle": "^0.1.1"
|
|
31
61
|
},
|
|
32
62
|
"files": [
|
|
33
63
|
"index.ts",
|