@jmfederico/pi-web 1.202605.2 → 1.202605.3

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.
Files changed (38) hide show
  1. package/README.md +3 -2
  2. package/dist/client/assets/CodeViewer-D5n6Fp_3.js +4 -0
  3. package/dist/client/assets/TerminalPanel-puMKOnRK.js +47 -0
  4. package/dist/client/assets/index-DcHlUdoz.js +805 -0
  5. package/dist/client/assets/vendor-editor-core-B4Sq6exx.js +12 -0
  6. package/dist/client/assets/{vendor-editor-languages-Cjllm-a8.js → vendor-editor-languages-DznYbTkJ.js} +9 -9
  7. package/dist/client/index.html +3 -1
  8. package/dist/server/app.js +9 -0
  9. package/dist/server/app.js.map +1 -1
  10. package/dist/server/piWebPluginService.js +221 -0
  11. package/dist/server/piWebPluginService.js.map +1 -0
  12. package/dist/server/realtime/sessionEventHub.js +3 -0
  13. package/dist/server/realtime/sessionEventHub.js.map +1 -1
  14. package/dist/server/sessiond/sessionProxyRoutes.js +5 -13
  15. package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
  16. package/dist/server/sessiond.js +1 -1
  17. package/dist/server/sessiond.js.map +1 -1
  18. package/dist/server/sessions/messagePaging.js +43 -0
  19. package/dist/server/sessions/messagePaging.js.map +1 -0
  20. package/dist/server/sessions/piSessionService.js +118 -28
  21. package/dist/server/sessions/piSessionService.js.map +1 -1
  22. package/dist/server/sessions/sessionCommandService.js +2 -0
  23. package/dist/server/sessions/sessionCommandService.js.map +1 -1
  24. package/dist/server/sessions/sessionRoutes.js +60 -0
  25. package/dist/server/sessions/sessionRoutes.js.map +1 -1
  26. package/dist/server/terminals/terminalRoutes.js +1 -1
  27. package/dist/server/terminals/terminalRoutes.js.map +1 -1
  28. package/dist/server/terminals/terminalService.js +12 -4
  29. package/dist/server/terminals/terminalService.js.map +1 -1
  30. package/dist/server/workspaces/pathSafety.js +8 -1
  31. package/dist/server/workspaces/pathSafety.js.map +1 -1
  32. package/package.json +4 -2
  33. package/pi-web-plugins/info/package.json +8 -0
  34. package/pi-web-plugins/info/pi-web-plugin.js +42 -0
  35. package/dist/client/assets/CodeViewer-DsXI9VCn.js +0 -4
  36. package/dist/client/assets/TerminalPanel-CpzJEFv1.js +0 -47
  37. package/dist/client/assets/index-Cbr8EG8h.js +0 -687
  38. package/dist/client/assets/vendor-editor-core-hulUn3GY.js +0 -12
package/README.md CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  ![Pi Web](docs/assets/pi-web-banner.png)
4
4
 
5
- ![Pi Web demo](docs/assets/pi-web-demo.gif)
6
-
7
5
  **Run AI coding agents on your own machine or server, keep them alive in real workspaces, and control everything from a browser.**
8
6
 
9
7
  Pi Web is a web control plane for [Pi Coding Agent](https://github.com/earendil-works/pi/tree/main/packages/coding-agent). Add your repositories once, open project workspaces and git worktrees, start agent sessions inside them, and come back later without losing the work. Your browser becomes the cockpit; your server becomes the persistent development environment. Start on your laptop, check in from your phone, and continue from an iPad or another machine whenever that is the device you have at hand.
10
8
 
9
+ ![Pi Web demo](docs/assets/pi-web-demo.gif)
10
+
11
11
  With Pi Web you can:
12
12
 
13
13
  - launch and supervise multiple coding-agent sessions in parallel;
@@ -53,6 +53,7 @@ This maps naturally to real development work:
53
53
  - View live session status: streaming, compaction, bash activity, token usage, cost, model, and context usage.
54
54
  - Send prompts, shell input, and supported commands through the Pi SDK path.
55
55
  - Reuse your existing Pi auth and model configuration from `~/.pi/agent`.
56
+ - Extend the UI with trusted plugins that add actions, workspace panels, and workspace-label metadata. See [Plugin API](docs/plugins.md) for LLM-friendly plugin-building docs.
56
57
 
57
58
  ## Architecture
58
59
 
@@ -0,0 +1,4 @@
1
+ import{w as a,J as d,$ as h,A as f,X as m,U as g,_ as v,a0 as y}from"./vendor-editor-core-B4Sq6exx.js";import{g as w,r as b,p as C,h as j,c as x,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 p,e as P,t as L}from"./index-DcHlUdoz.js";var D=Object.defineProperty,F=Object.getOwnPropertyDescriptor,n=(e,o,i,s)=>{for(var t=s>1?void 0:s?F(o,i):o,c=e.length-1,l;c>=0;c--)(l=e[c])&&(t=(s?l(o,i,t):l(t))||t);return s&&t&&D(o,i,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:d.create({doc:this.content,extensions:[h(),f.of(m),g(v,{fallback:!0}),d.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
+ `;n([p()],r.prototype,"content",2);n([p()],r.prototype,"language",2);n([P(".host")],r.prototype,"editorHost",2);r=n([L("code-viewer")],r);const M=a.theme({"&":{height:"100%",color:"#e6edf3",backgroundColor:"#0d1117",fontSize:"12px"},".cm-scroller":{fontFamily:"ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",lineHeight:"1.45"},".cm-gutters":{backgroundColor:"#0d1117",color:"#6e7681",borderRight:"1px solid #21262d"},".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[x()];case"html":return[j()];case"python":return[C()];case"rust":return[b()];case"go":return[w()];case"diff":return[y.define(E)];default:return[]}}export{r as CodeViewer};
@@ -0,0 +1,47 @@
1
+ import{i as m,c as p,d as u,b as d,a as f,n as h,e as b,r as l,t as v}from"./index-DcHlUdoz.js";import{D as w,o as g}from"./vendor-terminal-DjQ08hXu.js";import"./vendor-editor-core-B4Sq6exx.js";import"./vendor-editor-languages-DznYbTkJ.js";var x=Object.defineProperty,y=Object.getOwnPropertyDescriptor,n=(e,i,t,r)=>{for(var s=r>1?void 0:r?y(i,t):i,a=e.length-1,c;a>=0;a--)(c=e[a])&&(s=(r?c(i,t,s):c(s))||s);return r&&s&&x(i,t,s),s};let o=class extends m{constructor(){super(...arguments),this.autoStart=!1,this.terminals=[],this.loading=!1,this.visible=!1,this.suppressTerminalInput=!1}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.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 p.terminals(this.workspace.projectId,this.workspace.id);this.terminals=e,this.selectedId=e.find(i=>!i.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=await p.startTerminal(this.workspace.projectId,this.workspace.id,{cols:100,rows:30});this.terminals=[...this.terminals,e],this.selectTerminal(e.id)}catch(e){this.error=e instanceof Error?e.message:String(e)}}}async closeTerminal(e,i){i.stopPropagation();try{if(this.workspace===void 0)return;await p.closeTerminal(this.workspace.projectId,this.workspace.id,e);const t=this.terminals.filter(r=>r.id!==e);this.terminals=t,this.selectedId===e&&(this.selectedId=t[0]?.id,this.disposeTerminalView())}catch(t){this.error=t instanceof Error?t.message:String(t)}}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 i=new w({cursorBlink:!0,convertEol:!0,fontFamily:"ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",fontSize:13,theme:{background:"#05070a",foreground:"#e6edf3",cursor:"#58a6ff",selectionBackground:"#264f78"}}),t=new g;i.loadAddon(t),i.open(this.terminalHost),this.terminal=i,this.fitAddon=t,this.resizeObserver=new ResizeObserver(()=>{this.fitAndNotify()}),this.resizeObserver.observe(this.terminalHost),i.onData(r=>{if(this.suppressTerminalInput)return;const s=T(r);s!==""&&this.send({type:"input",data:s})}),this.connectSocket(e.projectId,e.id,this.selectedId,i),requestAnimationFrame(()=>{this.fitAndNotify()}),i.focus()}connectSocket(e,i,t,r){const s=u(e,i,t);s.binaryType="arraybuffer",this.socket=s,s.addEventListener("open",()=>{this.fitAndNotify()}),s.addEventListener("message",a=>{this.handleSocketMessage(a.data,t,r)}),s.addEventListener("close",()=>{this.socket===s&&(this.socket=void 0)})}async handleSocketMessage(e,i,t){try{const r=k(await I(e));r.type==="output"&&this.writeTerminalOutput(t,r.data,r.replay===!0),r.type==="exit"&&(t.writeln(`\r
2
+ [process exited${r.exitCode===void 0?"":` with code ${String(r.exitCode)}`}]`),this.terminals=this.terminals.map(s=>s.id===i?{...s,exited:!0,...r.exitCode===void 0?{}:{exitCode:r.exitCode}}:s)),r.type==="error"&&t.writeln(`\r
3
+ [terminal error: ${r.message}]`)}catch(r){t.writeln(`\r
4
+ [terminal error: ${r instanceof Error?r.message:String(r)}]`)}}writeTerminalOutput(e,i,t){if(!t){e.write(i);return}this.suppressTerminalInput=!0,e.write(i,()=>{this.suppressTerminalInput=!1})}fitAndNotify(){this.fitAddon===void 0||this.terminal===void 0||(this.fitAddon.fit(),this.send({type:"resize",cols:this.terminal.cols,rows:this.terminal.rows}))}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 d`
5
+ <section class="terminal-shell">
6
+ <div class="terminal-tabs">
7
+ ${this.terminals.map(e=>d`
8
+ <button class=${this.selectedId===e.id?"selected":""} @click=${()=>{this.selectTerminal(e.id)}}>
9
+ <span>${e.name}${e.exited?" · exited":""}</span>
10
+ <small @click=${i=>{this.closeTerminal(e.id,i)}}>×</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:d`<p class="error">${this.error}</p>`}
16
+ ${this.loading?d`<p class="muted">Loading terminals…</p>`:null}
17
+ <div class="terminal-host"></div>
18
+ </section>
19
+ `}};o.styles=f`
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: #05070a; }
22
+ .terminal-tabs { flex: 0 0 auto; display: flex; gap: 6px; align-items: center; padding: 6px; border-bottom: 1px solid #21262d; background: #0d1117; overflow: auto; }
23
+ button { display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 180px; border: 1px solid #30363d; border-radius: 7px; background: #161b22; color: #e6edf3; padding: 5px 7px; cursor: pointer; }
24
+ button.selected { border-color: #58a6ff; background: #0d2847; }
25
+ button.new { flex: 0 0 auto; color: #8b949e; }
26
+ button span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
27
+ button small { color: #8b949e; font-size: 14px; line-height: 1; }
28
+ button small:hover { color: #ff7b72; }
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: #05070a; }
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: #ff7b72; border-bottom: 1px solid #30363d; background: #161b22; }
45
+ .muted { margin: 10px; color: #8b949e; }
46
+ .xterm { height: 100%; }
47
+ `;n([h({attribute:!1})],o.prototype,"workspace",2);n([h({type:Boolean})],o.prototype,"autoStart",2);n([b(".terminal-host")],o.prototype,"terminalHost",2);n([l()],o.prototype,"terminals",2);n([l()],o.prototype,"selectedId",2);n([l()],o.prototype,"loading",2);n([l()],o.prototype,"error",2);n([l()],o.prototype,"visible",2);o=n([v("terminal-panel")],o);function k(e){const i=JSON.parse(e);if(!S(i))return{type:"error",message:"Invalid terminal message"};const t=i;return t.type==="output"&&typeof t.data=="string"?{type:"output",data:t.data,...typeof t.replay=="boolean"?{replay:t.replay}:{}}:t.type==="exit"?{type:"exit",...typeof t.exitCode=="number"?{exitCode:t.exitCode}:{}}:t.type==="error"&&typeof t.message=="string"?{type:"error",message:t.message}:{type:"error",message:"Invalid terminal message"}}function T(e){return e.replaceAll("\x1B[I","").replaceAll("\x1B[O","")}async function I(e){return typeof e=="string"?e:e instanceof ArrayBuffer?new TextDecoder().decode(e):e instanceof Blob?await e.text():String(e)}function S(e){return typeof e=="object"&&e!==null}export{o as TerminalPanel,T as filterTerminalInput};