pi-sticky-prompt 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pi-sticky-prompt contributors
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 ADDED
@@ -0,0 +1,278 @@
1
+ # pi-sticky-prompt
2
+
3
+ > Always-on-top, full-width macOS prompt bar for [pi](https://github.com/earendil-works/pi).
4
+
5
+ ![sticky bar expanded](./assets/sticky-bar-expanded.png)
6
+
7
+ Pi runs in normal terminal scrollback (not alternate-screen mode), so when
8
+ you scroll the terminal up to read history, the input prompt scrolls out
9
+ of view with everything else. **pi-sticky-prompt** solves that with a
10
+ tiny native macOS window that sits permanently on top of every other
11
+ window, on every space, and talks to your live pi sessions over a Unix
12
+ domain socket.
13
+
14
+ You can scroll the terminal as much as you want — the prompt stays glued
15
+ to the bottom of the screen.
16
+
17
+ ### Demo
18
+
19
+ https://github.com/user-attachments/assets/4b8a7e41-6df2-4bf2-98d3-4cd1513aefd9
20
+
21
+ | Collapsed | Session picker |
22
+ | --- | --- |
23
+ | ![collapsed](./assets/sticky-bar-collapsed.png) | ![session picker](./assets/sticky-bar-session-picker.png) |
24
+
25
+ ```
26
+ ┌──────────────────────────────────┐ ┌──────────────────────────┐
27
+ │ Terminal running pi │ UDS │ PiStickyPrompt.app │
28
+ │ (interactive, scrollback intact) │ ◄────── │ floating NSPanel │
29
+ │ │ │ always on top │
30
+ └──────────────────────────────────┘ └──────────────────────────┘
31
+ ```
32
+
33
+ ## Features
34
+
35
+ - 🪟 **Native floating window** — `NSPanel` with `.floating` level + `.canJoinAllSpaces`. Visible above every app, every space, even fullscreen Ghostty / Terminal / iTerm.
36
+ - 🖥️ **Auto-docked** to the bottom edge of whichever screen has a terminal app open. Plug in a monitor or move Terminal across screens — the bar follows.
37
+ - 🔒 **Lock / unlock** — locked: full-width pinned to bottom; unlocked: free-move, resize, drag between monitors.
38
+ - 📜 **Multi-session aware** — every pi process publishes its own socket; the bar lists all live sessions in a picker (`⌘L`) and remembers your selection across launches.
39
+ - ⌨️ **Global hotkey** `⌘⌥P` to toggle visibility from anywhere on the system.
40
+ - 📉 **Collapse to a one-line preview** of long input (`⌘M`); expanding grows upward leaving the toolbar flush with the screen edge.
41
+ - 🚦 **Status echo** — model, session name, and a live streaming indicator (green = idle, yellow = streaming, red = disconnected).
42
+ - ↩️ **Auto-focus the terminal** after sending — keystrokes you make right after pressing Enter land in the terminal hosting that pi session, not in the bar.
43
+ - 🛑 **Abort current pi turn** with `Esc`; press `Esc` twice quickly to hide the bar.
44
+
45
+ ## Requirements
46
+
47
+ - macOS 13+
48
+ - A pi installation (`@mariozechner/pi-coding-agent`)
49
+ - Xcode command-line tools for building the HUD: `xcode-select --install`
50
+ - Swift 5.9+ (ships with current macOS)
51
+
52
+ Tested with Ghostty, Terminal.app, iTerm2, Alacritty, WezTerm, kitty, and Warp.
53
+
54
+ ## Install
55
+
56
+ pi-sticky-prompt has two halves and each lives in the registry that
57
+ fits it best:
58
+
59
+ | Half | What it does | Where it lives |
60
+ | --- | --- | --- |
61
+ | **Extension** | Lets pi sessions expose themselves over a Unix domain socket so the HUD can find them | npm — `npm:pi-sticky-prompt` |
62
+ | **HUD app** | The native macOS floating window itself | Homebrew cask — `pi-sticky-prompt` |
63
+
64
+ ### 1. Install the pi extension
65
+
66
+ ```bash
67
+ pi install npm:pi-sticky-prompt
68
+ ```
69
+
70
+ Reload pi (`/reload`) or start a fresh session. Each session will now
71
+ publish a socket + descriptor under `~/.pi/agent/sockets/`.
72
+
73
+ ### 2. Install the macOS HUD
74
+
75
+ **Recommended — Homebrew:**
76
+
77
+ ```bash
78
+ brew tap alonmartin2222/pi
79
+ brew install --cask pi-sticky-prompt
80
+ ```
81
+
82
+ First launch may show a Gatekeeper prompt because the app is ad-hoc
83
+ signed — right-click `PiStickyPrompt.app` in `/Applications` and pick
84
+ *Open* once to whitelist it.
85
+
86
+ **Alternative — download the prebuilt zip from a GitHub release:**
87
+
88
+ 1. Grab `PiStickyPrompt.app.zip` from <https://github.com/alonmartin2222/pi-sticky-prompt/releases/latest>
89
+ 2. Unzip and drag `PiStickyPrompt.app` into `/Applications`
90
+
91
+ **Alternative — build from source:**
92
+
93
+ ```bash
94
+ git clone https://github.com/alonmartin2222/pi-sticky-prompt.git
95
+ cd pi-sticky-prompt
96
+ make install # builds and copies the .app to ~/Applications
97
+ make run # builds + opens it
98
+ ```
99
+
100
+ ### 3. Launch
101
+
102
+ Open `PiStickyPrompt.app` (Spotlight / Launchpad / `open -a PiStickyPrompt`).
103
+ Add it to your Login Items in System Settings if you want it always
104
+ running. Press **⌘⌥P** to toggle the bar.
105
+
106
+ ## Usage
107
+
108
+ 1. Start any number of `pi` sessions in any terminal. Each session writes:
109
+ - **socket**: `~/.pi/agent/sockets/pi-<pid>.sock`
110
+ - **descriptor**: `~/.pi/agent/sockets/pi-<pid>.json`
111
+ 2. Launch `PiStickyPrompt.app`. It scans the descriptor directory and
112
+ auto-attaches to the most-recent live session (or the one you previously
113
+ chose).
114
+ 3. Type. **Enter** sends; **Shift+Enter** inserts a newline.
115
+
116
+ ### Keys
117
+
118
+ While the bar has keyboard focus:
119
+
120
+ | Key | Action |
121
+ | ------------------ | --------------------------------------------------- |
122
+ | **⌘⌥P** *(global)* | Toggle bar visibility from anywhere on the system |
123
+ | **Enter** | Send the prompt to the attached pi session |
124
+ | **Shift+Enter** | Insert a newline inside the editor |
125
+ | **Esc** | Abort current pi turn (twice quickly: hide bar) |
126
+ | **⌘M** | Collapse to one-line preview / expand back |
127
+ | **⌘L** | Open the session picker |
128
+ | **⌘W** | Hide the bar |
129
+
130
+ A status-bar icon (`π▸`) also exposes Toggle / Pick Session / Quit.
131
+
132
+ ### Toolbar
133
+
134
+ ```
135
+ [● session-name │ model/name] [≡ 🔒 ▲]
136
+ │ │ │ │ │ │
137
+ │ │ │ │ │ └ collapse / expand
138
+ │ │ │ │ └ lock / unlock
139
+ │ │ │ └ session picker
140
+ │ │ └ provider/model
141
+ │ └ session name (or cwd basename if unnamed)
142
+ └ status dot: green = idle · yellow = streaming · red = disconnected
143
+ ```
144
+
145
+ When idle, sending a prompt triggers a new turn. When pi is mid-turn (yellow
146
+ dot), sending **steers** the running turn instead of queueing — same
147
+ behaviour as typing in the pi TUI itself.
148
+
149
+ ### Lock vs unlock
150
+
151
+ - **🔒 locked** *(default)* — full screen-width, pinned to the bottom of
152
+ whichever screen has a terminal app open. Re-snaps automatically on
153
+ display changes (`NSApplication.didChangeScreenParametersNotification`).
154
+ - **🔓 unlocked** *(orange tint)* — drag from any background pixel to
155
+ move, drag from the edges to resize, drag freely between monitors. The
156
+ last unlocked frame is remembered. Click again to re-dock.
157
+
158
+ ## Architecture
159
+
160
+ Two pieces:
161
+
162
+ ```
163
+ extensions/sticky-prompt.ts ← TypeScript pi extension
164
+ PiStickyPrompt/ ← Swift Package for the macOS HUD
165
+ ├── Package.swift
166
+ ├── Sources/PiStickyPrompt/
167
+ │ ├── main.swift ← entry point, sets accessory activation
168
+ │ ├── AppDelegate.swift ← menu-bar item + global hotkey wiring
169
+ │ ├── HUDController.swift ← owns the panel, picks a session, locking
170
+ │ ├── HUDPanel.swift ← NSPanel subclass; canBecomeKey overrides
171
+ │ ├── PromptView.swift ← top toolbar + editor + status row
172
+ │ ├── PromptTextView.swift ← NSTextView with Enter/Esc/⌘M handling
173
+ │ ├── BridgeClient.swift ← UDS client; line-delimited JSON protocol
174
+ │ ├── SessionDiscovery.swift ← scans ~/.pi/agent/sockets for live pids
175
+ │ ├── TerminalScreen.swift ← finds which NSScreen hosts a terminal
176
+ │ ├── TerminalLocator.swift ← walks parent PIDs to find owning terminal
177
+ │ └── Hotkey.swift ← Carbon RegisterEventHotKey wrapper
178
+ └── make-app.sh ← bundles the binary into a .app
179
+ ```
180
+
181
+ ### Wire protocol
182
+
183
+ Line-delimited JSON over the Unix domain socket (LF only, both directions):
184
+
185
+ ```
186
+ server -> client
187
+ {"type":"hello", pid, cwd, sessionFile, sessionName?, model?, streaming, started}
188
+ {"type":"state", streaming, model?, sessionName?}
189
+ {"type":"ack", ok, command:"prompt"|"abort", error?}
190
+ {"type":"bye"}
191
+
192
+ client -> server
193
+ {"type":"prompt", text}
194
+ {"type":"abort"}
195
+ {"type":"ping"}
196
+ ```
197
+
198
+ You can drive the bridge from the shell to verify it without the HUD:
199
+
200
+ ```bash
201
+ SOCK=$(ls -t ~/.pi/agent/sockets/pi-*.sock | head -1)
202
+ echo '{"type":"prompt","text":"hello from nc"}' | nc -U "$SOCK"
203
+ ```
204
+
205
+ ### Permissions
206
+
207
+ - **No Accessibility permission** required. We never use AX APIs.
208
+ - **No Screen Recording permission** required. `CGWindowListCopyWindowInfo`
209
+ returns window owner + bounds without it; we read only those, never
210
+ pixels or window names.
211
+ - The global hotkey uses Carbon's `RegisterEventHotKey`, which works for
212
+ accessory (LSUIElement) apps without any permission prompts.
213
+
214
+ ### Auto-focus to terminal on send
215
+
216
+ After a successful `prompt` ack, the HUD walks the BSD process tree
217
+ upward from the pi session's PID using `sysctl(KERN_PROC_PID)` until it
218
+ finds an ancestor whose bundle ID matches a known terminal app (Ghostty,
219
+ Terminal, iTerm2, Alacritty, WezTerm, kitty, Warp, Hyper). It then calls
220
+ `NSRunningApplication.activate(.activateIgnoringOtherApps)` on that app.
221
+ This brings the terminal to the front so your next keystroke goes to pi
222
+ output instead of the now-empty input bar.
223
+
224
+ ## Multiple pi sessions
225
+
226
+ The pi extension publishes one socket + descriptor per pi process. The
227
+ HUD scans the directory, hides any whose PID is no longer running, and
228
+ shows the rest in the session picker (`⌘L`). The current selection is
229
+ persisted in `UserDefaults` as `pi.preferredPID` so re-launches reattach
230
+ to the same session if it's still alive.
231
+
232
+ Heads-up: macOS doesn't expose per-window activation through
233
+ `NSRunningApplication`. If you have multiple windows of the same terminal
234
+ app, only the most-recently-focused one of that app comes forward. Per-
235
+ window raising would require Accessibility permission, which this project
236
+ deliberately avoids.
237
+
238
+ ## Disabling
239
+
240
+ - Hide the bar with `⌘⌥P` or quit it from the menu bar.
241
+ - To remove the extension half: `pi remove npm:pi-sticky-prompt` (or
242
+ whichever spec you used to install it).
243
+ - To keep the extension but stop the HUD: just don't launch the app. The
244
+ socket sits unused; pi sessions don't notice.
245
+
246
+ ## Limitations
247
+
248
+ - The bar is **viewport-pinned** because it is a separate macOS window,
249
+ not because pi affects terminal scrollback. Terminal scrollback itself
250
+ is unchanged.
251
+ - One HUD process per machine is the intended deployment. Multiple HUDs
252
+ can connect to the same socket but they will all see each other's
253
+ state echoes.
254
+ - macOS only. The HUD uses AppKit. The pi extension itself is
255
+ cross-platform Node code, but the only client implementation today is
256
+ the macOS app.
257
+ - Tested only on macOS 13+ on Apple Silicon. Intel builds should work
258
+ (the binary is built for `arm64` only by default — drop in a
259
+ universal slice in `make-app.sh` if needed).
260
+
261
+ ## Contributing
262
+
263
+ Issues and PRs welcome at <https://github.com/alonmartin2222/pi-sticky-prompt>.
264
+ The codebase is intentionally small:
265
+
266
+ - `extensions/sticky-prompt.ts` — ~250 lines TypeScript
267
+ - `PiStickyPrompt/Sources/PiStickyPrompt/*.swift` — ~900 lines Swift
268
+
269
+ Build the Swift app with `make debug` to get faster rebuild loops while
270
+ iterating. Use `swift build -c debug` directly if you don't need the
271
+ `.app` bundle.
272
+
273
+ The Carbon `RegisterEventHotKey` symbol signature is `'piPb'` (`0x70695062`)
274
+ — historical, but kept stable so config files stay portable.
275
+
276
+ ## License
277
+
278
+ MIT — see [LICENSE](./LICENSE).
Binary file
Binary file
Binary file
@@ -0,0 +1,252 @@
1
+ /**
2
+ * pi-sticky-prompt
3
+ *
4
+ * Exposes the active pi session over a Unix domain socket so an external,
5
+ * always-on-top input HUD (PiStickyPrompt.app) can send prompts and receive
6
+ * basic state. Each pi process gets its own socket; a sibling JSON
7
+ * descriptor file is published so the HUD can list and pick a session.
8
+ *
9
+ * Layout:
10
+ * ~/.pi/agent/sockets/pi-<pid>.sock — server socket
11
+ * ~/.pi/agent/sockets/pi-<pid>.json — descriptor (pid, cwd, started, ...)
12
+ *
13
+ * Wire protocol (line-delimited JSON, both directions, LF only):
14
+ *
15
+ * server -> client
16
+ * {"type":"hello", pid, cwd, sessionFile, sessionName?, model?, streaming, started}
17
+ * {"type":"state", streaming, model?, sessionName?}
18
+ * {"type":"ack", ok:bool, command:"prompt"|"abort", error?:string}
19
+ * {"type":"bye"}
20
+ *
21
+ * client -> server
22
+ * {"type":"prompt", text:string}
23
+ * {"type":"abort"}
24
+ * {"type":"ping"}
25
+ */
26
+
27
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
28
+ import { createServer, type Server, type Socket } from "node:net";
29
+ import { mkdir, rm, writeFile } from "node:fs/promises";
30
+ import { homedir } from "node:os";
31
+ import { join } from "node:path";
32
+
33
+ const SOCK_DIR = join(homedir(), ".pi", "agent", "sockets");
34
+ const SOCK_PATH = join(SOCK_DIR, `pi-${process.pid}.sock`);
35
+ const DESC_PATH = join(SOCK_DIR, `pi-${process.pid}.json`);
36
+
37
+ interface ClientLine {
38
+ type?: string;
39
+ text?: string;
40
+ }
41
+
42
+ interface ExtState {
43
+ streaming: boolean;
44
+ model?: string;
45
+ sessionName?: string;
46
+ sessionFile?: string;
47
+ }
48
+
49
+ export default function (pi: ExtensionAPI) {
50
+ let server: Server | null = null;
51
+ const clients = new Set<Socket>();
52
+ const state: ExtState = { streaming: false };
53
+
54
+ const send = (sock: Socket, msg: unknown) => {
55
+ try {
56
+ sock.write(JSON.stringify(msg) + "\n");
57
+ } catch {
58
+ /* client gone */
59
+ }
60
+ };
61
+ const broadcast = (msg: unknown) => {
62
+ for (const c of clients) send(c, msg);
63
+ };
64
+
65
+ const writeDescriptor = async () => {
66
+ const desc = {
67
+ pid: process.pid,
68
+ cwd: process.cwd(),
69
+ socket: SOCK_PATH,
70
+ started: Date.now(),
71
+ sessionFile: state.sessionFile,
72
+ sessionName: state.sessionName,
73
+ model: state.model,
74
+ streaming: state.streaming,
75
+ };
76
+ await writeFile(DESC_PATH, JSON.stringify(desc, null, 2));
77
+ };
78
+
79
+ const handleLine = async (sock: Socket, line: string, ctx: any) => {
80
+ let msg: ClientLine;
81
+ try {
82
+ msg = JSON.parse(line);
83
+ } catch {
84
+ send(sock, { type: "ack", ok: false, command: "?", error: "bad json" });
85
+ return;
86
+ }
87
+
88
+ if (msg.type === "ping") {
89
+ send(sock, { type: "pong" });
90
+ return;
91
+ }
92
+
93
+ if (msg.type === "prompt") {
94
+ const text = (msg.text ?? "").trim();
95
+ if (!text) {
96
+ send(sock, { type: "ack", ok: false, command: "prompt", error: "empty" });
97
+ return;
98
+ }
99
+ try {
100
+ if (state.streaming) {
101
+ pi.sendUserMessage(text, { deliverAs: "steer" });
102
+ } else {
103
+ pi.sendUserMessage(text);
104
+ }
105
+ send(sock, { type: "ack", ok: true, command: "prompt" });
106
+ } catch (e: any) {
107
+ send(sock, { type: "ack", ok: false, command: "prompt", error: String(e?.message ?? e) });
108
+ }
109
+ return;
110
+ }
111
+
112
+ if (msg.type === "abort") {
113
+ try {
114
+ if (typeof ctx?.abort === "function") {
115
+ ctx.abort();
116
+ }
117
+ send(sock, { type: "ack", ok: true, command: "abort" });
118
+ } catch (e: any) {
119
+ send(sock, { type: "ack", ok: false, command: "abort", error: String(e?.message ?? e) });
120
+ }
121
+ return;
122
+ }
123
+
124
+ send(sock, { type: "ack", ok: false, command: msg.type ?? "?", error: "unknown command" });
125
+ };
126
+
127
+ const onConnection = (sock: Socket, ctx: any) => {
128
+ clients.add(sock);
129
+ sock.setEncoding("utf8");
130
+
131
+ send(sock, {
132
+ type: "hello",
133
+ pid: process.pid,
134
+ cwd: process.cwd(),
135
+ sessionFile: state.sessionFile,
136
+ sessionName: state.sessionName,
137
+ model: state.model,
138
+ streaming: state.streaming,
139
+ started: Date.now(),
140
+ });
141
+
142
+ let buf = "";
143
+ sock.on("data", (chunk) => {
144
+ buf += chunk.toString();
145
+ let idx: number;
146
+ while ((idx = buf.indexOf("\n")) !== -1) {
147
+ const line = buf.slice(0, idx).replace(/\r$/, "");
148
+ buf = buf.slice(idx + 1);
149
+ if (line.length > 0) void handleLine(sock, line, ctx);
150
+ }
151
+ });
152
+ const cleanup = () => {
153
+ clients.delete(sock);
154
+ try { sock.destroy(); } catch { /* ignore */ }
155
+ };
156
+ sock.on("error", cleanup);
157
+ sock.on("close", cleanup);
158
+ };
159
+
160
+ const startServer = async (ctx: any) => {
161
+ await mkdir(SOCK_DIR, { recursive: true });
162
+ // Stale socket from a previous crashed pi with same pid (very unlikely)
163
+ await rm(SOCK_PATH, { force: true });
164
+
165
+ server = createServer((sock) => onConnection(sock, ctx));
166
+ await new Promise<void>((resolve, reject) => {
167
+ server!.once("error", reject);
168
+ server!.listen(SOCK_PATH, () => {
169
+ server!.removeListener("error", reject);
170
+ resolve();
171
+ });
172
+ });
173
+ await writeDescriptor();
174
+ };
175
+
176
+ const stopServer = async () => {
177
+ broadcast({ type: "bye" });
178
+ for (const c of clients) {
179
+ try { c.end(); } catch { /* ignore */ }
180
+ }
181
+ clients.clear();
182
+ if (server) {
183
+ await new Promise<void>((resolve) => server!.close(() => resolve()));
184
+ server = null;
185
+ }
186
+ await rm(SOCK_PATH, { force: true });
187
+ await rm(DESC_PATH, { force: true });
188
+ };
189
+
190
+ const refreshState = (next: Partial<ExtState>) => {
191
+ Object.assign(state, next);
192
+ broadcast({
193
+ type: "state",
194
+ streaming: state.streaming,
195
+ model: state.model,
196
+ sessionName: state.sessionName,
197
+ });
198
+ // Best-effort descriptor refresh; non-fatal if it fails.
199
+ writeDescriptor().catch(() => undefined);
200
+ };
201
+
202
+ pi.on("session_start", async (_event, ctx) => {
203
+ state.sessionFile = ctx.sessionManager.getSessionFile() ?? undefined;
204
+ state.sessionName = pi.getSessionName?.() ?? undefined;
205
+ state.model = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
206
+ state.streaming = false;
207
+
208
+ try {
209
+ await startServer(ctx);
210
+ ctx.ui?.notify?.(`pi-sticky-prompt listening on ${SOCK_PATH}`, "info");
211
+ } catch (e: any) {
212
+ ctx.ui?.notify?.(`pi-sticky-prompt failed to start: ${e?.message ?? e}`, "error");
213
+ }
214
+ });
215
+
216
+ pi.on("session_shutdown", async () => {
217
+ await stopServer();
218
+ });
219
+
220
+ pi.on("agent_start", async () => {
221
+ refreshState({ streaming: true });
222
+ });
223
+ pi.on("agent_end", async () => {
224
+ refreshState({ streaming: false });
225
+ });
226
+
227
+ pi.on("model_select", async (event) => {
228
+ const m = event.model;
229
+ refreshState({ model: m ? `${m.provider}/${m.id}` : undefined });
230
+ });
231
+
232
+ // Reflect /set-session-name etc. — there is no dedicated event, so we
233
+ // poll on each turn end (cheap, only runs once per LLM turn).
234
+ pi.on("turn_end", async () => {
235
+ const name = pi.getSessionName?.();
236
+ if (name !== state.sessionName) refreshState({ sessionName: name });
237
+ });
238
+
239
+ pi.registerCommand("prompt-bar-status", {
240
+ description: "Show pi-sticky-prompt status",
241
+ handler: async (_args, ctx) => {
242
+ const lines = [
243
+ `socket : ${SOCK_PATH}`,
244
+ `desc : ${DESC_PATH}`,
245
+ `clients: ${clients.size}`,
246
+ `stream : ${state.streaming}`,
247
+ `model : ${state.model ?? "-"}`,
248
+ ];
249
+ ctx.ui.notify(lines.join("\n"), "info");
250
+ },
251
+ });
252
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "pi-sticky-prompt",
3
+ "version": "0.1.1",
4
+ "description": "Always-on-top, full-width macOS prompt bar for pi. A floating native window that survives terminal scrollback and lets you keep typing while you read scrollback history.",
5
+ "keywords": [
6
+ "pi-package",
7
+ "pi-extension",
8
+ "macos",
9
+ "prompt-bar",
10
+ "hud",
11
+ "always-on-top",
12
+ "ghostty",
13
+ "terminal"
14
+ ],
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/alonmartin2222/pi-sticky-prompt.git"
19
+ },
20
+ "homepage": "https://github.com/alonmartin2222/pi-sticky-prompt#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/alonmartin2222/pi-sticky-prompt/issues"
23
+ },
24
+ "type": "module",
25
+ "engines": {
26
+ "node": ">=20"
27
+ },
28
+ "peerDependencies": {
29
+ "@mariozechner/pi-coding-agent": "*"
30
+ },
31
+ "pi": {
32
+ "extensions": ["./extensions/sticky-prompt.ts"],
33
+ "video": "https://github.com/alonmartin2222/pi-sticky-prompt/releases/download/v0.1.1/demo.mp4",
34
+ "image": "https://raw.githubusercontent.com/alonmartin2222/pi-sticky-prompt/main/assets/sticky-bar-expanded.png"
35
+ },
36
+ "files": [
37
+ "extensions/",
38
+ "assets/",
39
+ "README.md",
40
+ "LICENSE"
41
+ ]
42
+ }