pi-desktop-ui 1.0.1

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 ADDED
@@ -0,0 +1,129 @@
1
+ # Pi Desktop UI
2
+
3
+ A native desktop GUI for [pi](https://github.com/mariozechner/pi-coding-agent) — a fully functional chat window that mirrors your terminal session with real-time streaming, markdown rendering, and workspace management.
4
+
5
+ ![Pi Desktop - Chat View](assets/Screenshot_1.png)
6
+
7
+ ![Pi Desktop - Code Diffs & Tools](assets/Screenshot_2.png)
8
+
9
+ ## Features
10
+
11
+ - **Full bidirectional chat** — send messages from the window or the terminal, both stay in sync
12
+ - **Real-time streaming** — assistant responses stream token-by-token with thinking indicators
13
+ - **Markdown rendering** with syntax-highlighted code blocks
14
+ - **Mermaid diagrams** — flowcharts, sequence diagrams, state machines, and more rendered as SVG (neutral theme, dark mode adapted via CSS)
15
+ - **LaTeX math** — inline `$...$` and block `$$...$$` formulas rendered via KaTeX
16
+ - **Cancel streaming** — stop button replaces send during streaming, Escape / Ctrl+C keyboard shortcuts
17
+ - **Tool execution display** — see tool calls (bash, read, edit, write) with inline diffs for edits
18
+ - **Sidebar navigation:**
19
+ - **Threads** — browse and switch between session threads
20
+ - **Skills & Extensions** — view installed skills and loaded extensions
21
+ - **Settings** — model info, token stats, cost tracking
22
+ - **Explorer** — browse project files
23
+ - **Workspaces** — navigate across all pi workspaces and their sessions
24
+ - **Slash command palette** — access all pi commands from the window
25
+ - **File attachments** — drag & drop or attach files to messages
26
+ - **Custom footer & widget** in the terminal showing project, branch, model, and token stats
27
+
28
+ ## Installation
29
+
30
+ ### From Git (recommended)
31
+
32
+ ```bash
33
+ pi install git:github.com/pvjagtap/pi-desktop-ui
34
+ ```
35
+
36
+ ### Manual
37
+
38
+ Clone the repo into your pi extensions directory:
39
+
40
+ ```bash
41
+ git clone https://github.com/pvjagtap/pi-desktop-ui ~/.pi/agent/extensions/pi-desktop-ui
42
+ cd ~/.pi/agent/extensions/pi-desktop-ui
43
+ npm install
44
+ ```
45
+
46
+ Then reload pi:
47
+
48
+ ```
49
+ /reload
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ ### Auto-open on startup
55
+
56
+ ```bash
57
+ pi --desktop
58
+ ```
59
+
60
+ This launches pi and automatically opens the desktop window. You can also create a shortcut using the included `pi-desktop.cmd` (Windows) or `pi-desktop.sh` (macOS/Linux) scripts.
61
+
62
+ ### Open manually during a session
63
+
64
+ | Method | Action |
65
+ |--------|--------|
66
+ | `Ctrl+Alt+N` | Keyboard shortcut |
67
+ | `/desktop` | Slash command |
68
+ | `/nav` | Slash command (alias) |
69
+
70
+ The window opens alongside your terminal — type in either place. Messages, tool calls, and responses are synced in real-time.
71
+
72
+ ## Security
73
+
74
+ The extension implements multiple layers of defense:
75
+
76
+ - **XSS prevention** — all markdown output is sanitized through [DOMPurify](https://github.com/cure53/DOMPurify) with a strict tag/attribute allowlist
77
+ - **Content Security Policy** — restrictive CSP blocks inline scripts from external sources, disables `connect-src`, `object-src`, and `form-action`
78
+ - **Subresource Integrity** — CDN dependencies (marked, highlight.js, DOMPurify) are loaded with `integrity` hashes to prevent tampering
79
+ - **Pinned dependencies** — all CDN scripts are pinned to exact versions
80
+ - **Path traversal protection** — file explorer, thread viewer, and workspace handlers validate that paths stay within allowed directories (cwd and `~/.pi/agent/sessions/`)
81
+ - **Attachment limits** — file uploads are capped at 25 MB with sanitized filenames
82
+ - **Input validation** — message length caps, strict shape validation on persisted config, and traversal rejection on all user-supplied paths
83
+
84
+ ## Dependencies
85
+
86
+ - [glimpseui](https://github.com/nickarrow/glimpseui) — native webview windows from Node.js
87
+ - [marked](https://github.com/markedjs/marked) — markdown parsing
88
+ - [mermaid](https://github.com/mermaid-js/mermaid) — diagram rendering (loaded via CDN)
89
+ - [KaTeX](https://github.com/KaTeX/KaTeX) — LaTeX math rendering (loaded via CDN)
90
+ - [DOMPurify](https://github.com/cure53/DOMPurify) — HTML sanitization (loaded via CDN)
91
+ - [highlight.js](https://github.com/highlightjs/highlight.js) — syntax highlighting (loaded via CDN)
92
+ - [ws](https://github.com/websockets/ws) — WebSocket support
93
+
94
+ ## Requirements
95
+
96
+ - [pi](https://github.com/mariozechner/pi-coding-agent) coding agent
97
+ - The [glimpse](https://github.com/nickarrow/glimpseui) skill/package must be available (provides the `glimpseui` module)
98
+
99
+ ## How It Works
100
+
101
+ The extension hooks into pi's event system to capture the full agent lifecycle:
102
+
103
+ 1. **`session_start`** — initializes the custom footer and context widget
104
+ 2. **`message_start` / `message_update` / `message_end`** — streams assistant responses to the window
105
+ 3. **`tool_execution_start` / `tool_execution_end`** — displays tool calls with arguments and results
106
+ 4. **`agent_start` / `agent_end`** — shows loading states and updates token stats
107
+
108
+ User messages typed in the desktop window are sent to pi via `pi.sendUserMessage()`, and the response streams back through the same event hooks.
109
+
110
+ ## Package Structure
111
+
112
+ ```
113
+ pi-desktop-ui/
114
+ ├── assets/
115
+ │ ├── Screenshot_1.png
116
+ │ └── Screenshot_2.png
117
+ ├── web/
118
+ │ ├── index.html # Desktop window HTML shell
119
+ │ └── app.js # Frontend app (sidebar, chat, explorer, themes)
120
+ ├── index.ts # Extension entry point (events, commands, window management)
121
+ ├── package.json # Pi package manifest + dependencies
122
+ ├── pi-desktop.cmd # Windows launcher shortcut
123
+ ├── pi-desktop.sh # macOS/Linux launcher shortcut
124
+ └── README.md
125
+ ```
126
+
127
+ ## License
128
+
129
+ MIT
Binary file
Binary file
Binary file
Binary file
package/diagrams.md ADDED
@@ -0,0 +1,291 @@
1
+ # Pi Desktop UI — Architecture Diagrams
2
+
3
+ ## 1. Extension Lifecycle
4
+
5
+ ```mermaid
6
+ stateDiagram-v2
7
+ [*] --> Registered: pi loads extension
8
+ Registered --> SessionStart: session_start event
9
+ SessionStart --> FooterEnabled: enableFooter()
10
+ FooterEnabled --> WidgetEnabled: enableWidget()
11
+ WidgetEnabled --> Ready: setStatus("desktop")
12
+
13
+ state Ready {
14
+ [*] --> Idle
15
+ Idle --> WindowOpen: Ctrl+Alt+N / /desktop / /nav / --desktop flag
16
+ WindowOpen --> Idle: window closed
17
+ WindowOpen --> WindowOpen: session_start (reload/new/resume/fork)
18
+ }
19
+
20
+ Ready --> Shutdown: session_shutdown
21
+ Shutdown --> [*]: closeActiveWindow()
22
+ ```
23
+
24
+ ## 2. High-Level Architecture
25
+
26
+ ```mermaid
27
+ graph TB
28
+ subgraph Terminal["Pi Terminal - TUI"]
29
+ PiAgent["Pi Agent Core"]
30
+ Footer["Custom Footer"]
31
+ Widget["Context Widget"]
32
+ Status["Status Bar"]
33
+ end
34
+
35
+ subgraph Extension["index.ts Extension"]
36
+ EventHandlers["Event Handlers"]
37
+ WindowManager["Window Manager"]
38
+ DataCollectors["Data Collectors"]
39
+ Security["Security Layer"]
40
+ CommandReg["Command Registration"]
41
+ HTMLBuilder["HTML Builder"]
42
+ end
43
+
44
+ subgraph DesktopWindow["Glimpse Native Window"]
45
+ IndexHTML["web/index.html"]
46
+ Sidebar["Sidebar Navigation"]
47
+ ChatView["Chat View"]
48
+ ExplorerView["File Explorer"]
49
+ SkillsView["Skills View"]
50
+ SettingsView["Settings View"]
51
+ WorkspaceView["Workspace View"]
52
+ DiffOverlay["Diff Overlay"]
53
+ CommandPalette["Command Palette"]
54
+ StreamingUI["Streaming UI"]
55
+ end
56
+
57
+ PiAgent -->|"pi.on() events"| EventHandlers
58
+ EventHandlers -->|"sendToWindow()"| WindowManager
59
+ WindowManager -->|"glimpse.send(js)"| DesktopWindow
60
+ DesktopWindow -->|"glimpse.on(message)"| WindowManager
61
+ WindowManager -->|"handleWindowMessage()"| DataCollectors
62
+ DataCollectors -->|"sendToWindow()"| WindowManager
63
+ CommandReg -->|"registerCommand"| PiAgent
64
+ HTMLBuilder -->|"buildDesktopHtml()"| WindowManager
65
+ Extension --> Footer
66
+ Extension --> Widget
67
+ Extension --> Status
68
+ Security -.->|validates| DataCollectors
69
+ ```
70
+
71
+ ## 3. Message Streaming Flow
72
+
73
+ ```mermaid
74
+ sequenceDiagram
75
+ participant User as User (Terminal or Window)
76
+ participant Pi as Pi Agent
77
+ participant Ext as Extension (index.ts)
78
+ participant Win as Desktop Window (app.js)
79
+
80
+ User->>Win: Types message + Enter
81
+ Win->>Ext: { type: "send-message", text }
82
+ Ext->>Pi: pi.sendUserMessage(text)
83
+
84
+ Pi->>Ext: agent_start event
85
+ Ext->>Win: { type: "agent-start" }
86
+ Note over Win: Show waiting indicator
87
+
88
+ Pi->>Ext: message_start (assistant)
89
+ Ext->>Win: { type: "message-start" }
90
+
91
+ loop Thinking (optional)
92
+ Pi->>Ext: message_update (thinking_start)
93
+ Ext->>Win: { type: "thinking-start" }
94
+ Pi->>Ext: message_update (thinking_delta)
95
+ Ext->>Win: { type: "thinking-chunk", text }
96
+ Pi->>Ext: message_update (thinking_end)
97
+ Ext->>Win: { type: "thinking-end" }
98
+ end
99
+
100
+ loop Text streaming
101
+ Pi->>Ext: message_update (text_start)
102
+ Ext->>Win: { type: "message-chunk-start" }
103
+ Pi->>Ext: message_update (text_delta)
104
+ Ext->>Win: { type: "message-chunk", text }
105
+ Pi->>Ext: message_update (text_end)
106
+ Ext->>Win: { type: "message-chunk-end" }
107
+ end
108
+
109
+ Pi->>Ext: message_end (assistant)
110
+ Ext->>Win: { type: "message-end", content }
111
+ Note over Win: Append final message
112
+
113
+ Pi->>Ext: agent_end event
114
+ Ext->>Win: { type: "agent-end" }
115
+ Ext->>Win: { type: "stats-update", stats }
116
+ Note over Win: Update token stats
117
+ ```
118
+
119
+ ## 4. Tool Execution Flow
120
+
121
+ ```mermaid
122
+ sequenceDiagram
123
+ participant Pi as Pi Agent
124
+ participant Ext as Extension
125
+ participant Win as Desktop Window
126
+
127
+ Pi->>Ext: tool_execution_start
128
+ Note over Ext: Format args display<br/>(bash→command, read→path,<br/>edit→diffs as base64)
129
+
130
+ alt Plan Mode Active & Write Tool
131
+ Ext->>Win: { type: "plan-mode-violation" }
132
+ Note over Win: Show warning toast
133
+ end
134
+
135
+ Ext->>Win: { type: "tool-start", toolName,<br/>toolCallId, argsDisplay,<br/>editDiffsB64, editPath }
136
+ Note over Win: Show spinner + tool card
137
+
138
+ Pi->>Ext: tool_execution_end
139
+ Ext->>Win: { type: "tool-end", toolCallId,<br/>isError, resultText }
140
+ Note over Win: Update card in-place<br/>(preserves details open state)
141
+ ```
142
+
143
+ ## 5. Window ↔ Extension Communication Protocol
144
+
145
+ ```mermaid
146
+ graph LR
147
+ subgraph "Window → Extension Messages"
148
+ SM["send-message"] --> |text| A1[pi.sendUserMessage]
149
+ OT["open-thread"] --> |file| A2[extractThreadMessages]
150
+ NAV["nav"] --> |action| A3[getDirEntries]
151
+ ETE["explorer-tree-expand"] --> |path| A4[getDirEntries + validate]
152
+ EO["explorer-open"] --> |path| A5[readFile / openApp]
153
+ GC["get-commands"] --> A6[getAllCommands]
154
+ GS["get-stats"] --> A7[getTokenStats]
155
+ RT["refresh-threads"] --> A8[getSessionThreads]
156
+ RS["refresh-skills"] --> A9[getSkills + getExtensions]
157
+ GW["get-workspaces"] --> A10[getWorkspaces]
158
+ GWS["get-workspace-sessions"] --> A11[getWorkspaceSessions]
159
+ ST["search-threads"] --> A12[searchSessionThreads]
160
+ OFP["open-folder-path"] --> A13[resolve + validate path]
161
+ SPM["set-plan-mode"] --> A14[toggle planMode]
162
+ AF["attach-file"] --> A15[write to tmp]
163
+ CS["cancel-streaming"] --> A16[ctx.abort]
164
+ SHW["set-hidden-workspaces"] --> A17[saveHiddenWorkspaces]
165
+ CL["close"] --> A18[closeActiveWindow]
166
+ end
167
+ ```
168
+
169
+ ```mermaid
170
+ graph LR
171
+ subgraph "Extension → Window Messages"
172
+ B1["agent-start / agent-end"]
173
+ B2["message-start / chunk / end"]
174
+ B3["thinking-start / chunk / end"]
175
+ B4["tool-start / tool-end"]
176
+ B5["stats-update"]
177
+ B6["thread-messages"]
178
+ B7["explorer-data / tree-children"]
179
+ B8["file-content"]
180
+ B9["commands-list"]
181
+ B10["update-threads / update-skills"]
182
+ B11["workspaces-list / workspace-sessions"]
183
+ B12["search-results"]
184
+ B13["workspace-opened"]
185
+ B14["file-attached-ack"]
186
+ B15["plan-mode-violation"]
187
+ B16["session-changed"]
188
+ B17["provider-request"]
189
+ end
190
+ ```
191
+
192
+ ## 6. Sidebar Navigation State Machine
193
+
194
+ ```mermaid
195
+ stateDiagram-v2
196
+ [*] --> Threads: default view
197
+
198
+ Threads --> Skills: click Skills nav
199
+ Threads --> Settings: click Settings nav
200
+ Threads --> Explorer: click Explorer nav
201
+ Threads --> WorkspaceModal: click Open Workspace nav
202
+
203
+ Skills --> Threads: click Threads nav
204
+ Settings --> Threads: click Threads nav
205
+ Explorer --> Threads: click Explorer tree back
206
+ WorkspaceModal --> Threads: close modal
207
+
208
+ state Threads {
209
+ [*] --> CurrentSession
210
+ CurrentSession --> OldThread: click thread in sidebar
211
+ OldThread --> CurrentSession: Escape key
212
+ CurrentSession --> SearchResults: type in search box
213
+ SearchResults --> OldThread: click search result
214
+ SearchResults --> CurrentSession: clear search
215
+ }
216
+
217
+ state Explorer {
218
+ [*] --> FileTree
219
+ FileTree --> FileViewer: click file
220
+ FileViewer --> FileTree: Back button
221
+ FileTree --> FileTree: expand/collapse dirs
222
+ }
223
+
224
+ state WorkspaceModal {
225
+ [*] --> BrowseWorkspaces
226
+ BrowseWorkspaces --> OpenFolder: enter path
227
+ BrowseWorkspaces --> ExpandWorkspace: click workspace
228
+ }
229
+ ```
230
+
231
+ ## 7. Data Flow on Window Open
232
+
233
+ ```mermaid
234
+ flowchart TD
235
+ A[openDesktopWindow called] --> B{window already open?}
236
+ B -->|Yes| C[Notify warning & return]
237
+ B -->|No| D[collectWindowData]
238
+
239
+ D --> D1[getTokenStats]
240
+ D --> D2[getSessionThreads]
241
+ D --> D3[getSkills]
242
+ D --> D4[getExtensions]
243
+ D --> D5[getWorkspaces]
244
+ D --> D6[extractSessionMessages]
245
+ D --> D7[getDirEntries cwd]
246
+ D --> D8[getAllCommands]
247
+ D --> D9[loadHiddenWorkspaces]
248
+
249
+ D1 & D2 & D3 & D4 & D5 & D6 & D7 & D8 & D9 --> E[buildDesktopHtml]
250
+
251
+ E --> E1["Read index.html template"]
252
+ E --> E2["Read app.js"]
253
+ E --> E3["JSON.stringify data → Base64"]
254
+ E1 & E2 & E3 --> E4["Replace __INLINE_DATA__ and __INLINE_JS__"]
255
+
256
+ E4 --> F["glimpseui.open(html, opts)"]
257
+ F --> G["Register event listeners:<br/>on('message') → handleWindowMessage<br/>on('closed') → cleanup<br/>on('error') → cleanup"]
258
+ G --> H["Set hidden thinking label<br/>setHiddenThinkingLabel()"]
259
+ H --> I[Window Ready — bidirectional sync active]
260
+ ```
261
+
262
+ ## 8. Security Model
263
+
264
+ ```mermaid
265
+ flowchart TD
266
+ subgraph Input["User Input Validation"]
267
+ V1["Message length: max 100K chars"]
268
+ V2["Search query: 2-200 chars"]
269
+ V3["Attachment: max 25MB"]
270
+ V4["Path: no '..' traversal"]
271
+ V5["Filename: sanitized chars only"]
272
+ end
273
+
274
+ subgraph PathSec["Path Security"]
275
+ P1["isPathAllowed():<br/>must be under cwd or<br/>~/.pi/agent/sessions/"]
276
+ P2["isValidSessionFile():<br/>must end .jsonl +<br/>under sessions dir +<br/>no '..' in path"]
277
+ P3["Workspace dirName:<br/>reject / \\ chars"]
278
+ P4["open-folder-path:<br/>resolve + normalize +<br/>verify under sessions root"]
279
+ end
280
+
281
+ subgraph Output["Output Security"]
282
+ O1["DOMPurify: strict tag allowlist"]
283
+ O2["CSP: no connect-src,<br/>no object-src, no form-action"]
284
+ O3["SRI: integrity hashes on CDN"]
285
+ O4["escapeNonAscii():<br/>\\uXXXX encoding for<br/>Windows CP1252 safety"]
286
+ O5["Base64 data transfer:<br/>survives webview bridge"]
287
+ end
288
+
289
+ Input --> PathSec
290
+ PathSec --> Output
291
+ ```