@yunazgr/pi-companion 0.0.0-stage → 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ygrip
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,336 @@
1
- # Temporary Holding Version
1
+ # Pi Companion
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Lightweight, local-first remote control for Pi sessions.
4
+
5
+ Pi Companion is deliberately not another agent runtime. Pi owns execution and conversation state. A single Rust daemon owns session discovery, pairing, temporary file exchange, browser fan-out, and the embedded web UI.
6
+
7
+ The UI is a static SvelteKit application. There is no Node runtime in production and no Tauri shell. Rust embeds the generated frontend into the daemon binary.
8
+
9
+ ![Pi Companion overview, dark theme](docs/dashboard.webp)
10
+
11
+ <p>
12
+ <img src="docs/session-light.webp" alt="Session detail, light theme" width="68%" />
13
+ <img src="docs/mobile.webp" alt="Session detail on a phone" width="28%" />
14
+ </p>
15
+
16
+ ## Install
17
+
18
+ pi install npm:@yunazgr/pi-companion
19
+
20
+ or straight from git:
21
+
22
+ pi install git:github.com/ygrip/pi-companion
23
+
24
+ Then start pi as usual and run /companion in each session you want to follow from the dashboard or your phone. It starts the daemon if needed, prints the dashboard address and shares that session with paired devices (`/companion off` stops sharing). Nothing else to configure: the extension finds or starts the daemon on its own, downloading the matching sha256-verified daemon binary from GitHub Releases on first use (see Daemon).
25
+
26
+ ## Architecture
27
+
28
+ local browser :43721
29
+ |
30
+ v
31
+ +---------------------------+
32
+ | pi-companion-server |
33
+ | single Rust daemon |
34
+ | |
35
+ | live session registry |
36
+ | pairing/device registry |
37
+ | per-session temp sandbox |
38
+ +-------------+-------------+
39
+ |
40
+ localhost WebSocket
41
+ +---------+---------+
42
+ v v
43
+ Pi session A Pi session B
44
+ extension extension
45
+
46
+ paired phone
47
+ |
48
+ HTTPS/WSS
49
+ |
50
+ tunnel / reverse proxy
51
+ |
52
+ v
53
+ remote surface :43722
54
+ |
55
+ +---- same daemon
56
+
57
+ The local administration surface and paired-device surface are separate listeners. If you expose Pi Companion through a tunnel, target only port 43722. Never expose port 43721.
58
+
59
+ ## Daemon
60
+
61
+ There is exactly one daemon per machine, shared by every Pi session. The extension manages it with no configuration:
62
+
63
+ 1. It probes http://127.0.0.1:43721. If anything answers (a daemon started by another session, or one you ran yourself with cargo run), it just connects. It never starts a second copy.
64
+ 2. If nothing answers, one Pi session takes a lock (so several sessions starting at once don't race), launches the daemon detached, and waits for it to be ready. The others wait for that daemon.
65
+ 3. If a live connection drops, the extension waits 20 seconds before launching a replacement, so restarting a dev daemon doesn't get pre-empted.
66
+ 4. The daemon also refuses to run twice: if its ports are taken it prints a message and exits 0.
67
+
68
+ The daemon binary is resolved in this order:
69
+
70
+ | Order | Source |
71
+ |---|---|
72
+ | 1 | PI_COMPANION_SERVER (explicit path override) |
73
+ | 2 | a cargo build inside the package checkout: newest of server/target/release and server/target/debug |
74
+ | 3 | cached download: ~/.pi/agent/pi-companion/bin/<version>/ |
75
+ | 4 | pi-companion-server on PATH |
76
+ | 5 | download of the GitHub release matching the package version, verified against SHA256SUMS, then cached |
77
+
78
+ Optional environment variables (none are required):
79
+
80
+ | Variable | Effect |
81
+ |---|---|
82
+ | PI_COMPANION_SERVER | use this daemon binary |
83
+ | PI_COMPANION_URL | daemon WebSocket URL (default ws://127.0.0.1:43721); autostart only happens for loopback URLs |
84
+ | PI_COMPANION_AUTOSTART=0 | never launch a daemon, only connect |
85
+ | PI_COMPANION_PUBLIC_URL | daemon-side: public URL embedded in pairing QR codes |
86
+ | PI_COMPANION_ALLOWED_ORIGINS | daemon-side: extra comma-separated browser origins accepted besides the loopback addresses and the public URL |
87
+
88
+ Local admin:
89
+
90
+ http://127.0.0.1:43721
91
+
92
+ Paired-device surface:
93
+
94
+ http://127.0.0.1:43722
95
+
96
+ The remote listener is still loopback-only. Use an authenticated HTTPS tunnel or reverse proxy for access outside the machine.
97
+
98
+ Set the externally reachable paired-device URL before starting the daemon:
99
+
100
+ PI_COMPANION_PUBLIC_URL=https://companion.example.com pi-companion-server
101
+
102
+ The configured public URL is used to construct pairing invitations and is the browser origin the paired-device surface accepts (see Security boundary).
103
+
104
+ ## Per-session remote control
105
+
106
+ Inside any Pi session:
107
+
108
+ /companion # enable for this session (and print the dashboard address)
109
+ /companion off # stop sharing this session
110
+ /remote-control # toggle sharing on/off
111
+
112
+ A paired device cannot access sessions that have not explicitly enabled remote control.
113
+
114
+ ## Questions from Pi
115
+
116
+ Pi's `companion_ask_user` tool asks one to four questions at once. Each question can offer options with descriptions, allow several choices (`multiSelect`), and accept a free-text "Other" answer; a question without options is free text. The browser shows them in a bottom sheet within thumb reach; "Later" hides it until you tap the waiting-question badge.
117
+
118
+ Dialogs from other extensions (`ctx.ui.select`, `ctx.ui.confirm`, `ctx.ui.input`) are relayed to the same sheet while the terminal dialog stays open: whichever side answers first wins and the other closes. Question tools that draw their own `ctx.ui.custom` picker are relayed through a small adapter: pi-jar's `jar_ask` is supported, and a companion answer completes its terminal picker. Other `ctx.ui.custom` components and `ctx.ui.editor` stay terminal-only because they cannot be answered from outside. Pending questions are part of the session snapshot, so a browser that connects later still sees them.
119
+
120
+ ## Pairing and devices
121
+
122
+ Pairing is daemon-wide rather than session-specific. Everything lives on the **Devices** page of the local console (port 43721):
123
+
124
+ 1. Choose **Pair a device**. The daemon creates a single-use invitation (5 minutes by default, configurable in Settings) and shows it as a QR code, a copyable link and a short code (`XXXX-XXXX`).
125
+ 2. On the phone, open the paired-device address. An unpaired device gets a connect screen: scan the QR code with the in-app camera (needs HTTPS; otherwise use the phone's camera app, which opens the invitation link) or type the short code, then give the device a name.
126
+ 3. The daemon issues a long random credential and stores only its SHA-256 hash.
127
+
128
+ Typed codes are short, so wrong ones are counted: after ten misses within ten minutes every open invitation is withdrawn and code entry is refused (HTTP 429) until the window passes.
129
+
130
+ Each paired device shows a live **Connected** / **Disconnected** status (with the number of open tabs), when it was last seen and when it was paired. From the console you can:
131
+
132
+ - **Disconnect**: end the device's live connections (WebSocket close code 4001). It stays paired and can reconnect when its user chooses.
133
+ - **Revoke**: delete its credential and drop its connections (close code 4003). The device clears its stored credential and must scan a new code.
134
+
135
+ Paired devices (name, browser user agent, pairing and last-seen times, credential hash) and settings survive daemon restarts. They are stored in `~/.pi/agent/pi-companion/state.json`, created with mode 0600 inside a 0700 directory and replaced atomically; a looser mode found on startup is tightened. Override the directory with `PI_COMPANION_HOME`.
136
+
137
+ A phone pairs once with the daemon. Individual Pi sessions still opt in using /companion.
138
+
139
+ ## Settings
140
+
141
+ The **Settings** page (local console only) covers:
142
+
143
+ | Setting | Default | Notes |
144
+ |---|---|---|
145
+ | Theme | System | Light, dark or follow the OS. Stored per browser. |
146
+ | Public address | empty | The tunnel / reverse-proxy URL embedded in pairing codes. `PI_COMPANION_PUBLIC_URL` overrides it and locks the field. |
147
+ | Pairing code lifetime | 5 minutes | 1 to 60. |
148
+ | Largest file | 25 MB | 1 to 100 MB per upload. |
149
+ | Allowed file types | any | Optional MIME allowlist such as `image/*, application/pdf`. Uploads outside it get HTTP 415. |
150
+
151
+ It also shows the daemon version, both addresses, and where settings and session files are stored.
152
+
153
+ ## Temporary files
154
+
155
+ Each session has an isolated temporary directory beneath the operating system temp directory:
156
+
157
+ <temp>/pi-companion/<session-id>/
158
+
159
+ On Unix the daemon attempts to set the Pi Companion and session directories to mode 0700.
160
+
161
+ The browser can upload a file from the Files tab. Current constraints:
162
+
163
+ - 25 MiB maximum per upload by default (Settings); a rejected or interrupted upload leaves no partial file
164
+ - optional MIME allowlist: the type guessed from the file name and any specific declared Content-Type must both be allowed
165
+ - session ids in paths are limited to `[A-Za-z0-9_-]`, so they can never name a directory outside the sandbox
166
+ - filename is reduced to its final path component
167
+ - generated opaque file id prefixes the on-disk name
168
+ - the daemon never accepts a client-provided destination path
169
+ - file deletion accepts only the opaque file id
170
+ - deletion verifies the stored path is still inside that session sandbox
171
+ - sandbox content is removed 30 seconds after a disconnected session fails to reconnect
172
+
173
+ The Pi extension exposes:
174
+
175
+ companion_temp_files
176
+
177
+ which returns the sandbox file paths available to the agent, and:
178
+
179
+ companion_delete_temp_file
180
+
181
+ which destroys one temporary file by opaque id.
182
+
183
+ This means the agent can consume uploaded artifacts using its normal file capabilities while Pi Companion retains ownership of upload placement and cleanup.
184
+
185
+ ## Session identity
186
+
187
+ Every registered Pi session publishes a compact display model to the daemon:
188
+
189
+ - session name
190
+ - short title
191
+ - status: active, idle, or stopped
192
+ - main model
193
+ - thinking effort
194
+ - working directory and process id
195
+ - remote-control state
196
+
197
+ Stopped sessions remain visible in the daemon registry so the dashboard does not lose context when a Pi process exits. Their controls are disabled and their temporary sandbox is cleaned after the reconnect grace period.
198
+
199
+ ## UI
200
+
201
+ A single-page SvelteKit app, embedded in the daemon binary:
202
+
203
+ | Page | Who sees it | What it is for |
204
+ |---|---|---|
205
+ | Overview | everyone | Dot-field hero, live counts, sessions that need your answer, live sessions, Help entry |
206
+ | Sessions | everyone | Searchable, filterable list (Working / Waiting / Ended). Paired devices only see shared sessions |
207
+ | Session detail | everyone | Shell-style transcript that follows the theme: Markdown replies with highlight.js code and Mermaid diagrams, collapsible tool output and thinking. Question sheet, files, git changes, /plan; the composer (message / steer) appears on Activity only. Session details are collapsed under the title |
208
+ | Devices, Settings | local console only | Pairing, connection status, disconnect and revoke; daemon settings with a save bar that appears only for unsaved edits |
209
+ | Help, Privacy, Terms | everyone | Setup stepper, commands, tools, troubleshooting; data handling; terms of use. Help is opened from the Overview |
210
+
211
+ The Live indicator in the header and sidebar opens the connection sheet (status, version and, on this computer, the console and device addresses and data folders).
212
+
213
+ Git problems on the Changes tab (for example a folder that is not a git repository) show as a toast, never in the transcript.
214
+
215
+ Design notes:
216
+
217
+ - light and dark themes with a system default, applied before first paint so there is no flash
218
+ - claymorphism: solid surfaces with an outer drop shadow plus inset highlight and shade (no blur or glass); small icons are Lucide line icons on raised clay wells, feature art is 3D clay renders from [3dicons](https://3dicons.co) (CC0)
219
+ - warm amber on graphite or paper, matching the logo; system fonts only, so nothing loads from the network. highlight.js and Mermaid load only when a reply contains code or a diagram
220
+ - the window never scrolls. The sidebar, page content, activity feed, file list, diff, chips and tabs each scroll inside their own container, and the composer and mobile tab bar stay put
221
+ - the activity feed follows new output and stops following when you scroll up ("Jump to latest" brings you back)
222
+ - mobile: bottom tab bar, safe-area insets, 44px touch targets, 16px inputs (no iOS zoom), Enter inserts a newline on touch keyboards
223
+ - accessibility: skip link, visible focus rings, arrow-key tabs, labelled controls, live regions for the feed and questions, reduced-motion support
224
+ - the dot field is Raksara's component, loaded when the browser is idle, paused when hidden, and static under reduced motion
225
+ - dropping a file anywhere outside the Files drop zone is ignored. Without this, the browser tries to open the file itself (Firefox reports this as "may not load or link to file:///")
226
+
227
+ ### Caching and updates
228
+
229
+ The UI is built with `adapter-static` in SPA mode (`200.html` fallback) and embedded with `rust-embed`:
230
+
231
+ - `/_app/immutable/**` is content-hashed and served with `Cache-Control: public, max-age=31536000, immutable`
232
+ - the shell, `version.json` and icons are served with `no-cache` and a strong ETag, so a reload costs a 304 until something changes
233
+ - unknown file-like paths return 404 instead of the HTML shell, so a stale tab never parses HTML as JavaScript
234
+ - the app polls `version.json` every minute; when a new build is detected it shows a reload banner and the next navigation loads the new version
235
+ - HTML responses carry a same-origin Content-Security-Policy and `X-Frame-Options: DENY`
236
+
237
+ Generated `server/web-dist` files are not committed. CI builds them once and every native build embeds the same bundle.
238
+
239
+ ## Browser controls
240
+
241
+ The initial slice supports:
242
+
243
+ - multiple live Pi sessions
244
+ - prompt
245
+ - steer
246
+ - abort
247
+ - /plan
248
+ - git diff
249
+ - streamed lifecycle/tool/message events
250
+ - companion ask-user requests
251
+ - temporary file upload/list/delete
252
+ - paired-device management
253
+ - per-session remote-control opt-in
254
+
255
+ There is deliberately no arbitrary shell, arbitrary tool invocation, or arbitrary filesystem API.
256
+
257
+ ## Development
258
+
259
+ Requirements: Node 22.17+ and stable Rust.
260
+
261
+ npm install
262
+ npm run serve
263
+
264
+ This builds the UI, starts the daemon and prints where it is listening:
265
+
266
+ Pi Companion v0.1.0
267
+
268
+ Console http://127.0.0.1:43721
269
+ Paired devices http://127.0.0.1:43722
270
+ Pairing links http://127.0.0.1:43722 (default, this computer only; set one in Settings)
271
+ Data ~/.pi/agent/pi-companion (0 paired)
272
+
273
+ If a daemon is already running (for example one a Pi session started automatically), `npm run serve` prints its console address and exits instead of starting a second copy. Stop the running one first (`pkill -f pi-companion-server`) if you want your fresh build to take over. Set `RUST_LOG` (for example `RUST_LOG=debug`) for more detailed logs.
274
+
275
+ Type checks cover the extension (`tsc`) and the UI (`svelte-check`, warnings fail):
276
+
277
+ npm run check
278
+
279
+ Tests cover the question relay (`node --test`) and the daemon's wire protocol, pairing expiry and code throttling, origin policy, bridge and device reconnects, path traversal, upload limits, MIME allowlist, state-file permissions and multi-session isolation (`cargo test`):
280
+
281
+ npm test
282
+
283
+ UI modules import shared code through the `#lib/*` subpath import declared in `ui/package.json` (SvelteKit 3 replaced `$lib`). Subpath imports do not guess extensions, so always write the full file name: `#lib/format.ts`, `#lib/companion.svelte.ts`, `#lib/Icon.svelte`. `ui/tsconfig.json` extends SvelteKit's generated `$app/tsconfig`.
284
+
285
+ Then, in another shell, start Pi with the extension from this checkout:
286
+
287
+ pi -e ./src/index.ts
288
+
289
+ For hot-reloading UI work, keep the daemon running and use:
290
+
291
+ npm run ui:dev
292
+
293
+ which proxies /api and /ws to the daemon on 43721.
294
+
295
+ Running the daemon manually with npm run serve and the extension side by side just works: the extension sees the running daemon and connects to it. If you don't run it, the extension launches your local cargo build automatically.
296
+
297
+ ## Releases
298
+
299
+ The package carries the pi-package keyword, so published npm versions appear in the Pi package gallery (https://pi.dev/packages). Host-provided packages (@earendil-works/pi-coding-agent, typebox) are peer dependencies, as Pi requires.
300
+
301
+ GitHub Actions run only for release tags. Pushing ordinary commits or opening pull requests does not trigger any workflow; CI can also be started manually with workflow_dispatch.
302
+
303
+ A tag matching vX.Y.Z triggers both the CI and release workflows.
304
+
305
+ Before publishing, the workflow requires X.Y.Z to match both package.json and server/Cargo.toml. It then runs TypeScript and Rust checks, builds native daemon archives for Linux x86_64, macOS arm64, macOS x86_64, and Windows x86_64, generates SHA-256 checksums, and creates a GitHub Release.
306
+
307
+ Only after the GitHub Release exists, and only if the repository has an NPM_TOKEN secret, the same tag publishes @yunazgr/pi-companion to npm with provenance, so an npm install never looks for a daemon binary that is not uploaded yet. Git installs need no token.
308
+
309
+ On first use the extension downloads `pi-companion-server-<os>-<arch>` for its own package version (`releases/download/v<version>/`), verifies it against `SHA256SUMS`, and caches it under `~/.pi/agent/pi-companion/bin/<version>/`. A git install from a branch that is ahead of the newest tag falls back to the latest release.
310
+
311
+ ## Security boundary
312
+
313
+ The design follows a few non-negotiable rules:
314
+
315
+ - local admin and remote device surfaces use different ports
316
+ - both listeners bind to loopback
317
+ - only the remote listener should ever sit behind a tunnel
318
+ - pairing invitations are single-use and expire after five minutes; wrong short codes are throttled
319
+ - paired-device secrets are stored only as SHA-256 hashes, in a 0600 state file
320
+ - strict Origin checks: the paired-device surface accepts WebSocket upgrades and POST/DELETE only from its own loopback address, the public URL or `PI_COMPANION_ALLOWED_ORIGINS`, and refuses any foreign Origin; the console accepts only its own loopback origin, so a web page cannot drive Pi through it
321
+ - remote devices only see sessions with remoteEnabled=true
322
+ - uploads are placed only in daemon-created session sandboxes, within the size limit and optional MIME allowlist
323
+ - file deletion is id-based and boundary checked
324
+ - there is no generic shell endpoint
325
+ - Pi remains the authority that performs agent actions
326
+
327
+ ## Next hardening milestones
328
+
329
+ 1. Add per-device session permissions in addition to session opt-in.
330
+ 2. Bound browser event queues by both item count and bytes, dropping low-priority deltas first.
331
+ 3. Add upload count/quota limits per session.
332
+ 4. Package signed/notarized daemon binaries and service installation for macOS/Linux/Windows.
333
+
334
+ ## License
335
+
336
+ MIT. See [LICENSE](LICENSE).
package/SECURITY.md ADDED
@@ -0,0 +1,63 @@
1
+ # Security model
2
+
3
+ Pi Companion intentionally exposes less power than Pi itself.
4
+
5
+ ## Trust zones
6
+
7
+ ### Local admin surface
8
+
9
+ 127.0.0.1:43721
10
+
11
+ Trusted local administrator only. It can create pairing invitations, see which paired devices are connected, disconnect or revoke them, change daemon settings, and inspect all registered sessions.
12
+
13
+ Do not place this listener behind a tunnel or reverse proxy.
14
+
15
+ It has no credentials, so it refuses any request whose `Origin` is not its own loopback address (`http://127.0.0.1:43721`, `localhost`, `[::1]`) or listed in `PI_COMPANION_ALLOWED_ORIGINS`. A web page on another origin can therefore not open its WebSocket and steer Pi. Requests without `Origin` (the Pi bridge, CLI tools) are accepted.
16
+
17
+ ### Paired-device surface
18
+
19
+ 127.0.0.1:43722
20
+
21
+ Designed to sit behind an HTTPS/WSS tunnel. Every control/data endpoint requires a paired-device credential, except the one-time pairing claim endpoint and static pairing UI.
22
+
23
+ Strict Origin checks: WebSocket upgrades and POST/DELETE requests must carry an `Origin` equal to the loopback device address, the configured public URL, or an entry of `PI_COMPANION_ALLOWED_ORIGINS`; GET requests may omit `Origin` but are refused with any foreign one.
24
+
25
+ Pairing invitations are single-use and expire (5 minutes by default). The short typed code is rate limited: ten wrong codes within ten minutes withdraw every open invitation and further code claims get HTTP 429 until the window passes.
26
+
27
+ Paired devices can see only sessions that explicitly enabled remote control.
28
+
29
+ Revoking a device deletes its credential hash and closes its open connections immediately (WebSocket close 4003). Disconnecting closes them without deleting the credential (close 4001).
30
+
31
+ ### Stored state
32
+
33
+ Paired devices (name, browser user agent, pairing and last-seen times, and the SHA-256 hash of the credential; never the credential) and settings are written atomically to `~/.pi/agent/pi-companion/state.json`. On Unix the directory is 0700 and the file is created 0600 before any byte is written; a looser mode found at startup is tightened.
34
+
35
+ ### Web UI hardening
36
+
37
+ HTML responses send a same-origin Content-Security-Policy (no third-party scripts, styles, fonts or connections), `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer` and `X-Content-Type-Options: nosniff`. The UI loads nothing from the network beyond the daemon itself.
38
+
39
+ ### Pi bridge
40
+
41
+ Each Pi process keeps one outbound localhost WebSocket to the daemon. The browser cannot invoke arbitrary extension tools. The allowed command set is a closed protocol.
42
+
43
+ ## Files
44
+
45
+ Uploads are daemon-owned and session-scoped.
46
+
47
+ The browser cannot choose a destination path. The daemon derives a final filename (last path component, no control characters or leading dots), prefixes it with an opaque id, and places it in the current session sandbox. Session ids that reach the filesystem are restricted to `[A-Za-z0-9_-]`.
48
+
49
+ Uploads above the configured size limit are refused (HTTP 413) and their partial file is removed. An optional MIME allowlist (Settings) refuses other types with HTTP 415; both the type implied by the file name and any specific declared Content-Type must be allowed.
50
+
51
+ Agent deletion uses the opaque id rather than a path. Before unlinking, the daemon verifies that the recorded file is still beneath the expected session sandbox.
52
+
53
+ ## Non-goals
54
+
55
+ Pi Companion does not provide:
56
+
57
+ - remote shell access
58
+ - arbitrary filesystem browsing
59
+ - arbitrary tool invocation
60
+ - direct git push/commit endpoints
61
+ - a second copy of Pi conversation state
62
+
63
+ These should remain Pi actions, where Pi's existing policies and agent loop remain in control.
package/package.json CHANGED
@@ -1,6 +1,88 @@
1
1
  {
2
2
  "name": "@yunazgr/pi-companion",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Local-first remote control for Pi sessions: live activity feed, steering, git diff, file drop and phone pairing from a single Rust daemon.",
5
+ "keywords": [
6
+ "pi-package",
7
+ "pi",
8
+ "pi-extension",
9
+ "pi-coding-agent",
10
+ "remote-control",
11
+ "companion",
12
+ "dashboard",
13
+ "mobile"
14
+ ],
15
+ "homepage": "https://github.com/ygrip/pi-companion#readme",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/ygrip/pi-companion.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/ygrip/pi-companion/issues"
22
+ },
23
+ "author": "ygrip <yunaz.gilang@gmail.com>",
24
+ "license": "MIT",
25
+ "type": "module",
26
+ "main": "src/index.ts",
27
+ "files": [
28
+ "src",
29
+ "README.md",
30
+ "SECURITY.md",
31
+ "LICENSE"
32
+ ],
33
+ "engines": {
34
+ "node": ">=22.17"
35
+ },
36
+ "scripts": {
37
+ "serve": "npm run --silent ui:build -- --logLevel warn && cargo run -q --manifest-path server/Cargo.toml",
38
+ "server:dev": "npm run serve",
39
+ "server:check": "npm run ui:build && cargo check --manifest-path server/Cargo.toml",
40
+ "ui:dev": "cd ui && vite dev",
41
+ "ui:build": "cd ui && vite build",
42
+ "check": "tsc --noEmit && npm run ui:check",
43
+ "ui:check": "cd ui && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --fail-on-warnings",
44
+ "test": "npm run test:extension && npm run test:server",
45
+ "test:extension": "node --test --experimental-transform-types --no-warnings test/*.test.ts",
46
+ "test:server": "cargo test --manifest-path server/Cargo.toml"
47
+ },
48
+ "pi": {
49
+ "extensions": [
50
+ "./src/index.ts"
51
+ ],
52
+ "image": "https://raw.githubusercontent.com/ygrip/pi-companion/main/docs/dashboard.png"
53
+ },
54
+ "dependencies": {
55
+ "ws": "^8.18.3"
56
+ },
57
+ "peerDependencies": {
58
+ "@earendil-works/pi-coding-agent": "*",
59
+ "typebox": "*"
60
+ },
61
+ "devDependencies": {
62
+ "@earendil-works/pi-coding-agent": "*",
63
+ "@sveltejs/adapter-static": "^4.0.0",
64
+ "@sveltejs/kit": "^3.0.1",
65
+ "@sveltejs/vite-plugin-svelte": "^7.0.0",
66
+ "@types/node": "^24.7.0",
67
+ "@types/ws": "^8.18.1",
68
+ "dompurify": "^3.4.16",
69
+ "highlight.js": "^11.12.0",
70
+ "jsqr": "^1.4.0",
71
+ "marked": "^18.1.0",
72
+ "mermaid": "^12.1.0",
73
+ "svelte": "^5.57.2",
74
+ "svelte-check": "^4.7.6",
75
+ "typebox": "^1.0.13",
76
+ "typescript": "^6.0.0",
77
+ "vite": "^8.0.12"
78
+ },
79
+ "overrides": {
80
+ "node-domexception": "file:./tools/shims/node-domexception"
81
+ },
82
+ "allowScripts": {
83
+ "esbuild": true,
84
+ "@google/genai": false,
85
+ "protobufjs": false,
86
+ "fsevents": false
87
+ }
88
+ }