pi-yun 0.2.0 → 0.2.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.
Files changed (143) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +18 -18
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/react-loadable-manifest.json +1 -1
  5. package/.next/required-server-files.js +1 -1
  6. package/.next/required-server-files.json +1 -1
  7. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/server/app/_not-found.html +1 -1
  15. package/.next/server/app/_not-found.rsc +4 -4
  16. package/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
  17. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +4 -4
  18. package/.next/server/app/_not-found.segments/_tree.segment.rsc +3 -3
  19. package/.next/server/app/api/agent/[id]/events/route.js +1 -1
  20. package/.next/server/app/api/agent/[id]/route.js +1 -1
  21. package/.next/server/app/api/agent/new/route.js +1 -1
  22. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  23. package/.next/server/app/api/agent/running/route.js +1 -1
  24. package/.next/server/app/api/agent/running/route.js.nft.json +1 -1
  25. package/.next/server/app/api/app-update/route.js +1 -1
  26. package/.next/server/app/api/cwd/browse/route.js.nft.json +2 -2
  27. package/.next/server/app/api/cwd/validate/route.js +1 -1
  28. package/.next/server/app/api/cwd/validate/route.js.nft.json +2 -2
  29. package/.next/server/app/api/file-index/route.js +1 -1
  30. package/.next/server/app/api/file-index/route.js.nft.json +1 -1
  31. package/.next/server/app/api/files/[...path]/route.js +1 -1
  32. package/.next/server/app/api/files/[...path]/route.js.nft.json +1 -1
  33. package/.next/server/app/api/git/diff/route.js +1 -6
  34. package/.next/server/app/api/git/diff/route.js.nft.json +1 -1
  35. package/.next/server/app/api/git/status/route.js +1 -6
  36. package/.next/server/app/api/git/status/route.js.nft.json +1 -1
  37. package/.next/server/app/api/mcp/route.js +1 -1
  38. package/.next/server/app/api/mcp/route.js.nft.json +1 -1
  39. package/.next/server/app/api/mcp/sign-in/route.js +1 -1
  40. package/.next/server/app/api/mcp/sign-in/route.js.nft.json +1 -1
  41. package/.next/server/app/api/mcp/test/route.js +1 -1
  42. package/.next/server/app/api/mcp/test/route.js.nft.json +1 -1
  43. package/.next/server/app/api/models/default/route.js +1 -1
  44. package/.next/server/app/api/models/default/route.js.nft.json +2 -2
  45. package/.next/server/app/api/models/enabled/route.js +1 -1
  46. package/.next/server/app/api/models/enabled/route.js.nft.json +2 -2
  47. package/.next/server/app/api/models/route.js +1 -1
  48. package/.next/server/app/api/models/route.js.nft.json +1 -1
  49. package/.next/server/app/api/open-in-explorer/route.js +1 -1
  50. package/.next/server/app/api/open-in-explorer/route.js.nft.json +1 -1
  51. package/.next/server/app/api/plugins/check/route.js +1 -1
  52. package/.next/server/app/api/plugins/check/route.js.nft.json +1 -1
  53. package/.next/server/app/api/plugins/route.js +1 -1
  54. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  55. package/.next/server/app/api/project-trust/route.js +1 -1
  56. package/.next/server/app/api/project-trust/route.js.nft.json +1 -1
  57. package/.next/server/app/api/sessions/[id]/auto-name/route.js +1 -1
  58. package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
  59. package/.next/server/app/api/sessions/[id]/fork/route.js +1 -1
  60. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  61. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  62. package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
  63. package/.next/server/app/api/sessions/delete/route.js +1 -1
  64. package/.next/server/app/api/sessions/delete/route.js.nft.json +1 -1
  65. package/.next/server/app/api/sessions/route.js +1 -1
  66. package/.next/server/app/api/sessions/stats/route.js +1 -1
  67. package/.next/server/app/api/sessions/stats/route.js.nft.json +1 -1
  68. package/.next/server/app/api/sessions/ui-state/route.js +1 -1
  69. package/.next/server/app/api/sessions/ui-state/route.js.nft.json +1 -1
  70. package/.next/server/app/api/skills/check/route.js +1 -1
  71. package/.next/server/app/api/skills/check/route.js.nft.json +1 -1
  72. package/.next/server/app/api/skills/content/route.js +1 -1
  73. package/.next/server/app/api/skills/content/route.js.nft.json +1 -1
  74. package/.next/server/app/api/skills/install/route.js +1 -1
  75. package/.next/server/app/api/skills/install/route.js.nft.json +1 -1
  76. package/.next/server/app/api/skills/route.js +1 -1
  77. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  78. package/.next/server/app/api/skills/search/route.js +1 -1
  79. package/.next/server/app/api/skills/update/route.js +1 -1
  80. package/.next/server/app/api/skills/update/route.js.nft.json +1 -1
  81. package/.next/server/app/api/subagents/[id]/route.js +1 -1
  82. package/.next/server/app/api/subagents/profiles/route.js +1 -1
  83. package/.next/server/app/api/subagents/profiles/route.js.nft.json +1 -1
  84. package/.next/server/app/api/terminal/route.js +1 -1
  85. package/.next/server/app/api/terminal/route.js.nft.json +1 -1
  86. package/.next/server/app/api/worktrees/route.js +1 -1
  87. package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
  88. package/.next/server/app/index.html +1 -1
  89. package/.next/server/app/index.rsc +7 -7
  90. package/.next/server/app/index.segments/__PAGE__.segment.rsc +7 -7
  91. package/.next/server/app/index.segments/_full.segment.rsc +7 -7
  92. package/.next/server/app/index.segments/_tree.segment.rsc +4 -4
  93. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  94. package/.next/server/app/login.html +1 -1
  95. package/.next/server/app/login.rsc +5 -5
  96. package/.next/server/app/login.segments/_full.segment.rsc +5 -5
  97. package/.next/server/app/login.segments/_tree.segment.rsc +3 -3
  98. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +5 -5
  99. package/.next/server/app/page.js +24 -24
  100. package/.next/server/app/page_client-reference-manifest.js +1 -1
  101. package/.next/server/app/pet-preview/page_client-reference-manifest.js +1 -1
  102. package/.next/server/app/pet-preview.html +1 -1
  103. package/.next/server/app/pet-preview.rsc +5 -5
  104. package/.next/server/app/pet-preview.segments/_full.segment.rsc +5 -5
  105. package/.next/server/app/pet-preview.segments/_tree.segment.rsc +3 -3
  106. package/.next/server/app/pet-preview.segments/pet-preview/__PAGE__.segment.rsc +5 -5
  107. package/.next/server/app-paths-manifest.json +18 -18
  108. package/.next/server/chunks/1351.js +1 -1
  109. package/.next/server/chunks/1906.js +1 -1
  110. package/.next/server/chunks/2243.js +1 -1
  111. package/.next/server/chunks/2498.js +1 -1
  112. package/.next/server/chunks/27.js +1 -1
  113. package/.next/server/chunks/3374.js +6 -0
  114. package/.next/server/chunks/4698.js +1 -1
  115. package/.next/server/chunks/5000.js +1 -0
  116. package/.next/server/chunks/5787.js +1 -1
  117. package/.next/server/chunks/7365.js +1 -0
  118. package/.next/server/chunks/8952.js +1 -0
  119. package/.next/server/chunks/9165.js +1 -1
  120. package/.next/server/middleware-build-manifest.js +1 -1
  121. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  122. package/.next/server/pages/404.html +1 -1
  123. package/.next/server/pages/500.html +1 -1
  124. package/.next/static/chunks/1278-887d8dd9f4ab60fa.js +1 -0
  125. package/.next/static/chunks/5455.0653c79e4997724a.js +1 -0
  126. package/.next/static/chunks/app/{layout-955e68ada9a4c00a.js → layout-b707963fa7205d6d.js} +1 -1
  127. package/.next/static/chunks/app/page-4232d828a32cfeb9.js +228 -0
  128. package/.next/static/chunks/{webpack-8f20f0fe6235d33b.js → webpack-7f5c25954b44b5fb.js} +1 -1
  129. package/.next/static/css/3d221be0a6116b3b.css +1 -0
  130. package/.next/static/css/40f42d513e985a7c.css +1 -0
  131. package/.next/static/css/{785537d6385160c9.css → d986dbac15b9ebaa.css} +1 -1
  132. package/.next/trace +8 -8
  133. package/.next/trace-build +1 -1
  134. package/README.md +208 -207
  135. package/package.json +1 -1
  136. package/.next/server/chunks/2069.js +0 -1
  137. package/.next/static/chunks/1278-18f6ec77eb77312f.js +0 -1
  138. package/.next/static/chunks/5455.af2fa5ae4e041807.js +0 -1
  139. package/.next/static/chunks/app/page-7e30ceb131511d62.js +0 -228
  140. package/.next/static/css/34a258ef601979e9.css +0 -1
  141. package/.next/static/css/4cb902f98aeb9119.css +0 -1
  142. /package/.next/static/{6G0fewP2uW-xnL4J9JTfK → iKNuUVRZa7JT6IPMMtnRe}/_buildManifest.js +0 -0
  143. /package/.next/static/{6G0fewP2uW-xnL4J9JTfK → iKNuUVRZa7JT6IPMMtnRe}/_ssgManifest.js +0 -0
package/.next/trace-build CHANGED
@@ -1 +1 @@
1
- [{"name":"run-webpack","duration":204095814,"timestamp":70946271419,"id":14,"parentId":1,"tags":{},"startTime":1793189772646,"traceId":"d03a0068465e3834"},{"name":"run-typescript","duration":17992763,"timestamp":71150517384,"id":831,"parentId":1,"tags":{},"startTime":1793189976891,"traceId":"d03a0068465e3834"},{"name":"static-check","duration":15673910,"timestamp":71168642646,"id":834,"parentId":1,"tags":{},"startTime":1793189995017,"traceId":"d03a0068465e3834"},{"name":"static-generation","duration":7816791,"timestamp":71184490633,"id":988,"parentId":1,"tags":{},"startTime":1793190010865,"traceId":"d03a0068465e3834"},{"name":"collect-build-traces","duration":37014728,"timestamp":71184325427,"id":985,"parentId":1,"tags":{},"startTime":1793190010699,"traceId":"d03a0068465e3834"},{"name":"telemetry-flush","duration":63,"timestamp":71221349026,"id":997,"parentId":1,"tags":{},"startTime":1793190047727,"traceId":"d03a0068465e3834"},{"name":"next-build","duration":275899894,"timestamp":70945449203,"id":1,"tags":{"buildMode":"default","version":"16.3.8","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1793189771824,"traceId":"d03a0068465e3834"}]
1
+ [{"name":"run-webpack","duration":173676020,"timestamp":42427913244,"id":14,"parentId":1,"tags":{},"startTime":1793237225032,"traceId":"9e206200ea255e39"},{"name":"run-typescript","duration":15417162,"timestamp":42601735016,"id":807,"parentId":1,"tags":{},"startTime":1793237398856,"traceId":"9e206200ea255e39"},{"name":"static-check","duration":11986017,"timestamp":42617311639,"id":810,"parentId":1,"tags":{},"startTime":1793237414433,"traceId":"9e206200ea255e39"},{"name":"static-generation","duration":7093451,"timestamp":42629446053,"id":964,"parentId":1,"tags":{},"startTime":1793237426567,"traceId":"9e206200ea255e39"},{"name":"collect-build-traces","duration":35863550,"timestamp":42629302231,"id":961,"parentId":1,"tags":{},"startTime":1793237426423,"traceId":"9e206200ea255e39"},{"name":"telemetry-flush","duration":133,"timestamp":42665174855,"id":973,"parentId":1,"tags":{},"startTime":1793237462297,"traceId":"9e206200ea255e39"},{"name":"next-build","duration":238040390,"timestamp":42427134621,"id":1,"tags":{"buildMode":"default","version":"16.3.8","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1793237224254,"traceId":"9e206200ea255e39"}]
package/README.md CHANGED
@@ -1,207 +1,208 @@
1
- # Pi Web
2
-
3
- [中文文档](./docs/README.zh-CN.md) | [日本語](./docs/README.ja.md) | [Русский](./docs/README.ru.md)
4
-
5
- Local browser UI for the [pi coding agent](https://github.com/earendil-works/pi). Pi Web uses the same local configuration and session files as pi, so you can browse and resume conversations, run agent turns, configure models and resources, and inspect project files from a browser.
6
-
7
- **[Try the interactive demo →](https://agegr.github.io/pi-web/)** The real Pi Web UI runs entirely in your browser, with sample sessions, files and models. There is nothing to install; replies are pre-written and no model is called.
8
-
9
- ![Pi Web displaying a pi session with structured Markdown, tool calls, and project navigation](https://raw.githubusercontent.com/agegr/pi-web/main/docs/screenshot2.png)
10
-
11
- ## Features
12
-
13
- - **Session workspace**: browse, resume, rename, export, and delete conversations grouped by project, with running state, context usage, cost, and compaction details.
14
- - **Two ways to branch**: **New session** creates an independent session file from an earlier message; **Edit from here** creates a branch inside the current session.
15
- - **Project file tools**: browse and upload files, inspect Git diffs, and preview source, Markdown, images, audio, PDFs, and DOCX files with automatic refresh.
16
- - **Git worktrees**: switch checkouts from the sidebar while keeping sessions from the same repository grouped together.
17
- - **Web-based configuration**: manage provider login and API keys, models, model tests, plugin packages, and skills without leaving Pi Web.
18
- - **English, Simplified Chinese, and Traditional Chinese UI**: Pi Web follows the browser language initially and provides a language switcher in the top bar.
19
-
20
- ## Quick Start
21
-
22
- Pi Web requires Node.js 22.19.0 or newer. Check your version with `node --version`, then run:
23
-
24
- ```bash
25
- npx pi-web-yun@latest
26
- ```
27
-
28
- The CLI opens a browser after the server is ready. If it does not, open [http://127.0.0.1:30141](http://127.0.0.1:30141). Pi Web listens only on `127.0.0.1` by default.
29
-
30
- If no model provider is configured yet, open the **Models** panel to sign in or add an API key.
31
-
32
- To install the `pi-yun` command globally:
33
-
34
- ```bash
35
- npm install -g pi-yun@latest
36
- pi-yun
37
- ```
38
-
39
- Commands:
40
-
41
- ```bash
42
- pi-yun version # print the installed version
43
- pi-yun status # list running servers
44
- pi-yun stop [--port N] # stop a running server
45
- pi-yun open [--port N] # open a running server in the browser
46
- pi-yun update [--check] # update a global npm install
47
- ```
48
-
49
- To update, run `pi-yun stop`, then `pi-yun update` (or run the same install command again). To uninstall, run `npm uninstall -g pi-yun`.
50
-
51
- ## Configuration
52
-
53
- For port and hostname, command-line options override the corresponding environment variables. Either `--no-open` or `PI_WEB_NO_OPEN=1` disables automatic browser opening. Run `pi-web --help` (or `-h`) to print startup options and exit without starting the server. Unknown options exit with an error.
54
-
55
- | Option or environment variable | Purpose | Default |
56
- | --- | --- | --- |
57
- | `--help`, `-h` | Print startup options and exit | — |
58
- | `--port <port>`, `-p <port>`, or `PORT` | Server port | `30141` |
59
- | `--hostname <host>`, `-H <host>`, or `PI_WEB_HOSTNAME` | Bind hostname | `127.0.0.1` |
60
- | `--no-open` or `PI_WEB_NO_OPEN=1` | Do not open a browser automatically | Browser opens |
61
- | `PI_WEB_APP_NAME` | PWA manifest `name` and `short_name`; surrounding whitespace is trimmed, empty values use the default | `Pi Web` |
62
- | `PI_WEB_SKIP_VERSION_CHECK=1` | Disable Pi Web update checks | Unset |
63
- | `PI_WEB_ALLOWED_HOSTS` | Additional exact proxy or custom hostnames, comma-separated | Unset |
64
- | `PI_WEB_PASSWORD` | Enable browser password login; API clients may use Basic Auth with username `pi` | Authentication disabled |
65
- | `PI_WEB_IDLE_TIMEOUT_MS` | Session idle timeout in milliseconds, up to `2147483647`; `0` disables idle shutdown; invalid or out-of-range values use the default | `600000` (10 min) |
66
- | `PI_WEB_SHUTDOWN_DEADLINE_MS` | How long extensions get to handle `session_shutdown` before a closing session is disposed anyway, in milliseconds up to `2147483647`; `0`, invalid or out-of-range values use the default | `5000` (5 s) |
67
-
68
- Set `PI_WEB_APP_NAME` before starting the server (for example, `PI_WEB_APP_NAME='Work Pi' pi-web`). After changing it, restart the server to serve the new manifest; no rebuild is required. Updates to the name of an already installed PWA are managed by the browser and are not guaranteed to appear immediately. This does not change the page title or icons.
69
-
70
- For example:
71
-
72
- ```bash
73
- pi-web --help
74
- pi-web -p 8080 -H 0.0.0.0 --no-open
75
- ```
76
-
77
- ### Remote Access
78
-
79
- Binding to a non-loopback address exposes an agent that can execute high-privilege actions. On a trusted LAN, require a long random password:
80
-
81
- ```bash
82
- PI_WEB_PASSWORD='a-long-random-password' pi-web --hostname 0.0.0.0
83
- ```
84
-
85
- Password authentication does not encrypt the connection. Do not expose Pi Web over plain HTTP to the internet; use HTTPS through a trusted reverse proxy or a trusted VPN. If a reverse proxy sends an external hostname, add that exact name to `PI_WEB_ALLOWED_HOSTS`. This allow-list does not change the address Pi Web binds to.
86
-
87
- ### HTTP Proxy
88
-
89
- Server-side model and API requests honor the standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables.
90
-
91
- On macOS or Linux:
92
-
93
- ```bash
94
- HTTP_PROXY=http://127.0.0.1:7890 \
95
- HTTPS_PROXY=http://127.0.0.1:7890 \
96
- NO_PROXY=localhost,127.0.0.1 \
97
- npx pi-web-yun@latest
98
- ```
99
-
100
- On Windows PowerShell:
101
-
102
- ```powershell
103
- $env:HTTP_PROXY = "http://127.0.0.1:7890"
104
- $env:HTTPS_PROXY = "http://127.0.0.1:7890"
105
- $env:NO_PROXY = "localhost,127.0.0.1"
106
- npx pi-web-yun@latest
107
- ```
108
-
109
- ## Notes
110
-
111
- - **Agent data**: Pi Web reads pi data from `~/.pi/agent` by default, including session files under `sessions/<encoded-cwd>/<timestamp>_<uuid>.jsonl`. Set `PI_CODING_AGENT_DIR` to use another pi agent directory.
112
- - **Filesystem access**: Pi Web must be able to read the agent data directory and the working directories recorded by its sessions. Run Pi Web in the same filesystem environment as pi when sharing existing sessions.
113
- - **Shared configuration**: the Models panel uses pi's model, settings, and credential storage, so changes are visible to both interfaces.
114
- - **File access boundary**: the file browser is limited to working directories selected in Pi Web and project or session roots it already knows about; it is not a general filesystem browser.
115
- - **Git worktrees**: see [Worktrees in Pi Web](./docs/worktrees.md) for switcher visibility, worktree creation, and removal behavior.
116
-
117
- ### Downstream Session Context Menu
118
-
119
- Electron wrappers and other downstream integrations can provide a session-row
120
- context menu without patching `SessionSidebar`. Listen for the cancelable
121
- `pi-web:session-row-contextmenu` browser event and call `preventDefault()`
122
- synchronously when the integration will handle it:
123
-
124
- ```js
125
- window.addEventListener("pi-web:session-row-contextmenu", (event) => {
126
- event.preventDefault();
127
- const { id, path, cwd, name, clientX, clientY, refresh } = event.detail;
128
-
129
- void openSessionMenu({ id, path, cwd, name, clientX, clientY }).then((changed) => {
130
- if (changed) refresh();
131
- });
132
- });
133
- ```
134
-
135
- The detail object contains `id`, `path`, `cwd`, optional `name`, pointer
136
- coordinates, and a `refresh()` callback for actions that change the session
137
- list. If no listener cancels the extension event, Pi Web opens its built-in
138
- session menu (pin, rename, fork, mark read or unread, archive, open in new
139
- tab, delete) at the pointer instead of the browser's native context menu.
140
- The row's `⋯` button always opens the built-in menu and does not dispatch the
141
- event. Sessions not yet saved to disk get no built-in menu, so the native one
142
- still appears for them. This hook is browser-side and independent of Pi agent
143
- extensions.
144
-
145
- ### Extension Session Liveness
146
-
147
- Server-side Pi extensions with detached work can prevent automatic idle
148
- session eviction through the versioned global registry:
149
-
150
- ```js
151
- const liveness = globalThis[Symbol.for("@agegr/pi-web/session-liveness/v1")];
152
- const release = liveness?.version === 1
153
- ? liveness.register({
154
- name: "my-extension",
155
- sessionId,
156
- sessionFile: sessionFile || undefined,
157
- isActive: () => detachedJobs.size > 0,
158
- })
159
- : () => {};
160
- ```
161
-
162
- Register once per active extension session and call the returned idempotent
163
- `release` function on session shutdown, replacement, or reload. `isActive`
164
- must be synchronous, cheap, and scoped to the supplied exact session id or
165
- file. Provider errors fail safe by preserving that session. This lease only
166
- affects automatic idle eviction; explicit shutdown and Stop fallback cleanup
167
- still take precedence.
168
-
169
- ## Development
170
-
171
- ```bash
172
- npm install
173
- npm run dev
174
- ```
175
-
176
- The development server runs at [http://127.0.0.1:30141](http://127.0.0.1:30141). Run the common checks with:
177
-
178
- ```bash
179
- npm test
180
- node_modules/.bin/tsc --noEmit
181
- npm run lint
182
- ```
183
-
184
- Do not run `next build` or `npm run build` during normal development. It writes to `.next/` and can interfere with the development server; leave builds for release work.
185
-
186
- Before starting on a feature, read [Contributing](./CONTRIBUTING.md) for what is in scope and the pull request checklist.
187
-
188
- Contributor guides: [Internationalization](./docs/i18n.md) and [Release process](./docs/release.md).
189
-
190
- ## Repository Layout
191
-
192
- ```text
193
- app/ Next.js UI and API routes
194
- components/ React UI components
195
- hooks/ Client state and interaction hooks
196
- lib/ Session, agent, model, file, Git, and security logic
197
- public/ Static assets and PWA files
198
- bin/ npm CLI entrypoint and launch option parsing
199
- docs/ Focused user and contributor guides
200
- demo/ Static browser demo published to GitHub Pages (see demo/README.md)
201
- ```
202
-
203
- See [AGENTS.md](./AGENTS.md) for the architecture notes and detailed file map.
204
-
205
- ## License
206
-
207
- [MIT](./LICENSE)
1
+ # Pi Web
2
+
3
+ [中文文档](./docs/README.zh-CN.md) | [日本語](./docs/README.ja.md) | [Русский](./docs/README.ru.md)
4
+
5
+ Local browser UI for the [pi coding agent](https://github.com/earendil-works/pi). Pi Web uses the same local configuration and session files as pi, so you can browse and resume conversations, run agent turns, configure models and resources, and inspect project files from a browser.
6
+
7
+ **[Try the interactive demo →](https://agegr.github.io/pi-web/)** The real Pi Web UI runs entirely in your browser, with sample sessions, files and models. There is nothing to install; replies are pre-written and no model is called.
8
+
9
+ ![Pi Web displaying a pi session with structured Markdown, tool calls, and project navigation](https://raw.githubusercontent.com/agegr/pi-web/main/docs/screenshot2.png)
10
+
11
+ ## Features
12
+
13
+ - **Session workspace**: browse, resume, rename, export, and delete conversations grouped by project, with running state, context usage, cost, and compaction details.
14
+ - **Project creation**: use **+** beside **Projects** to name a project and select one source folder, with directory validation and persistent sidebar entries. See [adding a project](docs/worktrees.md#adding-a-project).
15
+ - **Two ways to branch**: **New session** creates an independent session file from an earlier message; **Edit from here** creates a branch inside the current session.
16
+ - **Project file tools**: browse and upload files, inspect Git diffs, and preview source, Markdown, images, audio, PDFs, and DOCX files with automatic refresh.
17
+ - **Git worktrees**: switch checkouts from the sidebar while keeping sessions from the same repository grouped together.
18
+ - **Web-based configuration**: manage provider login and API keys, models, model tests, plugin packages, and skills without leaving Pi Web.
19
+ - **English, Simplified Chinese, and Traditional Chinese UI**: Pi Web follows the browser language initially and provides a language switcher in the top bar.
20
+
21
+ ## Quick Start
22
+
23
+ Pi Web requires Node.js 22.19.0 or newer. Check your version with `node --version`, then run:
24
+
25
+ ```bash
26
+ npx pi-web-yun@latest
27
+ ```
28
+
29
+ The CLI opens a browser after the server is ready. If it does not, open [http://127.0.0.1:30141](http://127.0.0.1:30141). Pi Web listens only on `127.0.0.1` by default.
30
+
31
+ If no model provider is configured yet, open the **Models** panel to sign in or add an API key.
32
+
33
+ To install the `pi-yun` command globally:
34
+
35
+ ```bash
36
+ npm install -g pi-yun@latest
37
+ pi-yun
38
+ ```
39
+
40
+ Commands:
41
+
42
+ ```bash
43
+ pi-yun version # print the installed version
44
+ pi-yun status # list running servers
45
+ pi-yun stop [--port N] # stop a running server
46
+ pi-yun open [--port N] # open a running server in the browser
47
+ pi-yun update [--check] # update a global npm install
48
+ ```
49
+
50
+ To update, run `pi-yun stop`, then `pi-yun update` (or run the same install command again). To uninstall, run `npm uninstall -g pi-yun`.
51
+
52
+ ## Configuration
53
+
54
+ For port and hostname, command-line options override the corresponding environment variables. Either `--no-open` or `PI_WEB_NO_OPEN=1` disables automatic browser opening. Run `pi-web --help` (or `-h`) to print startup options and exit without starting the server. Unknown options exit with an error.
55
+
56
+ | Option or environment variable | Purpose | Default |
57
+ | --- | --- | --- |
58
+ | `--help`, `-h` | Print startup options and exit | — |
59
+ | `--port <port>`, `-p <port>`, or `PORT` | Server port | `30141` |
60
+ | `--hostname <host>`, `-H <host>`, or `PI_WEB_HOSTNAME` | Bind hostname | `127.0.0.1` |
61
+ | `--no-open` or `PI_WEB_NO_OPEN=1` | Do not open a browser automatically | Browser opens |
62
+ | `PI_WEB_APP_NAME` | PWA manifest `name` and `short_name`; surrounding whitespace is trimmed, empty values use the default | `Pi Web` |
63
+ | `PI_WEB_SKIP_VERSION_CHECK=1` | Disable Pi Web update checks | Unset |
64
+ | `PI_WEB_ALLOWED_HOSTS` | Additional exact proxy or custom hostnames, comma-separated | Unset |
65
+ | `PI_WEB_PASSWORD` | Enable browser password login; API clients may use Basic Auth with username `pi` | Authentication disabled |
66
+ | `PI_WEB_IDLE_TIMEOUT_MS` | Session idle timeout in milliseconds, up to `2147483647`; `0` disables idle shutdown; invalid or out-of-range values use the default | `600000` (10 min) |
67
+ | `PI_WEB_SHUTDOWN_DEADLINE_MS` | How long extensions get to handle `session_shutdown` before a closing session is disposed anyway, in milliseconds up to `2147483647`; `0`, invalid or out-of-range values use the default | `5000` (5 s) |
68
+
69
+ Set `PI_WEB_APP_NAME` before starting the server (for example, `PI_WEB_APP_NAME='Work Pi' pi-web`). After changing it, restart the server to serve the new manifest; no rebuild is required. Updates to the name of an already installed PWA are managed by the browser and are not guaranteed to appear immediately. This does not change the page title or icons.
70
+
71
+ For example:
72
+
73
+ ```bash
74
+ pi-web --help
75
+ pi-web -p 8080 -H 0.0.0.0 --no-open
76
+ ```
77
+
78
+ ### Remote Access
79
+
80
+ Binding to a non-loopback address exposes an agent that can execute high-privilege actions. On a trusted LAN, require a long random password:
81
+
82
+ ```bash
83
+ PI_WEB_PASSWORD='a-long-random-password' pi-web --hostname 0.0.0.0
84
+ ```
85
+
86
+ Password authentication does not encrypt the connection. Do not expose Pi Web over plain HTTP to the internet; use HTTPS through a trusted reverse proxy or a trusted VPN. If a reverse proxy sends an external hostname, add that exact name to `PI_WEB_ALLOWED_HOSTS`. This allow-list does not change the address Pi Web binds to.
87
+
88
+ ### HTTP Proxy
89
+
90
+ Server-side model and API requests honor the standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables.
91
+
92
+ On macOS or Linux:
93
+
94
+ ```bash
95
+ HTTP_PROXY=http://127.0.0.1:7890 \
96
+ HTTPS_PROXY=http://127.0.0.1:7890 \
97
+ NO_PROXY=localhost,127.0.0.1 \
98
+ npx pi-web-yun@latest
99
+ ```
100
+
101
+ On Windows PowerShell:
102
+
103
+ ```powershell
104
+ $env:HTTP_PROXY = "http://127.0.0.1:7890"
105
+ $env:HTTPS_PROXY = "http://127.0.0.1:7890"
106
+ $env:NO_PROXY = "localhost,127.0.0.1"
107
+ npx pi-web-yun@latest
108
+ ```
109
+
110
+ ## Notes
111
+
112
+ - **Agent data**: Pi Web reads pi data from `~/.pi/agent` by default, including session files under `sessions/<encoded-cwd>/<timestamp>_<uuid>.jsonl`. Set `PI_CODING_AGENT_DIR` to use another pi agent directory.
113
+ - **Filesystem access**: Pi Web must be able to read the agent data directory and the working directories recorded by its sessions. Run Pi Web in the same filesystem environment as pi when sharing existing sessions.
114
+ - **Shared configuration**: the Models panel uses pi's model, settings, and credential storage, so changes are visible to both interfaces.
115
+ - **File access boundary**: the file browser is limited to working directories selected in Pi Web and project or session roots it already knows about; it is not a general filesystem browser.
116
+ - **Git worktrees**: see [Worktrees in Pi Web](./docs/worktrees.md) for switcher visibility, worktree creation, and removal behavior.
117
+
118
+ ### Downstream Session Context Menu
119
+
120
+ Electron wrappers and other downstream integrations can provide a session-row
121
+ context menu without patching `SessionSidebar`. Listen for the cancelable
122
+ `pi-web:session-row-contextmenu` browser event and call `preventDefault()`
123
+ synchronously when the integration will handle it:
124
+
125
+ ```js
126
+ window.addEventListener("pi-web:session-row-contextmenu", (event) => {
127
+ event.preventDefault();
128
+ const { id, path, cwd, name, clientX, clientY, refresh } = event.detail;
129
+
130
+ void openSessionMenu({ id, path, cwd, name, clientX, clientY }).then((changed) => {
131
+ if (changed) refresh();
132
+ });
133
+ });
134
+ ```
135
+
136
+ The detail object contains `id`, `path`, `cwd`, optional `name`, pointer
137
+ coordinates, and a `refresh()` callback for actions that change the session
138
+ list. If no listener cancels the extension event, Pi Web opens its built-in
139
+ session menu (pin, rename, fork, mark read or unread, archive, open in new
140
+ tab, delete) at the pointer instead of the browser's native context menu.
141
+ The row's `⋯` button always opens the built-in menu and does not dispatch the
142
+ event. Sessions not yet saved to disk get no built-in menu, so the native one
143
+ still appears for them. This hook is browser-side and independent of Pi agent
144
+ extensions.
145
+
146
+ ### Extension Session Liveness
147
+
148
+ Server-side Pi extensions with detached work can prevent automatic idle
149
+ session eviction through the versioned global registry:
150
+
151
+ ```js
152
+ const liveness = globalThis[Symbol.for("@agegr/pi-web/session-liveness/v1")];
153
+ const release = liveness?.version === 1
154
+ ? liveness.register({
155
+ name: "my-extension",
156
+ sessionId,
157
+ sessionFile: sessionFile || undefined,
158
+ isActive: () => detachedJobs.size > 0,
159
+ })
160
+ : () => {};
161
+ ```
162
+
163
+ Register once per active extension session and call the returned idempotent
164
+ `release` function on session shutdown, replacement, or reload. `isActive`
165
+ must be synchronous, cheap, and scoped to the supplied exact session id or
166
+ file. Provider errors fail safe by preserving that session. This lease only
167
+ affects automatic idle eviction; explicit shutdown and Stop fallback cleanup
168
+ still take precedence.
169
+
170
+ ## Development
171
+
172
+ ```bash
173
+ npm install
174
+ npm run dev
175
+ ```
176
+
177
+ The development server runs at [http://127.0.0.1:30141](http://127.0.0.1:30141). Run the common checks with:
178
+
179
+ ```bash
180
+ npm test
181
+ node_modules/.bin/tsc --noEmit
182
+ npm run lint
183
+ ```
184
+
185
+ Do not run `next build` or `npm run build` during normal development. It writes to `.next/` and can interfere with the development server; leave builds for release work.
186
+
187
+ Before starting on a feature, read [Contributing](./CONTRIBUTING.md) for what is in scope and the pull request checklist.
188
+
189
+ Contributor guides: [Internationalization](./docs/i18n.md) and [Release process](./docs/release.md).
190
+
191
+ ## Repository Layout
192
+
193
+ ```text
194
+ app/ Next.js UI and API routes
195
+ components/ React UI components
196
+ hooks/ Client state and interaction hooks
197
+ lib/ Session, agent, model, file, Git, and security logic
198
+ public/ Static assets and PWA files
199
+ bin/ npm CLI entrypoint and launch option parsing
200
+ docs/ Focused user and contributor guides
201
+ demo/ Static browser demo published to GitHub Pages (see demo/README.md)
202
+ ```
203
+
204
+ See [AGENTS.md](./AGENTS.md) for the architecture notes and detailed file map.
205
+
206
+ ## License
207
+
208
+ [MIT](./LICENSE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-yun",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Web UI for the pi coding agent (fork of @agegr/pi-web)",
5
5
  "homepage": "https://github.com/Jason-24/pi-web-yun#readme",
6
6
  "repository": {
@@ -1 +0,0 @@
1
- "use strict";exports.id=2069,exports.ids=[2069],exports.modules={12069:(a,b,c)=>{c.d(b,{D7:()=>D,Id:()=>e,Jx:()=>w,KI:()=>z,Kk:()=>g,Q3:()=>o,cx:()=>f,fS:()=>d,ly:()=>v});let d=1,e=/^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/,f=1e3,g=262144,h=(a,b)=>Object.prototype.hasOwnProperty.call(a,b);function i(a){return"object"==typeof a&&null!==a&&!Array.isArray(a)}function j(a){return"number"==typeof a&&Number.isFinite(a)&&a>=0}function k(a){return"string"==typeof a&&e.test(a)}function l(a){return"string"==typeof a&&a.length>0&&a.length<=4096&&"__proto__"!==a}let m=/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;function n(a){if(null===a)return null;if("string"!=typeof a)return;let b=a.trim();return b?m.test(b)||[...b].length>80?void 0:b:null}function o(a,b){let c=a.projectNames;return c&&h(c,b)?c[b]:void 0}let p=new TextEncoder;function q(a){return p.encode(JSON.stringify(a)).length+1}function r(a){let b=1;for(let c of a)b+=q(c);return b}function s(a,b){return a<=1e3&&b<=262144}function t(a){let b=[],c=1;for(let d of a){if(b.length>=1e3)break;let a=q(d);c+a>262144||(b.push(d),c+=a)}return b}function u(a,b){return p.encode(JSON.stringify(a)).length+p.encode(JSON.stringify(b)).length+2}function v(){return{version:d,revision:0,sessions:{},projects:{}}}function w(a){var b,c;if(!i(a)||void 0!==a.version&&a.version!==d||void 0!==a.sessions&&!i(a.sessions)||void 0!==a.projects&&!i(a.projects))return null;let e=v(),h=a.revision;for(let[b,c]of("number"==typeof h&&Number.isSafeInteger(h)&&h>=0&&(e.revision=h),Object.entries(a.sessions??{}))){if(!k(b))continue;let a=function(a){if(!i(a))return null;let b={};return j(a.pinnedAt)&&(b.pinnedAt=a.pinnedAt),j(a.archivedAt)&&(b.archivedAt=a.archivedAt),void 0===b.pinnedAt&&void 0===b.archivedAt?null:b}(c);a&&(e.sessions[b]=a)}for(let[b,c]of Object.entries(a.projects??{})){if(!l(b))continue;let a=i(c)&&j(c.pinnedAt)&&l(c.root)?{pinnedAt:c.pinnedAt,root:c.root}:null;a&&(e.projects[b]=a)}if(Array.isArray(a.projectOrder)){let b=new Set,c=t(a.projectOrder.filter(a=>!(!l(a)||b.has(a))&&(b.add(a),!0)));c.length>0&&(e.projectOrder=c)}if(i(a.projectNames)){let d={},h=0,i=1;for(let[e,j]of Object.entries(a.projectNames)){let a=n(j);if(!l(e)||"string"!=typeof a)continue;let k=u(e,a);b=h+1,c=i+k,b<=f&&c<=g&&(d[e]=a,h++,i+=k)}h>0&&(e.projectNames=d)}return e}function x(a){return null===a?null:j(a)?a:void 0}function y(a,b,c){if(!Array.isArray(a)||!c&&0===a.length)return{ok:!1,error:`${b} must be ${c?"an":"a non-empty"} array`};if(a.length>500)return{ok:!1,error:`${b} may hold at most 500 projectKeys`};let d=[],e=new Set;for(let c of a){if(!l(c))return{ok:!1,error:`${b} must hold non-empty strings of at most 4096 characters`};e.has(c)||(e.add(c),d.push(c))}return{ok:!0,keys:d}}function z(a){if(!i(a))return{ok:!1,error:"Expected a JSON object"};switch(a.action){case"set":{let b=h(a,"pinned");if(b===h(a,"archived"))return{ok:!1,error:"Send exactly one of pinned or archived"};let c=b?a.pinned:a.archived;if("boolean"!=typeof c)return{ok:!1,error:`${b?"pinned":"archived"} must be a boolean`};let d=function(a){if(!Array.isArray(a)||0===a.length)return{ok:!1,error:"ids must be a non-empty array"};if(a.length>500)return{ok:!1,error:"ids may hold at most 500 session ids"};let b=[],c=new Set;for(let d of a){if(!k(d))return{ok:!1,error:"ids must be session ids"};c.has(d)||(c.add(d),b.push(d))}return{ok:!0,ids:b}}(a.ids);if(!d.ok)return d;return{ok:!0,request:b?{action:"set",ids:d.ids,pinned:c}:{action:"set",ids:d.ids,archived:c}}}case"restore":{let b=a.entries;if(!Array.isArray(b))return{ok:!1,error:"entries must be an array"};if(b.length>500)return{ok:!1,error:"entries may hold at most 500 sessions"};let c=[],d=new Set;for(let a of b){if(!i(a)||!k(a.id))return{ok:!1,error:"Each entry needs a session id"};if(d.has(a.id))return{ok:!1,error:"entries must name each session once"};d.add(a.id);let b=x(a.pinnedAt),e=x(a.archivedAt);if(void 0===b||void 0===e)return{ok:!1,error:"pinnedAt and archivedAt must be timestamps or null"};c.push({id:a.id,pinnedAt:b,archivedAt:e})}return{ok:!0,request:{action:"restore",entries:c}}}case"pin-project":if(!l(a.projectKey))return{ok:!1,error:"projectKey must be a non-empty string of at most 4096 characters"};if(!l(a.root))return{ok:!1,error:"root must be a non-empty string of at most 4096 characters"};if("boolean"!=typeof a.pinned)return{ok:!1,error:"pinned must be a boolean"};return{ok:!0,request:{action:"pin-project",projectKey:a.projectKey,root:a.root,pinned:a.pinned}};case"add-projects":{let b=y(a.keys,"keys",!1);if(!b.ok)return b;return{ok:!0,request:{action:"add-projects",keys:b.keys}}}case"move-project":{let{projectKey:b,anchorKey:c,position:d}=a;if(!l(b)||!l(c))return{ok:!1,error:"projectKey and anchorKey must be non-empty strings of at most 4096 characters"};if(b===c)return{ok:!1,error:"projectKey and anchorKey must differ"};if("before"!==d&&"after"!==d)return{ok:!1,error:'position must be "before" or "after"'};let e=void 0===a.add?{ok:!0,keys:[]}:y(a.add,"add",!0);if(!e.ok)return e;return{ok:!0,request:{action:"move-project",projectKey:b,anchorKey:c,position:d,add:e.keys}}}case"rename-project":{if(!l(a.projectKey))return{ok:!1,error:"projectKey must be a non-empty string of at most 4096 characters"};let b=n(a.name);if(void 0===b)return{ok:!1,error:"name must be null or one line of at most 80 characters"};return{ok:!0,request:{action:"rename-project",projectKey:a.projectKey,name:b}}}default:return{ok:!1,error:'action must be "set", "restore", "pin-project", "add-projects", "move-project" or "rename-project"'}}}function A(a,b,c){let d=h(a,b)?a[b]:void 0;return void 0===c.pinnedAt&&void 0===c.archivedAt?!!d&&(delete a[b],!0):(d?.pinnedAt!==c.pinnedAt||d?.archivedAt!==c.archivedAt)&&(a[b]=c,!0)}function B(a,b){var c;return c=a.projectOrder??[],!(c.length===b.length&&c.every((a,c)=>a===b[c]))&&(0===b.length?delete a.projectOrder:a.projectOrder=b,!0)}function C(a,b){let c=new Set(a),d=[];for(let a of b)c.has(a)||(c.add(a),d.push(a));return d}function D(a,b,c){var d,e,i,j;let k=function(a){let b={};for(let[c,d]of Object.entries(a.sessions))b[c]={...d};let c={};for(let[b,d]of Object.entries(a.projects))c[b]={...d};let d={...a,sessions:b,projects:c};return a.projectOrder&&(d.projectOrder=[...a.projectOrder]),a.projectNames&&(d.projectNames={...a.projectNames}),d}(a),l=!1;switch(b.action){case"set":for(let a of b.ids){let d=h(k.sessions,a)?k.sessions[a]:void 0;A(k.sessions,a,function(a,b,c){let d={...a};return"pinned"in b?b.pinned?(void 0===d.pinnedAt||void 0!==d.archivedAt)&&(d.pinnedAt=c,delete d.archivedAt):delete d.pinnedAt:b.archived?(d.archivedAt=c,delete d.pinnedAt):delete d.archivedAt,d}(d,b,c))&&(l=!0)}break;case"restore":for(let a of b.entries){let b={};null!==a.pinnedAt&&(b.pinnedAt=a.pinnedAt),null!==a.archivedAt&&(b.archivedAt=a.archivedAt),A(k.sessions,a.id,b)&&(l=!0)}break;case"pin-project":{let{projectKey:a,root:f}=b,g=h(k.projects,a)?k.projects[a]:void 0;if(b.pinned?g?.root!==f&&(k.projects[a]={pinnedAt:c,root:f},l=!0):g&&(delete k.projects[a],l=!0),(b.pinned?void 0===g:void 0!==g)&&k.projectOrder){let c,f,g=(d=k.projectOrder,e=k.projects,i=b.pinned,f=d.filter(b=>b!==a),i?(c=0,f.forEach((a,b)=>{h(e,a)&&(c=b+1)})):(c=f.findIndex(a=>!h(e,a)))<0&&(c=f.length),f.splice(c,0,a),f);s(g.length,r(g))&&B(k,g)&&(l=!0)}break}case"add-projects":B(k,function(a,b){let c=C(a,b),d=a.length,e=r(a),f=c.length;for(;f>0;){let a=q(c[f-1]);if(!s(d+1,e+a))break;d++,e+=a,f--}return[...c.slice(f),...a]}(k.projectOrder??[],b.keys))&&(l=!0);break;case"move-project":{let a,c,{projectKey:d,anchorKey:e,position:f,add:g}=b,h=k.projectOrder??[];B(k,function(a,b){let c=a.length,d=r(a);if(s(c,d))return a;let e=new Set;for(let f=a.length-1;f>=0&&!s(c,d);f--)b.has(a[f])||(e.add(f),c--,d-=q(a[f]));let f=a.filter((a,b)=>!e.has(b));return s(c,d)?f:t(f)}((j=[...C(h,g),...h],(a=j.filter(a=>a!==d)).includes(e)||a.unshift(e),c=a.indexOf(e),a.splice("before"===f?c:c+1,0,d),a),new Set([d,e,...g])))&&(l=!0);break}case"rename-project":{let a=n(b.name);void 0!==a&&function(a,b,c){let d={...a.projectNames};if((h(d,b)?d[b]:void 0)===(c??void 0))return!1;if(null===c)delete d[b];else{var e,i;if(d[b]=c,e=Object.keys(d).length,i=function(a){let b=1;for(let[c,d]of Object.entries(a))b+=u(c,d);return b}(d),!(e<=f)||!(i<=g))return!1}return 0===Object.keys(d).length?delete a.projectNames:a.projectNames=d,!0}(k,b.projectKey,a)&&(l=!0)}}return{state:k,changed:l}}}};