@jmfederico/pi-web 1.202605.7 → 1.202605.9
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 +25 -1
- package/dist/client/assets/CodeViewer-BJm9p3hj.js +4 -0
- package/dist/client/assets/TerminalPanel-DMOCBUgN.js +47 -0
- package/dist/client/assets/index-CVt9MoKL.js +1049 -0
- package/dist/client/index.html +40 -2
- package/dist/server/app.js +2 -0
- package/dist/server/app.js.map +1 -1
- package/dist/server/piWebPluginService.js +51 -58
- package/dist/server/piWebPluginService.js.map +1 -1
- package/dist/server/piWebStatus.js +371 -0
- package/dist/server/piWebStatus.js.map +1 -0
- package/dist/server/sessiond.js +7 -1
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/editPreview.js +196 -0
- package/dist/server/sessions/editPreview.js.map +1 -0
- package/dist/server/sessions/piSessionService.js +154 -25
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionArchiveStore.js +168 -20
- package/dist/server/sessions/sessionArchiveStore.js.map +1 -1
- package/docs/plugins.md +582 -0
- package/package.json +4 -2
- package/pi-web-plugins/info/package.json +3 -2
- package/pi-web-plugins/info/pi-web-plugin.js +37 -37
- package/pi-web-plugins/pi-web/package.json +9 -0
- package/pi-web-plugins/pi-web/pi-web-plugin.js +148 -0
- package/dist/client/assets/CodeViewer-Bblh2Nfs.js +0 -4
- package/dist/client/assets/TerminalPanel-CovbxLyI.js +0 -47
- package/dist/client/assets/index-BaavvH-H.js +0 -871
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://github.com/earendil-works/pi/tree/main/packages/coding-agent)
|
|
8
8
|
|
|
9
|
-
Website: <https://
|
|
9
|
+
Website: <https://pi-web.dev/>
|
|
10
10
|
|
|
11
11
|

|
|
12
12
|
|
|
@@ -97,6 +97,30 @@ Pi Web keeps its own state intentionally small:
|
|
|
97
97
|
- Sessions and chat history: Pi's default JSONL session storage
|
|
98
98
|
- Active session runtimes and WebSockets: memory in the session daemon
|
|
99
99
|
|
|
100
|
+
## Plugins
|
|
101
|
+
|
|
102
|
+
Pi Web production installs can load trusted local UI plugins without rebuilding Pi Web. Plugins are browser-side ES modules that can add action-palette actions, workspace panels, and workspace-label metadata. They do not run in the session daemon and are not sandboxed.
|
|
103
|
+
|
|
104
|
+
The supported package shape is intentionally singular: `piWeb.plugins` entries with explicit `id` and `module`, plus a browser module that exports `{ apiVersion: 1, name, activate }`. The bundled `pi-web-plugins/info` plugin is the canonical minimal real example, and `pi-web-plugins/pi-web` demonstrates a dynamic status panel.
|
|
105
|
+
|
|
106
|
+
A useful prompt for AI agents:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
Build a Pi Web plugin for this project. Goal: <describe the UI behavior>.
|
|
110
|
+
Before coding, read https://pi-web.dev/plugins.html and https://pi-web.dev/plugins.md.
|
|
111
|
+
Create it under ~/.pi-web/plugins/<plugin-id> using the documented Pi Web v1 plugin API.
|
|
112
|
+
Validate with /pi-web-plugins/manifest.json and explain reload/debug steps.
|
|
113
|
+
Do not modify Pi Web itself.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Reload the browser tab after adding or editing a plugin. If `PI_WEB_DATA_DIR` is set, use `$PI_WEB_DATA_DIR/plugins` instead of `~/.pi-web/plugins`. Check discovery with:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
curl http://127.0.0.1:8504/pi-web-plugins/manifest.json
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
See the full [Plugin API](docs/plugins.md) for contribution types, package metadata, and troubleshooting.
|
|
123
|
+
|
|
100
124
|
## Install
|
|
101
125
|
|
|
102
126
|
Recommended install uses npm plus systemd user services:
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{w as a,J as p,$ as h,A as m,X as g,U as f,_ as v,a0 as b}from"./vendor-editor-core-B4Sq6exx.js";import{g as y,r as w,p as C,h as x,c as j,m as _,j as k,a as u}from"./vendor-editor-languages-DznYbTkJ.js";import{d as E}from"./vendor-editor-legacy-B4QLsWF8.js";import{i as H,b as O,a as S,n as d,e as P,t as L}from"./index-CVt9MoKL.js";var D=Object.defineProperty,F=Object.getOwnPropertyDescriptor,i=(e,o,n,s)=>{for(var t=s>1?void 0:s?F(o,n):o,c=e.length-1,l;c>=0;c--)(l=e[c])&&(t=(s?l(o,n,t):l(t))||t);return s&&t&&D(o,n,t),t};let r=class extends H{constructor(){super(...arguments),this.content=""}firstUpdated(){this.recreateEditor()}updated(e){(e.has("content")||e.has("language"))&&this.recreateEditor()}disconnectedCallback(){this.view?.destroy(),this.view=void 0,super.disconnectedCallback()}render(){return O`<div class="host"></div>`}recreateEditor(){this.editorHost&&(this.view?.destroy(),this.view=new a({parent:this.editorHost,state:p.create({doc:this.content,extensions:[h(),m.of(g),f(v,{fallback:!0}),p.readOnly.of(!0),a.editable.of(!1),a.lineWrapping,M,...R(this.language)]})}))}};r.styles=S`
|
|
2
|
+
:host { display: block; min-height: 0; height: 100%; }
|
|
3
|
+
.host { height: 100%; min-height: 0; overflow: auto; }
|
|
4
|
+
`;i([d()],r.prototype,"content",2);i([d()],r.prototype,"language",2);i([P(".host")],r.prototype,"editorHost",2);r=i([L("code-viewer")],r);const M=a.theme({"&":{height:"100%",color:"var(--pi-text)",backgroundColor:"var(--pi-bg)",fontSize:"12px"},".cm-scroller":{fontFamily:"ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",lineHeight:"1.45"},".cm-gutters":{backgroundColor:"var(--pi-bg)",color:"var(--pi-dim)",borderRight:"1px solid var(--pi-border-muted)"},".cm-activeLineGutter":{backgroundColor:"transparent"},".cm-activeLine":{backgroundColor:"transparent"},".cm-content":{caretColor:"transparent"},"&.cm-focused":{outline:"none"}});function R(e){if(e===void 0)return[];switch(e){case"typescript":return[u({typescript:!0})];case"javascript":return[u()];case"json":return[k()];case"markdown":return[_()];case"css":return[j()];case"html":return[x()];case"python":return[C()];case"rust":return[w()];case"go":return[y()];case"diff":return[b.define(E)];default:return[]}}export{r as CodeViewer};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import{i as w,c as m,d as x,b as c,a as y,n as b,e as T,r as d,t as k}from"./index-CVt9MoKL.js";import{D as h,o as u}from"./vendor-terminal-DjQ08hXu.js";import"./vendor-editor-core-B4Sq6exx.js";import"./vendor-editor-languages-DznYbTkJ.js";var S=Object.defineProperty,O=Object.getOwnPropertyDescriptor,a=(e,t,i,r)=>{for(var s=r>1?void 0:r?O(t,i):t,o=e.length-1,l;o>=0;o--)(l=e[o])&&(s=(r?l(t,i,s):l(s))||s);return r&&s&&S(t,i,s),s};const I={cursorBlink:!0,convertEol:!0,fontFamily:"ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",fontSize:13},z={cols:100,rows:30};let n=class extends w{constructor(){super(...arguments),this.autoStart=!1,this.terminals=[],this.loading=!1,this.visible=!1,this.suppressTerminalInput=!1}connectedCallback(){super.connectedCallback(),this.themeObserver=new MutationObserver(()=>{this.applyTerminalTheme()}),this.themeObserver.observe(document.documentElement,{attributes:!0,attributeFilter:["class","style","data-theme"]})}firstUpdated(){this.intersectionObserver=new IntersectionObserver(e=>{this.visible=e[0]?.isIntersecting===!0}),this.intersectionObserver.observe(this)}disconnectedCallback(){this.intersectionObserver?.disconnect(),this.intersectionObserver=void 0,this.themeObserver?.disconnect(),this.themeObserver=void 0,this.disposeTerminalView(),super.disconnectedCallback()}willUpdate(){const e=this.workspace?.path;e!==this.observedCwd&&(this.observedCwd=e,this.loadedCwd=void 0,this.terminals=[],this.selectedId=void 0,this.disposeTerminalView())}updated(){this.loadVisibleWorkspaceTerminals(),this.ensureTerminalView()}loadVisibleWorkspaceTerminals(){const e=this.workspace?.path;!this.visible||e===void 0||e===this.loadedCwd||(this.loadedCwd=e,this.loadTerminals())}async loadTerminals(){this.loading=!0,this.error=void 0;try{if(this.workspace===void 0)return;const e=await m.terminals(this.workspace.projectId,this.workspace.id);this.terminals=e,this.selectedId=e.find(t=>!t.exited)?.id??e[0]?.id,e.length===0&&this.autoStart&&await this.startTerminal()}catch(e){this.error=e instanceof Error?e.message:String(e)}finally{this.loading=!1}}async startTerminal(){if(this.workspace!==void 0){this.error=void 0;try{const e=this.measureTerminalSize()??z,t=await m.startTerminal(this.workspace.projectId,this.workspace.id,e);this.terminals=[...this.terminals,t],this.selectTerminal(t.id)}catch(e){this.error=e instanceof Error?e.message:String(e)}}}async closeTerminal(e,t){t.stopPropagation();try{if(this.workspace===void 0)return;await m.closeTerminal(this.workspace.projectId,this.workspace.id,e);const i=this.terminals.filter(r=>r.id!==e);this.terminals=i,this.selectedId===e&&(this.selectedId=i[0]?.id,this.disposeTerminalView())}catch(i){this.error=i instanceof Error?i.message:String(i)}}selectTerminal(e){this.selectedId!==e&&(this.selectedId=e,this.disposeTerminalView())}ensureTerminalView(){const e=this.workspace;if(!this.visible||this.terminal!==void 0||this.selectedId===void 0||this.terminalHost===void 0||e===void 0)return;const t=new h(f(this)),i=new u;t.loadAddon(i),t.open(this.terminalHost),this.terminal=t,this.fitAddon=i,this.resizeObserver=new ResizeObserver(()=>{this.fitAndNotify()}),this.resizeObserver.observe(this.terminalHost),t.onData(s=>{if(this.suppressTerminalInput)return;const o=C(s);o!==""&&this.send({type:"input",data:o})});const r=this.fitTerminal();this.connectSocket(e.projectId,e.id,this.selectedId,t,r),requestAnimationFrame(()=>{this.fitAndNotify()}),t.focus()}connectSocket(e,t,i,r,s){const o=x(e,t,i,s);o.binaryType="arraybuffer",this.socket=o,o.addEventListener("open",()=>{this.fitAndNotify()}),o.addEventListener("message",l=>{this.handleSocketMessage(l.data,i,r)}),o.addEventListener("close",()=>{this.socket===o&&(this.socket=void 0)})}async handleSocketMessage(e,t,i){try{const r=A(await $(e));r.type==="output"&&this.writeTerminalOutput(i,r.data,r.replay===!0),r.type==="exit"&&(i.writeln(`\r
|
|
2
|
+
[process exited${r.exitCode===void 0?"":` with code ${String(r.exitCode)}`}]`),this.terminals=this.terminals.map(s=>s.id===t?{...s,exited:!0,...r.exitCode===void 0?{}:{exitCode:r.exitCode}}:s)),r.type==="error"&&i.writeln(`\r
|
|
3
|
+
[terminal error: ${r.message}]`)}catch(r){i.writeln(`\r
|
|
4
|
+
[terminal error: ${r instanceof Error?r.message:String(r)}]`)}}writeTerminalOutput(e,t,i){if(!i){e.write(t);return}this.suppressTerminalInput=!0,e.write(t,()=>{this.suppressTerminalInput=!1})}fitAndNotify(){const e=this.fitTerminal();e!==void 0&&this.send({type:"resize",...e})}fitTerminal(){if(this.fitAddon===void 0||this.terminal===void 0)return;const e=this.fitAddon.proposeDimensions(),t=v(e);if(t!==void 0)return this.fitAddon.fit(),t}measureTerminalSize(){const e=this.fitTerminal();if(e!==void 0)return e;if(this.terminal!==void 0||this.terminalHost===void 0)return;const t=new h(f(this)),i=new u;t.loadAddon(i),t.open(this.terminalHost);const r=v(i.proposeDimensions());return t.dispose(),r}applyTerminalTheme(){this.terminal!==void 0&&(this.terminal.options.theme=g(this))}send(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify(e))}disposeTerminalView(){this.resizeObserver?.disconnect(),this.resizeObserver=void 0,this.socket?.close(),this.socket=void 0,this.terminal?.dispose(),this.terminal=void 0,this.fitAddon=void 0}render(){return c`
|
|
5
|
+
<section class="terminal-shell">
|
|
6
|
+
<div class="terminal-tabs">
|
|
7
|
+
${this.terminals.map(e=>c`
|
|
8
|
+
<button class=${this.selectedId===e.id?"selected":""} @click=${()=>{this.selectTerminal(e.id)}}>
|
|
9
|
+
<span>${e.name}${e.exited?" · exited":""}</span>
|
|
10
|
+
<small @click=${t=>{this.closeTerminal(e.id,t)}}>×</small>
|
|
11
|
+
</button>
|
|
12
|
+
`)}
|
|
13
|
+
<button class="new" ?disabled=${this.workspace===void 0} @click=${()=>{this.startTerminal()}}>+ Shell</button>
|
|
14
|
+
</div>
|
|
15
|
+
${this.error===void 0?null:c`<p class="error">${this.error}</p>`}
|
|
16
|
+
${this.loading?c`<p class="muted">Loading terminals…</p>`:null}
|
|
17
|
+
<div class="terminal-host"></div>
|
|
18
|
+
</section>
|
|
19
|
+
`}};n.styles=y`
|
|
20
|
+
:host { flex: 1 1 auto; min-height: 0; display: flex; }
|
|
21
|
+
.terminal-shell { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; overflow: hidden; background: var(--pi-terminal-bg); }
|
|
22
|
+
.terminal-tabs { flex: 0 0 auto; display: flex; gap: 6px; align-items: center; padding: 6px; border-bottom: 1px solid var(--pi-border-muted); background: var(--pi-bg); overflow: auto; }
|
|
23
|
+
button { display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 180px; border: 1px solid var(--pi-border); border-radius: 7px; background: var(--pi-surface); color: var(--pi-text); padding: 5px 7px; cursor: pointer; }
|
|
24
|
+
button.selected { border-color: var(--pi-accent); background: var(--pi-selection-bg); }
|
|
25
|
+
button.new { flex: 0 0 auto; color: var(--pi-muted); }
|
|
26
|
+
button span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
27
|
+
button small { color: var(--pi-muted); font-size: 14px; line-height: 1; }
|
|
28
|
+
button small:hover { color: var(--pi-danger); }
|
|
29
|
+
button:disabled { opacity: .5; cursor: not-allowed; }
|
|
30
|
+
.terminal-host { flex: 1 1 auto; min-height: 0; padding: 6px; box-sizing: border-box; overflow: hidden; }
|
|
31
|
+
.terminal-host .xterm { height: 100%; cursor: text; position: relative; user-select: none; }
|
|
32
|
+
.terminal-host .xterm.focus, .terminal-host .xterm:focus { outline: none; }
|
|
33
|
+
.terminal-host .xterm-helpers { position: absolute; top: 0; z-index: 5; }
|
|
34
|
+
.terminal-host .xterm-helper-textarea { position: absolute !important; left: -9999em !important; top: 0 !important; width: 0 !important; height: 0 !important; min-width: 0 !important; min-height: 0 !important; padding: 0 !important; border: 0 !important; margin: 0 !important; opacity: 0 !important; z-index: -5 !important; white-space: nowrap !important; overflow: hidden !important; resize: none !important; outline: 0 !important; appearance: none !important; }
|
|
35
|
+
.terminal-host .xterm-viewport { position: absolute; inset: 0; overflow-y: scroll; cursor: default; background-color: var(--pi-terminal-bg); }
|
|
36
|
+
.terminal-host .xterm-screen { position: relative; }
|
|
37
|
+
.terminal-host .xterm-screen canvas { position: absolute; left: 0; top: 0; }
|
|
38
|
+
.terminal-host .xterm-char-measure-element { display: inline-block; visibility: hidden; position: absolute; top: 0; left: -9999em; line-height: normal; }
|
|
39
|
+
.terminal-host .xterm-accessibility:not(.debug), .terminal-host .xterm-message { position: absolute; inset: 0; z-index: 10; color: transparent; pointer-events: none; }
|
|
40
|
+
.terminal-host .xterm-accessibility-tree:not(.debug) *::selection { color: transparent; }
|
|
41
|
+
.terminal-host .xterm-accessibility-tree { font-family: monospace; user-select: text; white-space: pre; }
|
|
42
|
+
.terminal-host .xterm-accessibility-tree > div { transform-origin: left; width: fit-content; }
|
|
43
|
+
.terminal-host .live-region { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
|
|
44
|
+
.error { flex: 0 0 auto; margin: 0; padding: 8px; color: var(--pi-danger); border-bottom: 1px solid var(--pi-border); background: var(--pi-surface); }
|
|
45
|
+
.muted { margin: 10px; color: var(--pi-muted); }
|
|
46
|
+
.xterm { height: 100%; }
|
|
47
|
+
`;a([b({attribute:!1})],n.prototype,"workspace",2);a([b({type:Boolean})],n.prototype,"autoStart",2);a([T(".terminal-host")],n.prototype,"terminalHost",2);a([d()],n.prototype,"terminals",2);a([d()],n.prototype,"selectedId",2);a([d()],n.prototype,"loading",2);a([d()],n.prototype,"error",2);a([d()],n.prototype,"visible",2);n=a([k("terminal-panel")],n);function A(e){const t=JSON.parse(e);if(!N(t))return{type:"error",message:"Invalid terminal message"};const i=t;return i.type==="output"&&typeof i.data=="string"?{type:"output",data:i.data,...typeof i.replay=="boolean"?{replay:i.replay}:{}}:i.type==="exit"?{type:"exit",...typeof i.exitCode=="number"?{exitCode:i.exitCode}:{}}:i.type==="error"&&typeof i.message=="string"?{type:"error",message:i.message}:{type:"error",message:"Invalid terminal message"}}function C(e){return e.replaceAll("\x1B[I","").replaceAll("\x1B[O","")}async function $(e){return typeof e=="string"?e:e instanceof ArrayBuffer?new TextDecoder().decode(e):e instanceof Blob?await e.text():String(e)}function f(e){return{...I,theme:g(e)}}function g(e){return{background:p(e,"--pi-terminal-bg","#05070a"),foreground:p(e,"--pi-terminal-text","#e6edf3"),cursor:p(e,"--pi-accent","#58a6ff"),selectionBackground:p(e,"--pi-terminal-selection","#264f78")}}function p(e,t,i){const r=getComputedStyle(e).getPropertyValue(t).trim();return r===""?i:r}function v(e){if(!(e===void 0||!E(e.cols,e.rows)))return{cols:Math.floor(e.cols),rows:Math.floor(e.rows)}}function E(e,t){return Number.isFinite(e)&&Number.isFinite(t)&&e>0&&t>0}function N(e){return typeof e=="object"&&e!==null}export{n as TerminalPanel,C as filterTerminalInput};
|