@henryqw/pi-bark 0.1.2 → 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/README.md +39 -31
- package/dist/src/encryption.js +1 -2
- package/docs/push-encryption-flow.html +166 -0
- package/docs/push-encryption-flow.svg +148 -0
- package/extensions/bark.ts +9 -16
- package/package.json +2 -3
- package/src/encryption.ts +1 -2
- package/scripts/generate-encryption-key.ts +0 -52
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@henryqw/pi-bark`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Get [Bark](https://bark.day.app) notifications when Pi finishes or needs your input. Use `/copyb` to send Pi's latest response to your device.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -51,8 +51,8 @@ The `body` value is the same text that Pi's `/copy` command selects. Markdown, c
|
|
|
51
51
|
| --- | --- | --- |
|
|
52
52
|
| `/copyb` | command | Copy the last agent message and send a Bark push notification. |
|
|
53
53
|
| `/set-bark <device-key> [server-url]` | command | Save the Device Key and Bark server URL. |
|
|
54
|
-
| `/bark
|
|
55
|
-
| `/bark
|
|
54
|
+
| `/bark on\|off\|inherit` | command | Override automatic status notifications for the current CWD. |
|
|
55
|
+
| `/bark default on\|off` | command | Set the default for CWDs without an override. |
|
|
56
56
|
| `pi-bark-key [--force \| --disable]` | executable | Generate, replace, or disable the Custom Encryption Key. |
|
|
57
57
|
|
|
58
58
|
### Automatic status notifications
|
|
@@ -69,7 +69,7 @@ An unset session name appears as `Unnamed`. Status notifications do not include
|
|
|
69
69
|
Automatic notifications are enabled by default. Disable them only in the current CWD:
|
|
70
70
|
|
|
71
71
|
```text
|
|
72
|
-
/bark
|
|
72
|
+
/bark off
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
Use `on` to enable the current CWD explicitly. Use `inherit` to remove its override. The CWD then follows the global default.
|
|
@@ -77,22 +77,14 @@ Use `on` to enable the current CWD explicitly. Use `inherit` to remove its overr
|
|
|
77
77
|
Change that default for all CWDs without an override:
|
|
78
78
|
|
|
79
79
|
```text
|
|
80
|
-
/bark
|
|
80
|
+
/bark default off
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
These settings do not affect `/copyb`. CWD overrides live in pi-bark's global config, not in project files.
|
|
84
84
|
|
|
85
85
|
## Flow
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
flowchart LR
|
|
89
|
-
copy["/copyb"] --> clipboard["Clipboard"]
|
|
90
|
-
copy --> bark["pi-bark"]
|
|
91
|
-
status["Pi status event"] --> bark
|
|
92
|
-
bark --> request["Bark V2 /push request"]
|
|
93
|
-
request --> server["Bark server"]
|
|
94
|
-
server --> app["Bark App"]
|
|
95
|
-
```
|
|
87
|
+

|
|
96
88
|
|
|
97
89
|
## Config
|
|
98
90
|
|
|
@@ -107,39 +99,55 @@ Package-owned: `~/.pi/agent/config/pi-bark/config.json`
|
|
|
107
99
|
|
|
108
100
|
### Push encryption
|
|
109
101
|
|
|
110
|
-
Push encryption is optional. It prevents the Bark server and Apple Push Notification service from reading
|
|
102
|
+
Push encryption is optional. It prevents the Bark server and Apple Push Notification service from reading push content.
|
|
111
103
|
|
|
112
|
-
|
|
104
|
+
Once configured, pi-bark encrypts every push it sends. This includes `/copyb` and both automatic status notifications.
|
|
113
105
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
#### Set up encryption
|
|
107
|
+
|
|
108
|
+
1. Configure your Bark Device Key with `/set-bark`.
|
|
109
|
+
2. Generate and save a Custom Encryption Key:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npx --yes --package=@henryqw/pi-bark -- pi-bark-key
|
|
113
|
+
```
|
|
117
114
|
|
|
118
|
-
|
|
115
|
+
`npx` temporarily downloads the package and runs its `pi-bark-key` executable. The command saves the key to pi-bark's config and prints it once.
|
|
119
116
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
| Algorithm | `AES256` |
|
|
123
|
-
| Mode | `GCM` |
|
|
124
|
-
| Padding | `noPadding` |
|
|
125
|
-
| Key | The generated 32-character key |
|
|
117
|
+
3. Open **Bark App → Push Encryption**.
|
|
118
|
+
4. Enter these settings:
|
|
126
119
|
|
|
127
|
-
|
|
120
|
+
| Setting | Value |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| Algorithm | `AES256` |
|
|
123
|
+
| Mode | `GCM` |
|
|
124
|
+
| Padding | `noPadding` |
|
|
125
|
+
| Key | The generated 32-character key |
|
|
128
126
|
|
|
129
|
-
|
|
127
|
+
The generated ASCII key is 32 characters and 32 bytes.
|
|
128
|
+
|
|
129
|
+
5. If Bark requires an IV, enter any 12 ASCII characters, such as `000000000000`.
|
|
130
|
+
6. Save the Bark settings.
|
|
131
|
+
7. Run `/copyb` in Pi. Bark should show the exact last agent message instead of `Decryption Failed`.
|
|
132
|
+
|
|
133
|
+
The saved IV is only a placeholder for older Bark versions. pi-bark creates a fresh 12-byte IV for each push and sends it with the ciphertext. Bark uses that per-push IV instead of the saved value.
|
|
134
|
+
|
|
135
|
+
Do not paste the command output into shared logs. The Custom Encryption Key can decrypt your push content.
|
|
130
136
|
|
|
131
137
|
The GCM authentication tag is appended to the ciphertext before Base64 encoding, as Bark requires.
|
|
132
138
|
|
|
133
|
-
|
|
139
|
+
#### Replace or disable the key
|
|
140
|
+
|
|
141
|
+
The command refuses to overwrite an existing key. Replace it only when you also update the Bark App:
|
|
134
142
|
|
|
135
143
|
```bash
|
|
136
|
-
npx
|
|
144
|
+
npx --yes --package=@henryqw/pi-bark -- pi-bark-key --force
|
|
137
145
|
```
|
|
138
146
|
|
|
139
147
|
Disable push encryption in pi-bark with:
|
|
140
148
|
|
|
141
149
|
```bash
|
|
142
|
-
npx
|
|
150
|
+
npx --yes --package=@henryqw/pi-bark -- pi-bark-key --disable
|
|
143
151
|
```
|
|
144
152
|
|
|
145
153
|
Disable Push Encryption in the Bark App too. Otherwise its settings no longer match pi-bark.
|
package/dist/src/encryption.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { createCipheriv, randomBytes } from "node:crypto";
|
|
2
2
|
import { ENCRYPTION_KEY_BYTES } from "./config.js";
|
|
3
|
-
const RANDOM_TEXT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
4
3
|
const GCM_IV_BYTES = 12;
|
|
5
4
|
function randomText(length) {
|
|
6
|
-
return
|
|
5
|
+
return randomBytes(length).toString("base64url").slice(0, length);
|
|
7
6
|
}
|
|
8
7
|
export function generateEncryptionKey() {
|
|
9
8
|
return randomText(ENCRYPTION_KEY_BYTES);
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>How pi-bark encrypts every push</title>
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
8
|
+
<style>
|
|
9
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
10
|
+
html, body { margin: 0; min-height: 100%; }
|
|
11
|
+
body { display: grid; place-items: center; background: #f0eee9; }
|
|
12
|
+
figure { margin: 0; width: min(100%, 1280px); }
|
|
13
|
+
svg { display: block; width: 100%; height: auto; }
|
|
14
|
+
</style>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<figure>
|
|
18
|
+
<svg width="1280" height="720" viewBox="0 0 1280 720" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="push-encryption-flow-title push-encryption-flow-desc">
|
|
19
|
+
<title id="push-encryption-flow-title">How pi-bark encrypts every push</title>
|
|
20
|
+
<desc id="push-encryption-flow-desc">Architecture diagram showing copyb messages and automatic Pi status events entering one shared sender. When the config contains a custom key, every push uses AES256-GCM with a fresh 12-byte IV before Bark forwards the ciphertext to the app for local decryption.</desc>
|
|
21
|
+
<defs>
|
|
22
|
+
<style>
|
|
23
|
+
.eyebrow { fill: #4c5665; font: 500 8px 'Geist Mono', monospace; letter-spacing: .16em; }
|
|
24
|
+
.heading { fill: #101828; font: 400 28px 'Instrument Serif', serif; }
|
|
25
|
+
.intro { fill: #4c5665; font: 400 12px 'Geist', sans-serif; }
|
|
26
|
+
.zone { fill: #e6e4de; stroke: #101828; stroke-opacity: .14; stroke-width: 1; }
|
|
27
|
+
.zone-label { fill: #6a7282; font: 500 8px 'Geist Mono', monospace; letter-spacing: .16em; }
|
|
28
|
+
.boundary { fill: none; stroke: #1d4ed8; stroke-opacity: .50; stroke-width: 1; stroke-dasharray: 4 4; }
|
|
29
|
+
.boundary-label { fill: #1d4ed8; font: 500 8px 'Geist Mono', monospace; letter-spacing: .12em; }
|
|
30
|
+
.node-mask { fill: #f0eee9; }
|
|
31
|
+
.node { fill: #ffffff; stroke: #101828; stroke-width: 1; }
|
|
32
|
+
.node-input { fill: #4c5665; fill-opacity: .10; stroke: #6a7282; stroke-width: 1; }
|
|
33
|
+
.node-store { fill: #101828; fill-opacity: .05; stroke: #4c5665; stroke-width: 1; }
|
|
34
|
+
.node-external { fill: #101828; fill-opacity: .03; stroke: #101828; stroke-opacity: .30; stroke-width: 1; }
|
|
35
|
+
.node-focal { fill: #1d4ed8; fill-opacity: .08; stroke: #1d4ed8; stroke-width: 1.2; }
|
|
36
|
+
.tag { fill: none; stroke: #101828; stroke-opacity: .34; stroke-width: .8; }
|
|
37
|
+
.tag-focal { fill: none; stroke: #1d4ed8; stroke-opacity: .55; stroke-width: .8; }
|
|
38
|
+
.tag-text { fill: #6a7282; font: 500 8px 'Geist Mono', monospace; letter-spacing: .08em; text-anchor: middle; }
|
|
39
|
+
.tag-text-focal { fill: #1d4ed8; font: 500 8px 'Geist Mono', monospace; letter-spacing: .08em; text-anchor: middle; }
|
|
40
|
+
.name { fill: #101828; font: 600 12px 'Geist', sans-serif; text-anchor: middle; }
|
|
41
|
+
.sub { fill: #4c5665; font: 400 8px 'Geist Mono', monospace; text-anchor: middle; }
|
|
42
|
+
.sub-focal { fill: #1d4ed8; font: 500 8px 'Geist Mono', monospace; text-anchor: middle; }
|
|
43
|
+
.edge { fill: none; stroke: #4c5665; stroke-width: 1.2; marker-end: url(#push-encryption-arrow); }
|
|
44
|
+
.edge-secure { fill: none; stroke: #1d4ed8; stroke-width: 1.2; marker-end: url(#push-encryption-arrow-accent); }
|
|
45
|
+
.label-mask { fill: #f0eee9; }
|
|
46
|
+
.label { fill: #4c5665; font: 500 8px 'Geist Mono', monospace; letter-spacing: .04em; text-anchor: middle; }
|
|
47
|
+
.label-secure { fill: #1d4ed8; font: 500 8px 'Geist Mono', monospace; letter-spacing: .04em; text-anchor: middle; }
|
|
48
|
+
.legend-title { fill: #6a7282; font: 500 8px 'Geist Mono', monospace; letter-spacing: .16em; }
|
|
49
|
+
.legend-text { fill: #4c5665; font: 400 8px 'Geist Mono', monospace; }
|
|
50
|
+
</style>
|
|
51
|
+
<marker id="push-encryption-arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
|
52
|
+
<polygon points="0 0,8 3,0 6" fill="#4c5665"/>
|
|
53
|
+
</marker>
|
|
54
|
+
<marker id="push-encryption-arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
|
55
|
+
<polygon points="0 0,8 3,0 6" fill="#1d4ed8"/>
|
|
56
|
+
</marker>
|
|
57
|
+
<marker id="push-encryption-arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
|
58
|
+
<polygon points="0 0,8 3,0 6" fill="#1d4ed8"/>
|
|
59
|
+
</marker>
|
|
60
|
+
</defs>
|
|
61
|
+
|
|
62
|
+
<rect width="1280" height="720" fill="#f0eee9"/>
|
|
63
|
+
|
|
64
|
+
<text class="eyebrow" x="40" y="48">ARCHITECTURE · PI-BARK</text>
|
|
65
|
+
<text class="heading" x="40" y="84">One encryption path for every Bark push</text>
|
|
66
|
+
<text class="intro" x="40" y="112">A configured key applies to manual output and automatic status notifications.</text>
|
|
67
|
+
|
|
68
|
+
<!-- Local trust zone and encrypted transport boundary -->
|
|
69
|
+
<rect class="zone" x="40" y="152" width="816" height="416" rx="8"/>
|
|
70
|
+
<rect class="label-mask" x="52" y="156" width="80" height="16" rx="2"/>
|
|
71
|
+
<text class="zone-label" x="60" y="168">LOCAL PI</text>
|
|
72
|
+
|
|
73
|
+
<line class="boundary" x1="880" y1="176" x2="880" y2="544"/>
|
|
74
|
+
<rect class="label-mask" x="892" y="180" width="240" height="16" rx="2"/>
|
|
75
|
+
<text class="boundary-label" x="900" y="192">CIPHERTEXT LEAVES PI</text>
|
|
76
|
+
|
|
77
|
+
<!-- Connectors are drawn before nodes -->
|
|
78
|
+
<path class="edge" d="M252 248 H292 Q300 248 300 256 V308 Q300 316 308 316 H352"/>
|
|
79
|
+
<path class="edge" d="M252 400 H304 Q312 400 312 392 V372 Q312 364 320 364 H352"/>
|
|
80
|
+
<line class="edge" x1="452" y1="452" x2="452" y2="396"/>
|
|
81
|
+
<line class="edge-secure" x1="552" y1="340" x2="648" y2="340"/>
|
|
82
|
+
<line class="edge-secure" x1="848" y1="340" x2="920" y2="340"/>
|
|
83
|
+
<line class="edge-secure" x1="1060" y1="340" x2="1100" y2="340"/>
|
|
84
|
+
|
|
85
|
+
<!-- Connector labels -->
|
|
86
|
+
<rect class="label-mask" x="464" y="416" width="72" height="12" rx="2"/>
|
|
87
|
+
<text class="label" x="500" y="424">LOAD EACH PUSH</text>
|
|
88
|
+
|
|
89
|
+
<rect class="label-mask" x="568" y="320" width="64" height="12" rx="2"/>
|
|
90
|
+
<text class="label-secure" x="600" y="328">KEY PRESENT</text>
|
|
91
|
+
|
|
92
|
+
<rect class="label-mask" x="856" y="320" width="56" height="12" rx="2"/>
|
|
93
|
+
<text class="label-secure" x="884" y="328">POST /PUSH</text>
|
|
94
|
+
|
|
95
|
+
<!-- Opaque node masks -->
|
|
96
|
+
<rect class="node-mask" x="72" y="208" width="180" height="80" rx="6"/>
|
|
97
|
+
<rect class="node-mask" x="72" y="360" width="180" height="80" rx="6"/>
|
|
98
|
+
<rect class="node-mask" x="352" y="284" width="200" height="112" rx="6"/>
|
|
99
|
+
<rect class="node-mask" x="352" y="452" width="200" height="80" rx="6"/>
|
|
100
|
+
<rect class="node-mask" x="648" y="284" width="200" height="112" rx="6"/>
|
|
101
|
+
<rect class="node-mask" x="920" y="284" width="140" height="112" rx="6"/>
|
|
102
|
+
<rect class="node-mask" x="1100" y="284" width="140" height="112" rx="6"/>
|
|
103
|
+
|
|
104
|
+
<!-- Inputs -->
|
|
105
|
+
<rect class="node-input" x="72" y="208" width="180" height="80" rx="6"/>
|
|
106
|
+
<rect class="tag" x="84" y="220" width="56" height="16" rx="2"/>
|
|
107
|
+
<text class="tag-text" x="112" y="232">COMMAND</text>
|
|
108
|
+
<text class="name" x="162" y="256">/copyb</text>
|
|
109
|
+
<text class="sub" x="162" y="276">LAST AGENT MESSAGE</text>
|
|
110
|
+
|
|
111
|
+
<rect class="node-input" x="72" y="360" width="180" height="80" rx="6"/>
|
|
112
|
+
<rect class="tag" x="84" y="372" width="48" height="16" rx="2"/>
|
|
113
|
+
<text class="tag-text" x="108" y="384">EVENT</text>
|
|
114
|
+
<text class="name" x="162" y="408">Pi status</text>
|
|
115
|
+
<text class="sub" x="162" y="428">NEEDS INPUT · FINISHED</text>
|
|
116
|
+
|
|
117
|
+
<!-- Shared sender and config -->
|
|
118
|
+
<rect class="node" x="352" y="284" width="200" height="112" rx="6"/>
|
|
119
|
+
<rect class="tag" x="364" y="296" width="48" height="16" rx="2"/>
|
|
120
|
+
<text class="tag-text" x="388" y="308">SHARED</text>
|
|
121
|
+
<text class="name" x="452" y="340">Push sender</text>
|
|
122
|
+
<text class="sub" x="452" y="364">ONE PATH FOR EVERY PUSH</text>
|
|
123
|
+
<text class="sub" x="452" y="380">DEVICE KEY + CONTENT</text>
|
|
124
|
+
|
|
125
|
+
<rect class="node-store" x="352" y="452" width="200" height="80" rx="6"/>
|
|
126
|
+
<rect class="tag" x="364" y="464" width="48" height="16" rx="2"/>
|
|
127
|
+
<text class="tag-text" x="388" y="476">CONFIG</text>
|
|
128
|
+
<text class="name" x="452" y="500">pi-bark config</text>
|
|
129
|
+
<text class="sub" x="452" y="520">DEVICE KEY · CUSTOM KEY</text>
|
|
130
|
+
|
|
131
|
+
<!-- Encryption focal point -->
|
|
132
|
+
<rect class="node-focal" x="648" y="284" width="200" height="112" rx="6"/>
|
|
133
|
+
<rect class="tag-focal" x="660" y="296" width="56" height="16" rx="2"/>
|
|
134
|
+
<text class="tag-text-focal" x="688" y="308">ENCRYPT</text>
|
|
135
|
+
<text class="name" x="748" y="340">AES256-GCM</text>
|
|
136
|
+
<text class="sub-focal" x="748" y="364">FRESH 12-BYTE IV</text>
|
|
137
|
+
<text class="sub-focal" x="748" y="380">AUTH TAG APPENDED</text>
|
|
138
|
+
|
|
139
|
+
<!-- External delivery -->
|
|
140
|
+
<rect class="node-external" x="920" y="284" width="140" height="112" rx="6"/>
|
|
141
|
+
<rect class="tag" x="932" y="296" width="48" height="16" rx="2"/>
|
|
142
|
+
<text class="tag-text" x="956" y="308">SERVER</text>
|
|
143
|
+
<text class="name" x="990" y="340">Bark server</text>
|
|
144
|
+
<text class="sub" x="990" y="364">FORWARDS ONLY</text>
|
|
145
|
+
<text class="sub" x="990" y="380">CANNOT READ CONTENT</text>
|
|
146
|
+
|
|
147
|
+
<rect class="node" x="1100" y="284" width="140" height="112" rx="6"/>
|
|
148
|
+
<rect class="tag" x="1112" y="296" width="32" height="16" rx="2"/>
|
|
149
|
+
<text class="tag-text" x="1128" y="308">APP</text>
|
|
150
|
+
<text class="name" x="1170" y="340">Bark App</text>
|
|
151
|
+
<text class="sub" x="1170" y="364">MATCHING 32-BYTE KEY</text>
|
|
152
|
+
<text class="sub" x="1170" y="380">DECRYPTS LOCALLY</text>
|
|
153
|
+
|
|
154
|
+
<!-- Legend -->
|
|
155
|
+
<line x1="40" y1="616" x2="1240" y2="616" stroke="#101828" stroke-opacity=".14" stroke-width=".8"/>
|
|
156
|
+
<text class="legend-title" x="40" y="652">LEGEND</text>
|
|
157
|
+
<line x1="144" y1="648" x2="184" y2="648" stroke="#4c5665" stroke-width="1.2" marker-end="url(#push-encryption-arrow)"/>
|
|
158
|
+
<text class="legend-text" x="196" y="652">LOCAL CONTENT</text>
|
|
159
|
+
<line x1="344" y1="648" x2="384" y2="648" stroke="#1d4ed8" stroke-width="1.2" marker-end="url(#push-encryption-arrow-accent)"/>
|
|
160
|
+
<text class="legend-text" x="396" y="652">ENCRYPTED CONTENT</text>
|
|
161
|
+
<rect x="568" y="640" width="16" height="16" rx="4" fill="#1d4ed8" fill-opacity=".08" stroke="#1d4ed8" stroke-width="1"/>
|
|
162
|
+
<text class="legend-text" x="596" y="652">ENCRYPTION BOUNDARY</text>
|
|
163
|
+
</svg>
|
|
164
|
+
</figure>
|
|
165
|
+
</body>
|
|
166
|
+
</html>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg width="1280" height="720" viewBox="0 0 1280 720" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="push-encryption-flow-title push-encryption-flow-desc">
|
|
3
|
+
<title id="push-encryption-flow-title">How pi-bark encrypts every push</title>
|
|
4
|
+
<desc id="push-encryption-flow-desc">Architecture diagram showing copyb messages and automatic Pi status events entering one shared sender. When the config contains a custom key, every push uses AES256-GCM with a fresh 12-byte IV before Bark forwards the ciphertext to the app for local decryption.</desc>
|
|
5
|
+
<defs>
|
|
6
|
+
<style>@import url('https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap');</style>
|
|
7
|
+
<style>
|
|
8
|
+
.eyebrow { fill: #4c5665; font: 500 8px 'Geist Mono', monospace; letter-spacing: .16em; }
|
|
9
|
+
.heading { fill: #101828; font: 400 28px 'Instrument Serif', serif; }
|
|
10
|
+
.intro { fill: #4c5665; font: 400 12px 'Geist', sans-serif; }
|
|
11
|
+
.zone { fill: #e6e4de; stroke: #101828; stroke-opacity: .14; stroke-width: 1; }
|
|
12
|
+
.zone-label { fill: #6a7282; font: 500 8px 'Geist Mono', monospace; letter-spacing: .16em; }
|
|
13
|
+
.boundary { fill: none; stroke: #1d4ed8; stroke-opacity: .50; stroke-width: 1; stroke-dasharray: 4 4; }
|
|
14
|
+
.boundary-label { fill: #1d4ed8; font: 500 8px 'Geist Mono', monospace; letter-spacing: .12em; }
|
|
15
|
+
.node-mask { fill: #f0eee9; }
|
|
16
|
+
.node { fill: #ffffff; stroke: #101828; stroke-width: 1; }
|
|
17
|
+
.node-input { fill: #4c5665; fill-opacity: .10; stroke: #6a7282; stroke-width: 1; }
|
|
18
|
+
.node-store { fill: #101828; fill-opacity: .05; stroke: #4c5665; stroke-width: 1; }
|
|
19
|
+
.node-external { fill: #101828; fill-opacity: .03; stroke: #101828; stroke-opacity: .30; stroke-width: 1; }
|
|
20
|
+
.node-focal { fill: #1d4ed8; fill-opacity: .08; stroke: #1d4ed8; stroke-width: 1.2; }
|
|
21
|
+
.tag { fill: none; stroke: #101828; stroke-opacity: .34; stroke-width: .8; }
|
|
22
|
+
.tag-focal { fill: none; stroke: #1d4ed8; stroke-opacity: .55; stroke-width: .8; }
|
|
23
|
+
.tag-text { fill: #6a7282; font: 500 8px 'Geist Mono', monospace; letter-spacing: .08em; text-anchor: middle; }
|
|
24
|
+
.tag-text-focal { fill: #1d4ed8; font: 500 8px 'Geist Mono', monospace; letter-spacing: .08em; text-anchor: middle; }
|
|
25
|
+
.name { fill: #101828; font: 600 12px 'Geist', sans-serif; text-anchor: middle; }
|
|
26
|
+
.sub { fill: #4c5665; font: 400 8px 'Geist Mono', monospace; text-anchor: middle; }
|
|
27
|
+
.sub-focal { fill: #1d4ed8; font: 500 8px 'Geist Mono', monospace; text-anchor: middle; }
|
|
28
|
+
.edge { fill: none; stroke: #4c5665; stroke-width: 1.2; marker-end: url(#push-encryption-arrow); }
|
|
29
|
+
.edge-secure { fill: none; stroke: #1d4ed8; stroke-width: 1.2; marker-end: url(#push-encryption-arrow-accent); }
|
|
30
|
+
.label-mask { fill: #f0eee9; }
|
|
31
|
+
.label { fill: #4c5665; font: 500 8px 'Geist Mono', monospace; letter-spacing: .04em; text-anchor: middle; }
|
|
32
|
+
.label-secure { fill: #1d4ed8; font: 500 8px 'Geist Mono', monospace; letter-spacing: .04em; text-anchor: middle; }
|
|
33
|
+
.legend-title { fill: #6a7282; font: 500 8px 'Geist Mono', monospace; letter-spacing: .16em; }
|
|
34
|
+
.legend-text { fill: #4c5665; font: 400 8px 'Geist Mono', monospace; }
|
|
35
|
+
</style>
|
|
36
|
+
<marker id="push-encryption-arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
|
37
|
+
<polygon points="0 0,8 3,0 6" fill="#4c5665"/>
|
|
38
|
+
</marker>
|
|
39
|
+
<marker id="push-encryption-arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
|
40
|
+
<polygon points="0 0,8 3,0 6" fill="#1d4ed8"/>
|
|
41
|
+
</marker>
|
|
42
|
+
<marker id="push-encryption-arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
|
43
|
+
<polygon points="0 0,8 3,0 6" fill="#1d4ed8"/>
|
|
44
|
+
</marker>
|
|
45
|
+
</defs>
|
|
46
|
+
|
|
47
|
+
<rect width="1280" height="720" fill="#f0eee9"/>
|
|
48
|
+
|
|
49
|
+
<text class="eyebrow" x="40" y="48">ARCHITECTURE · PI-BARK</text>
|
|
50
|
+
<text class="heading" x="40" y="84">One encryption path for every Bark push</text>
|
|
51
|
+
<text class="intro" x="40" y="112">A configured key applies to manual output and automatic status notifications.</text>
|
|
52
|
+
|
|
53
|
+
<!-- Local trust zone and encrypted transport boundary -->
|
|
54
|
+
<rect class="zone" x="40" y="152" width="816" height="416" rx="8"/>
|
|
55
|
+
<rect class="label-mask" x="52" y="156" width="80" height="16" rx="2"/>
|
|
56
|
+
<text class="zone-label" x="60" y="168">LOCAL PI</text>
|
|
57
|
+
|
|
58
|
+
<line class="boundary" x1="880" y1="176" x2="880" y2="544"/>
|
|
59
|
+
<rect class="label-mask" x="892" y="180" width="240" height="16" rx="2"/>
|
|
60
|
+
<text class="boundary-label" x="900" y="192">CIPHERTEXT LEAVES PI</text>
|
|
61
|
+
|
|
62
|
+
<!-- Connectors are drawn before nodes -->
|
|
63
|
+
<path class="edge" d="M252 248 H292 Q300 248 300 256 V308 Q300 316 308 316 H352"/>
|
|
64
|
+
<path class="edge" d="M252 400 H304 Q312 400 312 392 V372 Q312 364 320 364 H352"/>
|
|
65
|
+
<line class="edge" x1="452" y1="452" x2="452" y2="396"/>
|
|
66
|
+
<line class="edge-secure" x1="552" y1="340" x2="648" y2="340"/>
|
|
67
|
+
<line class="edge-secure" x1="848" y1="340" x2="920" y2="340"/>
|
|
68
|
+
<line class="edge-secure" x1="1060" y1="340" x2="1100" y2="340"/>
|
|
69
|
+
|
|
70
|
+
<!-- Connector labels -->
|
|
71
|
+
<rect class="label-mask" x="464" y="416" width="72" height="12" rx="2"/>
|
|
72
|
+
<text class="label" x="500" y="424">LOAD EACH PUSH</text>
|
|
73
|
+
|
|
74
|
+
<rect class="label-mask" x="568" y="320" width="64" height="12" rx="2"/>
|
|
75
|
+
<text class="label-secure" x="600" y="328">KEY PRESENT</text>
|
|
76
|
+
|
|
77
|
+
<rect class="label-mask" x="856" y="320" width="56" height="12" rx="2"/>
|
|
78
|
+
<text class="label-secure" x="884" y="328">POST /PUSH</text>
|
|
79
|
+
|
|
80
|
+
<!-- Opaque node masks -->
|
|
81
|
+
<rect class="node-mask" x="72" y="208" width="180" height="80" rx="6"/>
|
|
82
|
+
<rect class="node-mask" x="72" y="360" width="180" height="80" rx="6"/>
|
|
83
|
+
<rect class="node-mask" x="352" y="284" width="200" height="112" rx="6"/>
|
|
84
|
+
<rect class="node-mask" x="352" y="452" width="200" height="80" rx="6"/>
|
|
85
|
+
<rect class="node-mask" x="648" y="284" width="200" height="112" rx="6"/>
|
|
86
|
+
<rect class="node-mask" x="920" y="284" width="140" height="112" rx="6"/>
|
|
87
|
+
<rect class="node-mask" x="1100" y="284" width="140" height="112" rx="6"/>
|
|
88
|
+
|
|
89
|
+
<!-- Inputs -->
|
|
90
|
+
<rect class="node-input" x="72" y="208" width="180" height="80" rx="6"/>
|
|
91
|
+
<rect class="tag" x="84" y="220" width="56" height="16" rx="2"/>
|
|
92
|
+
<text class="tag-text" x="112" y="232">COMMAND</text>
|
|
93
|
+
<text class="name" x="162" y="256">/copyb</text>
|
|
94
|
+
<text class="sub" x="162" y="276">LAST AGENT MESSAGE</text>
|
|
95
|
+
|
|
96
|
+
<rect class="node-input" x="72" y="360" width="180" height="80" rx="6"/>
|
|
97
|
+
<rect class="tag" x="84" y="372" width="48" height="16" rx="2"/>
|
|
98
|
+
<text class="tag-text" x="108" y="384">EVENT</text>
|
|
99
|
+
<text class="name" x="162" y="408">Pi status</text>
|
|
100
|
+
<text class="sub" x="162" y="428">NEEDS INPUT · FINISHED</text>
|
|
101
|
+
|
|
102
|
+
<!-- Shared sender and config -->
|
|
103
|
+
<rect class="node" x="352" y="284" width="200" height="112" rx="6"/>
|
|
104
|
+
<rect class="tag" x="364" y="296" width="48" height="16" rx="2"/>
|
|
105
|
+
<text class="tag-text" x="388" y="308">SHARED</text>
|
|
106
|
+
<text class="name" x="452" y="340">Push sender</text>
|
|
107
|
+
<text class="sub" x="452" y="364">ONE PATH FOR EVERY PUSH</text>
|
|
108
|
+
<text class="sub" x="452" y="380">DEVICE KEY + CONTENT</text>
|
|
109
|
+
|
|
110
|
+
<rect class="node-store" x="352" y="452" width="200" height="80" rx="6"/>
|
|
111
|
+
<rect class="tag" x="364" y="464" width="48" height="16" rx="2"/>
|
|
112
|
+
<text class="tag-text" x="388" y="476">CONFIG</text>
|
|
113
|
+
<text class="name" x="452" y="500">pi-bark config</text>
|
|
114
|
+
<text class="sub" x="452" y="520">DEVICE KEY · CUSTOM KEY</text>
|
|
115
|
+
|
|
116
|
+
<!-- Encryption focal point -->
|
|
117
|
+
<rect class="node-focal" x="648" y="284" width="200" height="112" rx="6"/>
|
|
118
|
+
<rect class="tag-focal" x="660" y="296" width="56" height="16" rx="2"/>
|
|
119
|
+
<text class="tag-text-focal" x="688" y="308">ENCRYPT</text>
|
|
120
|
+
<text class="name" x="748" y="340">AES256-GCM</text>
|
|
121
|
+
<text class="sub-focal" x="748" y="364">FRESH 12-BYTE IV</text>
|
|
122
|
+
<text class="sub-focal" x="748" y="380">AUTH TAG APPENDED</text>
|
|
123
|
+
|
|
124
|
+
<!-- External delivery -->
|
|
125
|
+
<rect class="node-external" x="920" y="284" width="140" height="112" rx="6"/>
|
|
126
|
+
<rect class="tag" x="932" y="296" width="48" height="16" rx="2"/>
|
|
127
|
+
<text class="tag-text" x="956" y="308">SERVER</text>
|
|
128
|
+
<text class="name" x="990" y="340">Bark server</text>
|
|
129
|
+
<text class="sub" x="990" y="364">FORWARDS ONLY</text>
|
|
130
|
+
<text class="sub" x="990" y="380">CANNOT READ CONTENT</text>
|
|
131
|
+
|
|
132
|
+
<rect class="node" x="1100" y="284" width="140" height="112" rx="6"/>
|
|
133
|
+
<rect class="tag" x="1112" y="296" width="32" height="16" rx="2"/>
|
|
134
|
+
<text class="tag-text" x="1128" y="308">APP</text>
|
|
135
|
+
<text class="name" x="1170" y="340">Bark App</text>
|
|
136
|
+
<text class="sub" x="1170" y="364">MATCHING 32-BYTE KEY</text>
|
|
137
|
+
<text class="sub" x="1170" y="380">DECRYPTS LOCALLY</text>
|
|
138
|
+
|
|
139
|
+
<!-- Legend -->
|
|
140
|
+
<line x1="40" y1="616" x2="1240" y2="616" stroke="#101828" stroke-opacity=".14" stroke-width=".8"/>
|
|
141
|
+
<text class="legend-title" x="40" y="652">LEGEND</text>
|
|
142
|
+
<line x1="144" y1="648" x2="184" y2="648" stroke="#4c5665" stroke-width="1.2" marker-end="url(#push-encryption-arrow)"/>
|
|
143
|
+
<text class="legend-text" x="196" y="652">LOCAL CONTENT</text>
|
|
144
|
+
<line x1="344" y1="648" x2="384" y2="648" stroke="#1d4ed8" stroke-width="1.2" marker-end="url(#push-encryption-arrow-accent)"/>
|
|
145
|
+
<text class="legend-text" x="396" y="652">ENCRYPTED CONTENT</text>
|
|
146
|
+
<rect x="568" y="640" width="16" height="16" rx="4" fill="#1d4ed8" fill-opacity=".08" stroke="#1d4ed8" stroke-width="1"/>
|
|
147
|
+
<text class="legend-text" x="596" y="652">ENCRYPTION BOUNDARY</text>
|
|
148
|
+
</svg>
|
package/extensions/bark.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { copyToClipboard, type ExtensionAPI, type SessionEntry } from "@earendil
|
|
|
3
3
|
import {
|
|
4
4
|
createBarkConfigStore,
|
|
5
5
|
DEFAULT_SERVER_URL,
|
|
6
|
-
parseDeviceKey,
|
|
7
6
|
parseServerUrl,
|
|
8
7
|
statusNotificationsEnabled,
|
|
9
8
|
type BarkConfig,
|
|
@@ -68,28 +67,22 @@ export default function barkExtension(pi: ExtensionAPI, options: BarkExtensionOp
|
|
|
68
67
|
|
|
69
68
|
let statusPushQueue = Promise.resolve();
|
|
70
69
|
let pendingFinishedPush: AbortController | undefined;
|
|
71
|
-
const queueStatus = (
|
|
70
|
+
const queueStatus = async (
|
|
72
71
|
title: string,
|
|
73
72
|
cwd: string,
|
|
74
73
|
shouldSend?: Promise<boolean>,
|
|
75
74
|
cancelSignal?: AbortSignal,
|
|
76
75
|
): Promise<void> => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
configSnapshot = configStore.loadSync().value;
|
|
80
|
-
} catch (error) {
|
|
81
|
-
return Promise.reject(error);
|
|
82
|
-
}
|
|
83
|
-
if (!configSnapshot.deviceKey || !statusNotificationsEnabled(configSnapshot, cwd)) return Promise.resolve();
|
|
76
|
+
const configSnapshot = configStore.loadSync().value;
|
|
77
|
+
if (!configSnapshot.deviceKey || !statusNotificationsEnabled(configSnapshot, cwd)) return;
|
|
84
78
|
const sessionName = pi.getSessionName()?.trim() || "Unnamed";
|
|
85
79
|
const contentSnapshot = { title, body: `Pi session: ${sessionName}` };
|
|
86
80
|
const send = async () => {
|
|
87
81
|
if (shouldSend && !(await shouldSend)) return;
|
|
88
82
|
await sendPush(configSnapshot, contentSnapshot, fetchImpl, cancelSignal);
|
|
89
83
|
};
|
|
90
|
-
|
|
91
|
-
statusPushQueue
|
|
92
|
-
return push;
|
|
84
|
+
statusPushQueue = statusPushQueue.then(send, send);
|
|
85
|
+
return statusPushQueue;
|
|
93
86
|
};
|
|
94
87
|
|
|
95
88
|
pi.on("ui_prompt_start", (_event, ctx) => {
|
|
@@ -120,12 +113,12 @@ export default function barkExtension(pi: ExtensionAPI, options: BarkExtensionOp
|
|
|
120
113
|
});
|
|
121
114
|
});
|
|
122
115
|
|
|
123
|
-
pi.registerCommand("bark
|
|
116
|
+
pi.registerCommand("bark", {
|
|
124
117
|
description: "Configure automatic Bark status notifications",
|
|
125
118
|
handler: async (args, ctx) => {
|
|
126
119
|
const [first, second, extra] = args.trim().split(/\s+/);
|
|
127
120
|
if (extra || (first === "default" ? !["on", "off"].includes(second) : second)) {
|
|
128
|
-
throw new Error("Usage: /bark
|
|
121
|
+
throw new Error("Usage: /bark <on|off|inherit> | default <on|off>");
|
|
129
122
|
}
|
|
130
123
|
if (first === "default") {
|
|
131
124
|
const enabled = second === "on";
|
|
@@ -137,7 +130,7 @@ export default function barkExtension(pi: ExtensionAPI, options: BarkExtensionOp
|
|
|
137
130
|
return;
|
|
138
131
|
}
|
|
139
132
|
if (!(["on", "off", "inherit"] as const).includes(first as "on" | "off" | "inherit")) {
|
|
140
|
-
throw new Error("Usage: /bark
|
|
133
|
+
throw new Error("Usage: /bark <on|off|inherit> | default <on|off>");
|
|
141
134
|
}
|
|
142
135
|
const cwd = resolve(ctx.cwd);
|
|
143
136
|
await configStore.update((config) => {
|
|
@@ -182,7 +175,7 @@ export default function barkExtension(pi: ExtensionAPI, options: BarkExtensionOp
|
|
|
182
175
|
await configStore.update((config) => ({
|
|
183
176
|
...config,
|
|
184
177
|
serverUrl: parseServerUrl(serverUrl),
|
|
185
|
-
deviceKey
|
|
178
|
+
deviceKey,
|
|
186
179
|
}));
|
|
187
180
|
ctx.ui.notify("Saved the Bark Device Key and server URL.", "info");
|
|
188
181
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-bark",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Send Pi session status and agent output to Bark on iOS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"LICENSE",
|
|
19
19
|
"dist",
|
|
20
|
+
"docs",
|
|
20
21
|
"extensions",
|
|
21
|
-
"scripts",
|
|
22
22
|
"src",
|
|
23
23
|
"README.md"
|
|
24
24
|
],
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsc --project tsconfig.build.json",
|
|
30
|
-
"generate:key": "npm run build && node dist/scripts/generate-encryption-key.js",
|
|
31
30
|
"test": "npm run build && node --test test/*.test.ts",
|
|
32
31
|
"typecheck": "tsc --noEmit --allowImportingTsExtensions --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck extensions/bark.ts scripts/*.ts src/*.ts test/*.test.ts",
|
|
33
32
|
"prepack": "npm run build",
|
package/src/encryption.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { createCipheriv, randomBytes } from "node:crypto";
|
|
2
2
|
import { ENCRYPTION_KEY_BYTES, type BarkEncryption } from "./config.ts";
|
|
3
3
|
|
|
4
|
-
const RANDOM_TEXT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
5
4
|
const GCM_IV_BYTES = 12;
|
|
6
5
|
|
|
7
6
|
function randomText(length: number): string {
|
|
8
|
-
return
|
|
7
|
+
return randomBytes(length).toString("base64url").slice(0, length);
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
export function generateEncryptionKey(): string {
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
createBarkConfigStore,
|
|
5
|
-
ENCRYPTION_ALGORITHM,
|
|
6
|
-
ENCRYPTION_MODE,
|
|
7
|
-
ENCRYPTION_PADDING,
|
|
8
|
-
} from "../src/config.ts";
|
|
9
|
-
import { generateEncryptionKey } from "../src/encryption.ts";
|
|
10
|
-
|
|
11
|
-
const args = process.argv.slice(2);
|
|
12
|
-
const force = args.length === 1 && args[0] === "--force";
|
|
13
|
-
const disable = args.length === 1 && args[0] === "--disable";
|
|
14
|
-
|
|
15
|
-
if (args.length > 1 || (args.length === 1 && !force && !disable)) {
|
|
16
|
-
console.error("Usage: pi-bark-key [--force | --disable]");
|
|
17
|
-
process.exitCode = 1;
|
|
18
|
-
} else {
|
|
19
|
-
const store = createBarkConfigStore();
|
|
20
|
-
try {
|
|
21
|
-
if (disable) {
|
|
22
|
-
await store.update((config) => ({ ...config, encryption: null }));
|
|
23
|
-
console.log("Disabled Bark push encryption.");
|
|
24
|
-
} else {
|
|
25
|
-
let key = "";
|
|
26
|
-
await store.update((config) => {
|
|
27
|
-
if (config.encryption && !force) {
|
|
28
|
-
throw new Error("A Bark Custom Encryption Key already exists. Use --force to replace it.");
|
|
29
|
-
}
|
|
30
|
-
key = generateEncryptionKey();
|
|
31
|
-
return {
|
|
32
|
-
...config,
|
|
33
|
-
encryption: {
|
|
34
|
-
algorithm: ENCRYPTION_ALGORITHM,
|
|
35
|
-
mode: ENCRYPTION_MODE,
|
|
36
|
-
padding: ENCRYPTION_PADDING,
|
|
37
|
-
key,
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
});
|
|
41
|
-
console.log("Generated and saved a Bark Custom Encryption Key.");
|
|
42
|
-
console.log(`Algorithm: ${ENCRYPTION_ALGORITHM}`);
|
|
43
|
-
console.log(`Mode: ${ENCRYPTION_MODE}`);
|
|
44
|
-
console.log(`Padding: ${ENCRYPTION_PADDING}`);
|
|
45
|
-
console.log(`Key: ${key}`);
|
|
46
|
-
console.log("Enter these settings in Bark App > Push Encryption.");
|
|
47
|
-
}
|
|
48
|
-
} catch (error) {
|
|
49
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
50
|
-
process.exitCode = 1;
|
|
51
|
-
}
|
|
52
|
-
}
|