@tryinget/pi-activity-strip 0.4.0 → 0.5.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/README.md +96 -152
- package/bin/pi-activity-strip.mjs +31 -62
- package/native/bin/linux-x64-gnu/artifact.json +21 -0
- package/native/bin/linux-x64-gnu/pi-activity-strip-panel +0 -0
- package/native/panel/Cargo.lock +1045 -0
- package/native/panel/Cargo.toml +23 -0
- package/native/panel/src/app.rs +606 -0
- package/native/panel/src/card_view.rs +303 -0
- package/native/panel/src/main.rs +31 -0
- package/native/panel/src/protocol.rs +234 -0
- package/native/panel/src/runtime.rs +110 -0
- package/native/panel/src/style.css +176 -0
- package/package.json +13 -1
- package/src/broker/server.mjs +43 -7
- package/src/broker/session-store.mjs +41 -13
- package/src/client/broker-client.mjs +10 -3
- package/src/client/session-telemetry.mjs +54 -14
- package/src/common/activity-order.mjs +15 -9
- package/src/common/alignment-controller.mjs +24 -42
- package/src/common/card-display.mjs +5 -0
- package/src/common/compatibility.mjs +15 -13
- package/src/common/constants.mjs +10 -28
- package/src/common/contracts.d.ts +42 -4
- package/src/common/contracts.ts +42 -4
- package/src/common/niri-focus.mjs +79 -79
- package/src/common/protocol.mjs +48 -16
- package/src/common/session-cards.mjs +102 -0
- package/src/common/status-report.mjs +2 -1
- package/src/common/telemetry.mjs +8 -1
- package/src/common/terminal-identity.mjs +149 -0
- package/src/native/main.mjs +393 -0
- package/src/native/panel-projection.mjs +99 -0
- package/src/{electron/niri-workspace-events.mjs → native/workspace-events.mjs} +4 -1
- package/src/common/electron.mjs +0 -57
- package/src/electron/main.mjs +0 -499
- package/src/electron/niri-native-window-runtime.mjs +0 -178
- package/src/electron/preload.cjs +0 -50
- package/src/electron/renderer-visibility-runtime.mjs +0 -107
- package/src/electron/workspace-view-runtime.mjs +0 -360
- package/src/ui/strip-html.mjs +0 -500
package/README.md
CHANGED
|
@@ -1,91 +1,77 @@
|
|
|
1
1
|
---
|
|
2
|
-
summary: "
|
|
2
|
+
summary: "Native Niri layer-shell activity ribbon for live Pi sessions."
|
|
3
3
|
read_when:
|
|
4
4
|
- "Starting work in this package workspace."
|
|
5
|
-
- "Installing or verifying the
|
|
5
|
+
- "Installing or verifying the Activity Strip in Pi."
|
|
6
6
|
system4d:
|
|
7
|
-
container: "Monorepo package
|
|
8
|
-
compass: "
|
|
9
|
-
engine: "Pi
|
|
10
|
-
fog: "Main risks are
|
|
7
|
+
container: "Monorepo package with a Node telemetry broker/controller and native GTK4 layer-shell panel."
|
|
8
|
+
compass: "Show exact workspace-local Pi activity without changing normal terminal workflows or mutating compositor configuration."
|
|
9
|
+
engine: "Pi telemetry -> local broker -> Niri workspace projection -> native layer-shell panel."
|
|
10
|
+
fog: "Main risks are native ABI availability, stale-session identity, and unverified multi-output behavior."
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# @tryinget/pi-activity-strip
|
|
14
14
|
|
|
15
|
-
A
|
|
15
|
+
A screen-top activity ribbon for live Pi sessions running in Ghostty.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
- multiple Ghostty tabs
|
|
19
|
-
- multiple Pi sessions
|
|
20
|
-
- a persistent top-row ribbon
|
|
21
|
-
- fine-grained live detail without changing how you normally run Pi
|
|
17
|
+
The runtime is Electron-free. A Node controller retains the tested telemetry, identity, ordering, and exact-focus logic; a small Rust/Relm4/GTK4 panel owns rendering and the Wayland layer-shell surface.
|
|
22
18
|
|
|
23
19
|
## What it does
|
|
24
20
|
|
|
25
|
-
- auto-starts
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
- marks the Pi session in the currently focused Niri/Ghostty terminal with a stronger border and left rail, without adding another label
|
|
37
|
-
- keeps green `done`/`monitoring` cards directly beside the Activity tile, followed by active work and then other settled sessions, on a calm 15-second ordering clock
|
|
38
|
-
- reveals prompt, response, path, and full activity detail on hover or keyboard focus
|
|
39
|
-
- focuses the exact matching Ghostty/Niri window on click or Enter, failing closed when identity is missing or ambiguous
|
|
40
|
-
- keeps an aligned strip resident on its Niri workspace while that workspace still has tracked terminals, so visiting an empty workspace does not unmap or reposition it
|
|
21
|
+
- auto-starts with interactive Pi TUI sessions
|
|
22
|
+
- shows one card per admitted Ghostty terminal on the focused Niri workspace
|
|
23
|
+
- aggregates independent publishers beneath stable terminal cards
|
|
24
|
+
- displays repo, phase, tool, detail, elapsed time, and freshness
|
|
25
|
+
- marks the exact currently focused terminal card
|
|
26
|
+
- keeps monitoring-success cards beside the Activity tile, then active and settled cards
|
|
27
|
+
- expands rich details on hover or keyboard focus
|
|
28
|
+
- supports Left/Right navigation and Shift+Left/Right manual movement
|
|
29
|
+
- focuses the exact matching Ghostty window on click or Enter
|
|
30
|
+
- hides completely on workspaces without tracked cards
|
|
31
|
+
- releases its 84px exclusive zone automatically when hidden or crashed
|
|
41
32
|
|
|
42
33
|
## Architecture
|
|
43
34
|
|
|
44
35
|
```text
|
|
45
|
-
Pi
|
|
46
|
-
->
|
|
47
|
-
->
|
|
48
|
-
->
|
|
36
|
+
Pi publisher streams
|
|
37
|
+
-> Node Unix-socket broker and session store
|
|
38
|
+
-> exact terminal identity + Niri workspace projection
|
|
39
|
+
-> versioned NDJSON child protocol
|
|
40
|
+
-> Rust / Relm4 / GTK4 panel
|
|
41
|
+
-> wlr-layer-shell top surface with an 84px exclusive zone
|
|
49
42
|
```
|
|
50
43
|
|
|
51
|
-
|
|
52
|
-
It does not require moving your workflow onto `pi-server` first.
|
|
44
|
+
Layer-shell replaces the old floating Electron window and dynamic Niri-config strut helper. The package no longer edits `~/.config/niri/config.kdl`, resets tiled heights, or requires Electron.
|
|
53
45
|
|
|
54
|
-
|
|
46
|
+
The compact surface is 84px tall. One engaged card expands the surface to 252px while the exclusive zone remains 84px, so detail overlays content without repeatedly resizing tiled windows.
|
|
55
47
|
|
|
56
|
-
|
|
57
|
-
- local per-host broker
|
|
58
|
-
- primary-display top-row strip
|
|
59
|
-
- one card per tracked live Pi terminal on the focused Niri workspace, regardless of activity state
|
|
60
|
-
- headless-safe telemetry publishing
|
|
61
|
-
- explicit open/focus-strip/focus-session/status/doctor/snapshot/fix-top/stop commands
|
|
62
|
-
- focus-scoped Left/Right navigation and Shift+Left/Right manual card movement
|
|
63
|
-
- local visual capture helpers so the agent can inspect the strip directly
|
|
64
|
-
|
|
65
|
-
Not implemented yet:
|
|
66
|
-
- multi-monitor strip replication
|
|
67
|
-
- historical timeline
|
|
68
|
-
- persisted manual card order across strip restarts
|
|
69
|
-
- remote observers via `pi-server`
|
|
48
|
+
## Supported host
|
|
70
49
|
|
|
71
|
-
|
|
50
|
+
The packaged native artifact currently supports:
|
|
72
51
|
|
|
73
|
-
|
|
52
|
+
- Linux x86_64
|
|
53
|
+
- Wayland
|
|
54
|
+
- a compositor implementing `wlr-layer-shell` (dogfooded on Niri 26.04)
|
|
55
|
+
- GTK4 and gtk4-layer-shell runtime libraries
|
|
56
|
+
|
|
57
|
+
On Arch Linux:
|
|
74
58
|
|
|
75
59
|
```bash
|
|
76
|
-
|
|
77
|
-
pi install "$PWD"
|
|
60
|
+
sudo pacman -S gtk4 gtk4-layer-shell
|
|
78
61
|
```
|
|
79
62
|
|
|
80
|
-
|
|
81
|
-
- run `/reload` in each tab you want tracked
|
|
63
|
+
Multi-output replication remains unimplemented. The current surface is single-output and must not be described as multi-monitor complete.
|
|
82
64
|
|
|
83
|
-
|
|
84
|
-
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cd ~/ai-society/softwareco/owned/pi-extensions/packages/pi-activity-strip
|
|
69
|
+
pi install "$PWD"
|
|
70
|
+
```
|
|
85
71
|
|
|
86
|
-
|
|
72
|
+
Reload already-running Pi tabs with `/reload`. New tabs load the package automatically.
|
|
87
73
|
|
|
88
|
-
|
|
74
|
+
## Commands
|
|
89
75
|
|
|
90
76
|
```bash
|
|
91
77
|
npm run strip:open
|
|
@@ -96,7 +82,7 @@ npm run strip:fix-top
|
|
|
96
82
|
npm run strip:stop
|
|
97
83
|
```
|
|
98
84
|
|
|
99
|
-
|
|
85
|
+
Direct CLI:
|
|
100
86
|
|
|
101
87
|
```bash
|
|
102
88
|
node ./bin/pi-activity-strip.mjs open
|
|
@@ -105,130 +91,88 @@ node ./bin/pi-activity-strip.mjs focus-session <full-pi-session-id>
|
|
|
105
91
|
node ./bin/pi-activity-strip.mjs status
|
|
106
92
|
node ./bin/pi-activity-strip.mjs doctor
|
|
107
93
|
node ./bin/pi-activity-strip.mjs snapshot
|
|
108
|
-
node ./bin/pi-activity-strip.mjs
|
|
94
|
+
node ./bin/pi-activity-strip.mjs stop
|
|
109
95
|
```
|
|
110
96
|
|
|
111
|
-
|
|
97
|
+
`fix-top` is now a compatibility no-op: layer-shell placement is compositor-owned.
|
|
112
98
|
|
|
113
|
-
|
|
99
|
+
## Keyboard-only entry
|
|
114
100
|
|
|
115
|
-
|
|
116
|
-
/activity-strip
|
|
117
|
-
/activity-strip status
|
|
118
|
-
/activity-strip doctor
|
|
119
|
-
/activity-strip fix-top
|
|
120
|
-
/activity-strip stop
|
|
121
|
-
/activity-strip-stop
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
In Pi with UI support:
|
|
125
|
-
- `/activity-strip status` opens a detailed runtime status report when an editor surface is available
|
|
126
|
-
- `/activity-strip doctor` opens the host-compatibility report
|
|
127
|
-
|
|
128
|
-
## Interaction model
|
|
129
|
-
|
|
130
|
-
- **Workspace locality:** one strip follows the Niri workspace selected with Up/Down and renders only tracked Pi terminals whose exact Ghostty windows are on that workspace. Focused-workspace events trigger reconciliation immediately, with polling retained as a fallback. When an empty workspace is visited, an aligned strip whose resident workspace still has tracked terminals remains rendered on that prior workspace; Niri keeps it off the empty workspace and returning brings the already-positioned strip back with its row. If its resident terminals disappear, the renderer is concealed and input-disabled. When an actual remap or floating correction is unavoidable, reveal waits beyond Niri's compositor movement animation and then re-verifies placement and membership. The broker remains global and non-Niri desktops retain the global card view.
|
|
131
|
-
- **Ordering:** green `done` cards whose footer reads `monitoring` stay at the far left beside the Activity tile. Active tool/thinking/waiting cards follow, then other settled cards. The group order refreshes every 15 seconds rather than on every telemetry packet; text and timers still update live.
|
|
132
|
-
- **Current terminal:** on Niri, the card matching the focused Ghostty window gets a stronger border and left rail without an extra label. Matching uses the same exact session-title identity seam as click-to-focus and fails closed when focus or identity is missing or ambiguous.
|
|
133
|
-
- **Pointer:** hover expands the strip and reveals detail, last prompt, assistant preview, and path. Leaving the strip or activating another window collapses it immediately. Single click asks Niri to focus the one Ghostty title carrying that exact Pi session-id suffix.
|
|
134
|
-
- **Keyboard inside the strip:** Left/Right changes card focus, Enter activates the focused card, and Shift+Left/Right manually moves it. Manual movement lasts until a later activity regroup or runtime restart.
|
|
135
|
-
- **Fail-closed focus:** current telemetry uses the exact Pi identity directly. For already-running tabs that still publish a legacy broker id, the strip may recover only the process-bound `pi-session-presence` sidecar when its source, PID, and cwd all agree; this is not repo-name guessing or arbitrary-PID focusing. If identity still matches zero or multiple Ghostty windows, focus does nothing and asks for one `/reload`.
|
|
136
|
-
|
|
137
|
-
### Keyboard-only entry on Niri
|
|
138
|
-
|
|
139
|
-
The package deliberately does not reserve a global Electron shortcut. Bind one compositor key to the fail-closed CLI entrypoint instead:
|
|
101
|
+
Bind a Niri key to the fail-closed CLI entrypoint:
|
|
140
102
|
|
|
141
103
|
```kdl
|
|
142
104
|
binds {
|
|
143
|
-
Mod+Shift+A { spawn "node" "/home/tryinget/ai-society/softwareco/owned/pi-extensions/packages/pi-activity-strip/bin/pi-activity-strip.mjs" "focus-strip"; }
|
|
105
|
+
Mod+Shift+A repeat=false allow-inhibiting=false hotkey-overlay-title="Toggle Pi Activity Ribbon keyboard mode" { spawn "/usr/bin/node" "/home/tryinget/ai-society/softwareco/owned/pi-extensions/packages/pi-activity-strip/bin/pi-activity-strip.mjs" "focus-strip"; }
|
|
144
106
|
}
|
|
145
107
|
```
|
|
146
108
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
## Verification commands
|
|
109
|
+
The shortcut toggles exclusive keyboard mode. On entry, the first card is selected. Left/Right wraps through cards, Shift+Left/Right moves the selected card with wraparound, Enter focuses its exact Ghostty terminal and exits, and Escape or a second shortcut press exits without activation. Space does not activate a card. Pointer hover expands detail but deliberately does not capture keyboard input. Empty workspaces and click-through mode reject keyboard entry.
|
|
150
110
|
|
|
151
|
-
|
|
111
|
+
## Interaction contract
|
|
152
112
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
113
|
+
- **Workspace locality:** Niri focus events trigger immediate reprojection; bounded polling remains a fallback.
|
|
114
|
+
- **Hide/reclaim:** zero cards unmaps the layer surface. Niri then removes its exclusive zone as part of normal Wayland surface lifecycle.
|
|
115
|
+
- **Crash behavior:** panel lifetime is bound to the Node controller through Linux parent-death signaling and stdin EOF. Unexpected panel exits are restarted with bounded backoff; a dead surface cannot retain an exclusive zone.
|
|
116
|
+
- **Exact focus:** card activation returns to Node, which performs existing fail-closed terminal identity resolution and Niri focus.
|
|
117
|
+
- **Ordering:** monitoring, active, and settled groups refresh on a calm 15-second clock. Manual moves survive until regroup or restart.
|
|
118
|
+
- **Accessibility:** cards expose native GTK labels, selected/expanded state, activation descriptions, and GTK accessible announcements.
|
|
158
119
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
```bash
|
|
162
|
-
npm run strip:open
|
|
163
|
-
npm run strip:status
|
|
164
|
-
npm run strip:doctor
|
|
165
|
-
npm run strip:snapshot
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Capture what the agent should inspect
|
|
120
|
+
## Environment controls
|
|
169
121
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
122
|
+
- `PI_ACTIVITY_STRIP_AUTO_START=0` disables extension autostart.
|
|
123
|
+
- `PI_ACTIVITY_STRIP_CLICK_THROUGH=1` installs an empty Wayland input region and disables keyboard entry.
|
|
124
|
+
- `PI_ACTIVITY_STRIP_NATIVE_PANEL_BIN=/absolute/path` selects another receipted panel artifact.
|
|
125
|
+
- `PI_ACTIVITY_STRIP_SOCKET_DIR` and `PI_ACTIVITY_STRIP_SOCKET_PATH` isolate broker fixtures and nested-compositor tests.
|
|
174
126
|
|
|
175
|
-
|
|
127
|
+
Unverified native binaries are rejected unless `PI_ACTIVITY_STRIP_ALLOW_UNVERIFIED_PANEL=1` is explicitly set for development fixtures.
|
|
176
128
|
|
|
177
|
-
|
|
129
|
+
## Verification
|
|
178
130
|
|
|
179
131
|
```bash
|
|
180
|
-
npm run
|
|
132
|
+
npm run native:build # exact Rust 1.98 build, tests, staged artifact receipt
|
|
133
|
+
npm run check # canonical Node/package quality gate
|
|
134
|
+
npm run native:check # Rust formatting and tests
|
|
135
|
+
npm run release:check # full packed install and native-artifact smoke
|
|
181
136
|
```
|
|
182
137
|
|
|
183
|
-
|
|
138
|
+
The staged artifact receipt binds:
|
|
184
139
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
140
|
+
- binary SHA-256
|
|
141
|
+
- Cargo lock SHA-256
|
|
142
|
+
- complete Rust/CSS source SHA-256
|
|
143
|
+
- Rust compiler version
|
|
144
|
+
- glibc symbol floor
|
|
145
|
+
- required shared libraries
|
|
188
146
|
|
|
189
|
-
|
|
190
|
-
- opens the strip
|
|
191
|
-
- runs a real headless Pi session with this extension loaded
|
|
192
|
-
- exercises a real tool call
|
|
193
|
-
- verifies that the broker observed the session while it was active
|
|
194
|
-
|
|
195
|
-
### Compatibility diagnostics
|
|
147
|
+
Live verification must inspect Niri layers rather than regular windows:
|
|
196
148
|
|
|
197
149
|
```bash
|
|
198
|
-
|
|
199
|
-
node ./bin/pi-activity-strip.mjs doctor --json
|
|
150
|
+
niri msg -j layers | jq '[.[] | select(.namespace == "pi-activity-strip")]'
|
|
200
151
|
```
|
|
201
152
|
|
|
202
|
-
|
|
203
|
-
- whether a graphical session is present
|
|
204
|
-
- whether Electron can be resolved
|
|
205
|
-
- whether Niri-specific top-edge repair is available
|
|
206
|
-
- whether the current setup is multi-display even though the strip remains primary-display-only
|
|
207
|
-
|
|
208
|
-
## Environment controls
|
|
153
|
+
## Current scope
|
|
209
154
|
|
|
210
|
-
|
|
211
|
-
- disable automatic strip opening on Pi session start
|
|
212
|
-
- `PI_ACTIVITY_STRIP_CLICK_THROUGH=1`
|
|
213
|
-
- opt out of interaction and restore a mouse-transparent overlay; interactive hover/click/keyboard behavior is the default
|
|
214
|
-
- `PI_ACTIVITY_STRIP_ELECTRON_BIN=/path/to/electron`
|
|
215
|
-
- override Electron binary discovery
|
|
216
|
-
- `GLIMPSE_ELECTRON_BIN=/path/to/electron`
|
|
217
|
-
- shared Electron override also respected
|
|
155
|
+
Implemented:
|
|
218
156
|
|
|
219
|
-
|
|
157
|
+
- local broker and telemetry publishers
|
|
158
|
+
- native GTK4 layer-shell rendering
|
|
159
|
+
- workspace-local Niri projection
|
|
160
|
+
- hide/reclaim and restore
|
|
161
|
+
- pointer and keyboard card interaction
|
|
162
|
+
- exact Ghostty activation
|
|
163
|
+
- bounded panel restart and parent-death cleanup
|
|
164
|
+
- click-through input region
|
|
220
165
|
|
|
221
|
-
|
|
166
|
+
Not implemented:
|
|
222
167
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
5. ensure `pi-little-helpers` session presence is loaded when you want exact click-to-Ghostty focus; its `· <full-32-hex-session-id-token>` title suffix is the preferred fail-closed identity seam (an 8-hex legacy title remains usable only when no legacy duplicate or migrated full title shares its prefix)
|
|
168
|
+
- one panel per output
|
|
169
|
+
- historical timeline
|
|
170
|
+
- persisted manual ordering
|
|
171
|
+
- remote observers via `pi-server`
|
|
228
172
|
|
|
229
173
|
## References
|
|
230
174
|
|
|
231
175
|
- [Project vision](docs/project/vision.md)
|
|
232
176
|
- [Project resources](docs/project/resources.md)
|
|
233
|
-
- [Verification
|
|
234
|
-
- [
|
|
177
|
+
- [Verification evidence](docs/project/verification.md)
|
|
178
|
+
- [Superseded adaptive-strut design investigation](docs/project/2026-08-31-adaptive-niri-space-design.md)
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
// - "operating or diagnosing the activity strip from a terminal"
|
|
6
6
|
// ---
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import fs from "node:fs";
|
|
9
10
|
import path from "node:path";
|
|
10
11
|
import { setTimeout as delay } from "node:timers/promises";
|
|
11
12
|
import { fileURLToPath } from "node:url";
|
|
12
|
-
import { promisify } from "node:util";
|
|
13
13
|
import {
|
|
14
14
|
getBrokerStatus,
|
|
15
15
|
isBrokerAlive,
|
|
@@ -20,61 +20,22 @@ import {
|
|
|
20
20
|
assessActivityStripCompatibility,
|
|
21
21
|
formatCompatibilityReport,
|
|
22
22
|
} from "../src/common/compatibility.mjs";
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
import {
|
|
24
|
+
ACTIVITY_STRIP_SOCKET_DIR,
|
|
25
|
+
ACTIVITY_STRIP_START_TIMEOUT_MS,
|
|
26
|
+
} from "../src/common/constants.mjs";
|
|
26
27
|
import { makeMessage } from "../src/common/protocol.mjs";
|
|
27
28
|
import { formatBrokerRuntimeStatus } from "../src/common/status-report.mjs";
|
|
28
29
|
|
|
29
30
|
const __filename = fileURLToPath(import.meta.url);
|
|
30
31
|
const __dirname = path.dirname(__filename);
|
|
31
|
-
const
|
|
32
|
-
const
|
|
32
|
+
const nativeEntry = path.resolve(__dirname, "..", "src", "native", "main.mjs");
|
|
33
|
+
const runtimeLockPath = path.join(ACTIVITY_STRIP_SOCKET_DIR, "runtime.lock");
|
|
33
34
|
|
|
34
35
|
function usage() {
|
|
35
36
|
console.log(
|
|
36
|
-
`Usage: pi-activity-strip <open|focus-strip|focus-session|status|doctor|snapshot|fix-top|stop|serve> [options]\n\nCommands:\n open Start the interactive top-row activity strip (--click-through opts out)\n focus-strip Focus the visible strip already resident on the focused Niri workspace\n focus-session ID Focus the one Ghostty/Niri window matching an exact Pi session identity\n status Check broker + overlay readiness and surface runtime warnings\n doctor Inspect host compatibility assumptions before opening the strip\n snapshot Print the current broker snapshot as JSON\n fix-top
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function moveStripToTop() {
|
|
41
|
-
const { stdout } = await execFileAsync("niri", ["msg", "-j", "windows"], {
|
|
42
|
-
env: process.env,
|
|
43
|
-
});
|
|
44
|
-
const windows = JSON.parse(stdout);
|
|
45
|
-
if (!Array.isArray(windows)) {
|
|
46
|
-
throw new Error("Unexpected niri windows payload");
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const stripWindow = resolveActivityStripWindow(windows);
|
|
50
|
-
if (!stripWindow) {
|
|
51
|
-
throw new Error("Could not find one unique Pi Activity Strip window in niri");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const layout = /** @type {{ tile_pos_in_workspace_view?: unknown[] }} */ (
|
|
55
|
-
stripWindow.layout ?? {}
|
|
37
|
+
`Usage: pi-activity-strip <open|focus-strip|focus-session|status|doctor|snapshot|fix-top|stop|serve> [options]\n\nCommands:\n open Start the interactive top-row activity strip (--click-through opts out)\n focus-strip Focus the visible strip already resident on the focused Niri workspace\n focus-session ID Focus the one Ghostty/Niri window matching an exact Pi session identity\n status Check broker + overlay readiness and surface runtime warnings\n doctor Inspect host compatibility assumptions before opening the strip\n snapshot Print the current broker snapshot as JSON\n fix-top Confirm compositor-owned layer-shell placement\n stop Ask the running strip to shut down\n serve Internal helper; starts the native runtime in the foreground\n`,
|
|
56
38
|
);
|
|
57
|
-
const currentY = Number(layout.tile_pos_in_workspace_view?.[1] ?? 0);
|
|
58
|
-
if (Math.abs(currentY) < 1) {
|
|
59
|
-
return 0;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
await execFileAsync(
|
|
63
|
-
"niri",
|
|
64
|
-
[
|
|
65
|
-
"msg",
|
|
66
|
-
"action",
|
|
67
|
-
"move-floating-window",
|
|
68
|
-
"--id",
|
|
69
|
-
String(stripWindow.id),
|
|
70
|
-
"-y",
|
|
71
|
-
String(-Math.round(currentY)),
|
|
72
|
-
],
|
|
73
|
-
{ env: process.env },
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
console.log("Moved activity strip to the top edge.");
|
|
77
|
-
return 0;
|
|
78
39
|
}
|
|
79
40
|
|
|
80
41
|
async function waitForBrokerReady(timeoutMs = ACTIVITY_STRIP_START_TIMEOUT_MS) {
|
|
@@ -111,8 +72,19 @@ async function waitForBrokerReady(timeoutMs = ACTIVITY_STRIP_START_TIMEOUT_MS) {
|
|
|
111
72
|
/** @param {{ detached?: boolean }} [options] @returns {Promise<number>} */
|
|
112
73
|
async function openStrip({ detached = true } = {}) {
|
|
113
74
|
if (await isBrokerAlive()) {
|
|
114
|
-
|
|
115
|
-
|
|
75
|
+
const status = await getBrokerStatus({ expectReply: true }).catch(() => null);
|
|
76
|
+
if (status?.runtimeStatus?.state !== "error") {
|
|
77
|
+
console.log("Activity strip is already running.");
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
await requestBrokerShutdown().catch(() => null);
|
|
81
|
+
for (let attempt = 0; attempt < 20 && (await isBrokerAlive()); attempt += 1) {
|
|
82
|
+
await delay(50);
|
|
83
|
+
}
|
|
84
|
+
if (await isBrokerAlive()) {
|
|
85
|
+
console.error(formatBrokerRuntimeStatus(status));
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
116
88
|
}
|
|
117
89
|
|
|
118
90
|
const compatibility = await assessActivityStripCompatibility();
|
|
@@ -121,11 +93,11 @@ async function openStrip({ detached = true } = {}) {
|
|
|
121
93
|
return 1;
|
|
122
94
|
}
|
|
123
95
|
|
|
124
|
-
|
|
125
|
-
const child = spawn(
|
|
96
|
+
fs.mkdirSync(ACTIVITY_STRIP_SOCKET_DIR, { recursive: true, mode: 0o700 });
|
|
97
|
+
const child = spawn("flock", ["--nonblock", runtimeLockPath, process.execPath, nativeEntry], {
|
|
126
98
|
detached,
|
|
127
99
|
stdio: detached ? "ignore" : "inherit",
|
|
128
|
-
env: process.env,
|
|
100
|
+
env: { ...process.env, PI_ACTIVITY_STRIP_RUNTIME_LOCK_HELD: "1" },
|
|
129
101
|
});
|
|
130
102
|
|
|
131
103
|
if (detached) {
|
|
@@ -166,7 +138,9 @@ async function main() {
|
|
|
166
138
|
process.exitCode = 1;
|
|
167
139
|
return;
|
|
168
140
|
}
|
|
169
|
-
const result = await
|
|
141
|
+
const result = await sendBrokerMessage(makeMessage("focus-strip"), {
|
|
142
|
+
expectReply: true,
|
|
143
|
+
});
|
|
170
144
|
if (!result.ok) console.error(result.error || "Strip focus did nothing.");
|
|
171
145
|
process.exitCode = result.ok ? 0 : 1;
|
|
172
146
|
} catch {
|
|
@@ -234,15 +208,10 @@ async function main() {
|
|
|
234
208
|
}
|
|
235
209
|
return;
|
|
236
210
|
}
|
|
237
|
-
case "fix-top":
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
} catch (error) {
|
|
241
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
242
|
-
process.exitCode = 1;
|
|
243
|
-
}
|
|
211
|
+
case "fix-top":
|
|
212
|
+
console.log("Native layer-shell placement is compositor-owned; no repair was needed.");
|
|
213
|
+
process.exitCode = 0;
|
|
244
214
|
return;
|
|
245
|
-
}
|
|
246
215
|
case "stop": {
|
|
247
216
|
try {
|
|
248
217
|
const result = await requestBrokerShutdown();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "pi-activity-strip-native-artifact.v1",
|
|
3
|
+
"target": "x86_64-unknown-linux-gnu",
|
|
4
|
+
"sha256": "4cc72663dda90369a3a3ad60aabeefc4a609f42c6cc4a4661d2dc052570a3f0a",
|
|
5
|
+
"cargoLockSha256": "f8ebc11bc721c569af982169e448a5ea4fed15bc06001017d97acda22b1c890b",
|
|
6
|
+
"sourceSha256": "9467e315aa20fef76c25b88d29e69f3ef6960481b55b639c602e77805986f0e1",
|
|
7
|
+
"rustc": "rustc 1.98.0 (88d9e12ae 2026-08-18)",
|
|
8
|
+
"glibcFloor": "GLIBC_2.34",
|
|
9
|
+
"neededSonames": [
|
|
10
|
+
"libgtk4-layer-shell.so.0",
|
|
11
|
+
"libgtk-4.so.1",
|
|
12
|
+
"libcairo.so.2",
|
|
13
|
+
"libgio-2.0.so.0",
|
|
14
|
+
"libgobject-2.0.so.0",
|
|
15
|
+
"libglib-2.0.so.0",
|
|
16
|
+
"libgcc_s.so.1",
|
|
17
|
+
"libm.so.6",
|
|
18
|
+
"libc.so.6",
|
|
19
|
+
"ld-linux-x86-64.so.2"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
Binary file
|