claude-b 0.3.2 → 0.3.3

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/RELEASING.md ADDED
@@ -0,0 +1,167 @@
1
+ # Releasing Claude-B
2
+
3
+ A release publishes `claude-b` to three registries simultaneously from a
4
+ single git tag. The workflow is idempotent — re-running it on the same
5
+ tag is safe.
6
+
7
+ ## What gets published where
8
+
9
+ | Target | URL | Consumer install |
10
+ |---|---|---|
11
+ | npm (public) | https://www.npmjs.com/package/claude-b | `npm i -g claude-b` |
12
+ | Docker Hub (public) | https://hub.docker.com/r/danimoya/claude-b | `docker pull danimoya/claude-b` |
13
+ | GHCR | https://github.com/danimoya/Claude-B/pkgs/container/claude-b | `docker pull ghcr.io/danimoya/claude-b` |
14
+ | GitHub Release | https://github.com/danimoya/Claude-B/releases | tag browser |
15
+
16
+ Docker images are multi-arch (`linux/amd64`, `linux/arm64`).
17
+
18
+ ## One-time setup
19
+
20
+ ### GitHub secrets
21
+
22
+ Set on the repo (`Settings → Secrets and variables → Actions`):
23
+
24
+ | Secret | Source |
25
+ |---|---|
26
+ | `NPM_TOKEN` | npmjs.com → **Access Tokens** → *Granular* with publish scope on `claude-b` |
27
+ | `DOCKERHUB_USERNAME` | `danimoya` |
28
+ | `DOCKERHUB_TOKEN` | hub.docker.com → **Account Settings → Security → New Access Token** (R/W/D) — cached in `~/.dockerhub-credentials` |
29
+
30
+ `GITHUB_TOKEN` is automatic; nothing to set for GHCR.
31
+
32
+ CLI shortcut:
33
+
34
+ ```bash
35
+ gh secret set NPM_TOKEN -b "npm_..."
36
+ source ~/.dockerhub-credentials
37
+ gh secret set DOCKERHUB_USERNAME -b "$DOCKERHUB_USERNAME"
38
+ gh secret set DOCKERHUB_TOKEN -b "$DOCKERHUB_TOKEN"
39
+ ```
40
+
41
+ ### DockerHub — first push is private
42
+
43
+ DockerHub creates any newly-pushed repo as *private* by default. On the
44
+ free plan the second private repo hits `plan_exceeded` and anonymous
45
+ pulls 404. Flip it to public *once*:
46
+
47
+ ```bash
48
+ source ~/.dockerhub-credentials
49
+ JWT=$(curl -sS -X POST \
50
+ -H 'Content-Type: application/json' \
51
+ -d "{\"username\":\"$DOCKERHUB_USERNAME\",\"password\":\"$DOCKERHUB_TOKEN\"}" \
52
+ https://hub.docker.com/v2/users/login/ | jq -r .token)
53
+
54
+ curl -sS -X POST \
55
+ -H "Authorization: JWT $JWT" \
56
+ -H 'Content-Type: application/json' \
57
+ -d '{"is_private":false}' \
58
+ https://hub.docker.com/v2/repositories/danimoya/claude-b/privacy/
59
+ ```
60
+
61
+ Note the `/privacy/` sub-path — `PATCH` to the root endpoint returns
62
+ 200 but silently ignores `is_private`.
63
+
64
+ ## Releasing a new version
65
+
66
+ ```bash
67
+ # 1. Make sure main is clean and builds
68
+ pnpm build && pnpm typecheck
69
+
70
+ # 2. Bump version (semver)
71
+ npm version patch # or minor / major — updates package.json + creates a tag
72
+
73
+ # 3. Push commit + tag
74
+ git push origin main --follow-tags
75
+ ```
76
+
77
+ `npm version` creates the commit *and* the tag (`vX.Y.Z`). The
78
+ `--follow-tags` push triggers `.github/workflows/release.yml`.
79
+
80
+ Watch it:
81
+
82
+ ```bash
83
+ gh run watch --exit-status --workflow=release.yml
84
+ ```
85
+
86
+ If only one job fails (usually npm, when a token rolls):
87
+
88
+ ```bash
89
+ gh run rerun <run-id> --failed
90
+ ```
91
+
92
+ ## Verifying a release
93
+
94
+ ```bash
95
+ # npm
96
+ npm view claude-b dist-tags
97
+
98
+ # Docker Hub (multi-arch check)
99
+ docker manifest inspect danimoya/claude-b:latest | jq '.manifests[] | .platform'
100
+
101
+ # GHCR
102
+ docker pull ghcr.io/danimoya/claude-b:latest
103
+
104
+ # End-to-end: the landing script
105
+ curl -fsSL https://cb.danimoya.com | head
106
+ ```
107
+
108
+ ## Keeping the Docker Hub README in sync
109
+
110
+ The Hub page's README is *not* pulled from this repo automatically.
111
+ Edit `website/DOCKERHUB.md` and push:
112
+
113
+ ```bash
114
+ ./website/sync-dockerhub-readme.sh
115
+ ```
116
+
117
+ The helper PATCHes Hub's `description` and `full_description`. Creds
118
+ come from `~/.dockerhub-credentials` unless overridden via env.
119
+
120
+ ## Workflow file reference
121
+
122
+ `.github/workflows/release.yml` — three jobs, published independently:
123
+
124
+ - **`npm`** — `npm publish --access public`. Falls back to
125
+ Trusted Publishing (OIDC, `--provenance`) when `NPM_TOKEN` is absent.
126
+ - **`docker`** — `docker/build-push-action@v6` with QEMU for arm64.
127
+ Tags: `X.Y.Z`, `X.Y`, `X`, `vX.Y.Z`, `latest`. Pushes to GHCR
128
+ always; to Docker Hub only when `DOCKERHUB_USERNAME` is set.
129
+ - **`release`** — `softprops/action-gh-release@v2`. Depends on `docker`
130
+ only, so a broken npm token never blocks the GitHub Release.
131
+
132
+ A manual dispatch (`workflow_dispatch`) with `dry_run=true` builds
133
+ everything and pushes nothing — handy for testing CI edits without a
134
+ throwaway tag.
135
+
136
+ ## Upgrading to npm Trusted Publishing (optional)
137
+
138
+ When you want to retire the long-lived `NPM_TOKEN`:
139
+
140
+ 1. Publish once manually from a laptop with 2FA (`npm publish --otp=...`)
141
+ so the package slot exists on npm.
142
+ 2. Go to https://www.npmjs.com/package/claude-b/access → *Trusted
143
+ Publishers* → *Add* → **GitHub Actions**. Fill:
144
+
145
+ | Field | Value |
146
+ |---|---|
147
+ | Organization or user | `danimoya` |
148
+ | Repository | `Claude-B` |
149
+ | Workflow filename | `release.yml` |
150
+ | Environment name | *(blank)* |
151
+
152
+ 3. Delete `NPM_TOKEN` from the repo secrets. The next release publishes
153
+ with cryptographic provenance and a green "verified" badge on the
154
+ package page.
155
+
156
+ The workflow is already OIDC-ready (`id-token: write` permission,
157
+ `--provenance` when no token is present).
158
+
159
+ ## Credentials & files — where things live
160
+
161
+ | File / secret | Purpose | Location |
162
+ |---|---|---|
163
+ | `~/.dockerhub-credentials` | Docker Hub PAT for release + README sync | dev machine, mode 600 |
164
+ | `~/.npm-credentials` | Nginx Proxy Manager admin (unrelated to npm) | dev machine, mode 600 |
165
+ | `GH secret NPM_TOKEN` | npmjs.com publish auth | repo secrets |
166
+ | `GH secret DOCKERHUB_*` | Docker Hub publish auth | repo secrets |
167
+ | `.npmrc` | never committed — see `.gitignore` | repo root if created locally |
@@ -0,0 +1,46 @@
1
+ %%{init: {'theme':'base', 'themeVariables': {
2
+ 'background': '#fdf7e9',
3
+ 'primaryColor': '#fff9e7',
4
+ 'primaryTextColor': '#2d2a22',
5
+ 'primaryBorderColor': '#c9b87a',
6
+ 'lineColor': '#8a7338',
7
+ 'secondaryColor': '#fff2c6',
8
+ 'tertiaryColor': '#fdf7e9',
9
+ 'actorBkg': '#fff9e7',
10
+ 'actorBorder': '#c9b87a',
11
+ 'actorTextColor': '#2d2a22',
12
+ 'activationBkgColor': '#fff2c6',
13
+ 'activationBorderColor': '#c9b87a',
14
+ 'noteBkgColor': '#fff9e7',
15
+ 'noteBorderColor': '#c9b87a',
16
+ 'signalColor': '#2d2a22',
17
+ 'signalTextColor': '#2d2a22',
18
+ 'labelBoxBkgColor': '#fff9e7',
19
+ 'labelBoxBorderColor': '#c9b87a',
20
+ 'labelTextColor': '#2d2a22',
21
+ 'loopTextColor': '#2d2a22',
22
+ 'sequenceNumberColor': '#2d2a22'
23
+ }}}%%
24
+ sequenceDiagram
25
+ autonumber
26
+ actor U as You
27
+ participant B as Claude-B
28
+ participant STT as STT<br/>Whisper · Speechmatics · Deepgram
29
+ participant O as Optimizer<br/>Claude Haiku 4.5
30
+ participant C as Claude Code<br/>Sonnet / Opus
31
+ participant T as TTS<br/>OpenAI · Deepgram
32
+
33
+ U->>B: voice note in
34
+ B->>STT: Opus audio
35
+ STT-->>B: raw transcript
36
+ B->>O: transcript + last 3 turns of<br/>session context
37
+ O-->>B: polished prompt
38
+ B-->>U: preview · confirm / edit / cancel
39
+ U->>B: confirm
40
+ B->>C: execute
41
+ C-->>B: response
42
+ B-->>U: markdown reply
43
+ U->>B: tap to listen
44
+ B->>T: text
45
+ T-->>B: Opus audio
46
+ B-->>U: voice reply out
Binary file
@@ -0,0 +1 @@
1
+ <svg id="container" width="1489" xmlns="http://www.w3.org/2000/svg" height="862" viewBox="-50 -10 1489 862" role="graphics-document document" aria-roledescription="sequence"><g><rect x="1231" y="776" fill="#eaeaea" stroke="#666" width="158" height="65" name="T" rx="3" ry="3" class="actor actor-bottom"></rect><text x="1310" y="808.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="1310" dy="-8">TTS</tspan></text><text x="1310" y="808.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="1310" dy="8">OpenAI · Deepgram</tspan></text></g><g><rect x="1031" y="776" fill="#eaeaea" stroke="#666" width="150" height="65" name="C" rx="3" ry="3" class="actor actor-bottom"></rect><text x="1106" y="808.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="1106" dy="-8">Claude Code</tspan></text><text x="1106" y="808.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="1106" dy="8">Sonnet / Opus</tspan></text></g><g><rect x="831" y="776" fill="#eaeaea" stroke="#666" width="150" height="65" name="O" rx="3" ry="3" class="actor actor-bottom"></rect><text x="906" y="808.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="906" dy="-8">Optimizer</tspan></text><text x="906" y="808.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="906" dy="8">Claude Haiku 4.5</tspan></text></g><g><rect x="503" y="776" fill="#eaeaea" stroke="#666" width="278" height="65" name="STT" rx="3" ry="3" class="actor actor-bottom"></rect><text x="642" y="808.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="642" dy="-8">STT</tspan></text><text x="642" y="808.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="642" dy="8">Whisper · Speechmatics · Deepgram</tspan></text></g><g><rect x="303" y="776" fill="#eaeaea" stroke="#666" width="150" height="65" name="B" rx="3" ry="3" class="actor actor-bottom"></rect><text x="378" y="808.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="378" dy="0">Claude-B</tspan></text></g><g></g><g><line id="actor5" x1="1310" y1="65" x2="1310" y2="776" class="actor-line 200" stroke-width="0.5px" stroke="#999" name="T"></line><g id="root-5"><rect x="1231" y="0" fill="#eaeaea" stroke="#666" width="158" height="65" name="T" rx="3" ry="3" class="actor actor-top"></rect><text x="1310" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="1310" dy="-8">TTS</tspan></text><text x="1310" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="1310" dy="8">OpenAI · Deepgram</tspan></text></g></g><g><line id="actor4" x1="1106" y1="65" x2="1106" y2="776" class="actor-line 200" stroke-width="0.5px" stroke="#999" name="C"></line><g id="root-4"><rect x="1031" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" name="C" rx="3" ry="3" class="actor actor-top"></rect><text x="1106" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="1106" dy="-8">Claude Code</tspan></text><text x="1106" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="1106" dy="8">Sonnet / Opus</tspan></text></g></g><g><line id="actor3" x1="906" y1="65" x2="906" y2="776" class="actor-line 200" stroke-width="0.5px" stroke="#999" name="O"></line><g id="root-3"><rect x="831" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" name="O" rx="3" ry="3" class="actor actor-top"></rect><text x="906" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="906" dy="-8">Optimizer</tspan></text><text x="906" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="906" dy="8">Claude Haiku 4.5</tspan></text></g></g><g><line id="actor2" x1="642" y1="65" x2="642" y2="776" class="actor-line 200" stroke-width="0.5px" stroke="#999" name="STT"></line><g id="root-2"><rect x="503" y="0" fill="#eaeaea" stroke="#666" width="278" height="65" name="STT" rx="3" ry="3" class="actor actor-top"></rect><text x="642" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="642" dy="-8">STT</tspan></text><text x="642" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="642" dy="8">Whisper · Speechmatics · Deepgram</tspan></text></g></g><g><line id="actor1" x1="378" y1="65" x2="378" y2="776" class="actor-line 200" stroke-width="0.5px" stroke="#999" name="B"></line><g id="root-1"><rect x="303" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" name="B" rx="3" ry="3" class="actor actor-top"></rect><text x="378" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-box" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="378" dy="0">Claude-B</tspan></text></g></g><g><line id="actor0" x1="75" y1="80" x2="75" y2="776" class="actor-line 200" stroke-width="0.5px" stroke="#999" name="U"></line></g><style>#container{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#2d2a22;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#container .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#container .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#container .error-icon{fill:#fdf7e9;}#container .error-text{fill:#020816;stroke:#020816;}#container .edge-thickness-normal{stroke-width:1px;}#container .edge-thickness-thick{stroke-width:3.5px;}#container .edge-pattern-solid{stroke-dasharray:0;}#container .edge-thickness-invisible{stroke-width:0;fill:none;}#container .edge-pattern-dashed{stroke-dasharray:3;}#container .edge-pattern-dotted{stroke-dasharray:2;}#container .marker{fill:#8a7338;stroke:#8a7338;}#container .marker.cross{stroke:#8a7338;}#container svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#container p{margin:0;}#container .actor{stroke:#c9b87a;fill:#fff9e7;}#container text.actor&gt;tspan{fill:#2d2a22;stroke:none;}#container .actor-line{stroke:#c9b87a;}#container .innerArc{stroke-width:1.5;stroke-dasharray:none;}#container .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#2d2a22;}#container .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#2d2a22;}#container #arrowhead path{fill:#2d2a22;stroke:#2d2a22;}#container .sequenceNumber{fill:#2d2a22;}#container #sequencenumber{fill:#2d2a22;}#container #crosshead path{fill:#2d2a22;stroke:#2d2a22;}#container .messageText{fill:#2d2a22;stroke:none;}#container .labelBox{stroke:#c9b87a;fill:#fff9e7;}#container .labelText,#container .labelText&gt;tspan{fill:#2d2a22;stroke:none;}#container .loopText,#container .loopText&gt;tspan{fill:#2d2a22;stroke:none;}#container .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:#c9b87a;fill:#c9b87a;}#container .note{stroke:#c9b87a;fill:#fff9e7;}#container .noteText,#container .noteText&gt;tspan{fill:#333;stroke:none;}#container .activation0{fill:#fff2c6;stroke:#c9b87a;}#container .activation1{fill:#fff2c6;stroke:#c9b87a;}#container .activation2{fill:#fff2c6;stroke:#c9b87a;}#container .actorPopupMenu{position:absolute;}#container .actorPopupMenuPanel{position:absolute;fill:#fff9e7;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#container .actor-man line{stroke:#c9b87a;fill:#fff9e7;}#container .actor-man circle,#container line{stroke:#c9b87a;fill:#fff9e7;stroke-width:2px;}#container :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g></g><defs><symbol id="computer" width="24" height="24"><path transform="scale(.5)" d="M2 2v13h20v-13h-20zm18 11h-16v-9h16v9zm-10.228 6l.466-1h3.524l.467 1h-4.457zm14.228 3h-24l2-6h2.104l-1.33 4h18.45l-1.297-4h2.073l2 6zm-5-10h-14v-7h14v7z"></path></symbol></defs><defs><symbol id="database" fill-rule="evenodd" clip-rule="evenodd"><path transform="scale(.5)" d="M12.258.001l.256.004.255.005.253.008.251.01.249.012.247.015.246.016.242.019.241.02.239.023.236.024.233.027.231.028.229.031.225.032.223.034.22.036.217.038.214.04.211.041.208.043.205.045.201.046.198.048.194.05.191.051.187.053.183.054.18.056.175.057.172.059.168.06.163.061.16.063.155.064.15.066.074.033.073.033.071.034.07.034.069.035.068.035.067.035.066.035.064.036.064.036.062.036.06.036.06.037.058.037.058.037.055.038.055.038.053.038.052.038.051.039.05.039.048.039.047.039.045.04.044.04.043.04.041.04.04.041.039.041.037.041.036.041.034.041.033.042.032.042.03.042.029.042.027.042.026.043.024.043.023.043.021.043.02.043.018.044.017.043.015.044.013.044.012.044.011.045.009.044.007.045.006.045.004.045.002.045.001.045v17l-.001.045-.002.045-.004.045-.006.045-.007.045-.009.044-.011.045-.012.044-.013.044-.015.044-.017.043-.018.044-.02.043-.021.043-.023.043-.024.043-.026.043-.027.042-.029.042-.03.042-.032.042-.033.042-.034.041-.036.041-.037.041-.039.041-.04.041-.041.04-.043.04-.044.04-.045.04-.047.039-.048.039-.05.039-.051.039-.052.038-.053.038-.055.038-.055.038-.058.037-.058.037-.06.037-.06.036-.062.036-.064.036-.064.036-.066.035-.067.035-.068.035-.069.035-.07.034-.071.034-.073.033-.074.033-.15.066-.155.064-.16.063-.163.061-.168.06-.172.059-.175.057-.18.056-.183.054-.187.053-.191.051-.194.05-.198.048-.201.046-.205.045-.208.043-.211.041-.214.04-.217.038-.22.036-.223.034-.225.032-.229.031-.231.028-.233.027-.236.024-.239.023-.241.02-.242.019-.246.016-.247.015-.249.012-.251.01-.253.008-.255.005-.256.004-.258.001-.258-.001-.256-.004-.255-.005-.253-.008-.251-.01-.249-.012-.247-.015-.245-.016-.243-.019-.241-.02-.238-.023-.236-.024-.234-.027-.231-.028-.228-.031-.226-.032-.223-.034-.22-.036-.217-.038-.214-.04-.211-.041-.208-.043-.204-.045-.201-.046-.198-.048-.195-.05-.19-.051-.187-.053-.184-.054-.179-.056-.176-.057-.172-.059-.167-.06-.164-.061-.159-.063-.155-.064-.151-.066-.074-.033-.072-.033-.072-.034-.07-.034-.069-.035-.068-.035-.067-.035-.066-.035-.064-.036-.063-.036-.062-.036-.061-.036-.06-.037-.058-.037-.057-.037-.056-.038-.055-.038-.053-.038-.052-.038-.051-.039-.049-.039-.049-.039-.046-.039-.046-.04-.044-.04-.043-.04-.041-.04-.04-.041-.039-.041-.037-.041-.036-.041-.034-.041-.033-.042-.032-.042-.03-.042-.029-.042-.027-.042-.026-.043-.024-.043-.023-.043-.021-.043-.02-.043-.018-.044-.017-.043-.015-.044-.013-.044-.012-.044-.011-.045-.009-.044-.007-.045-.006-.045-.004-.045-.002-.045-.001-.045v-17l.001-.045.002-.045.004-.045.006-.045.007-.045.009-.044.011-.045.012-.044.013-.044.015-.044.017-.043.018-.044.02-.043.021-.043.023-.043.024-.043.026-.043.027-.042.029-.042.03-.042.032-.042.033-.042.034-.041.036-.041.037-.041.039-.041.04-.041.041-.04.043-.04.044-.04.046-.04.046-.039.049-.039.049-.039.051-.039.052-.038.053-.038.055-.038.056-.038.057-.037.058-.037.06-.037.061-.036.062-.036.063-.036.064-.036.066-.035.067-.035.068-.035.069-.035.07-.034.072-.034.072-.033.074-.033.151-.066.155-.064.159-.063.164-.061.167-.06.172-.059.176-.057.179-.056.184-.054.187-.053.19-.051.195-.05.198-.048.201-.046.204-.045.208-.043.211-.041.214-.04.217-.038.22-.036.223-.034.226-.032.228-.031.231-.028.234-.027.236-.024.238-.023.241-.02.243-.019.245-.016.247-.015.249-.012.251-.01.253-.008.255-.005.256-.004.258-.001.258.001zm-9.258 20.499v.01l.001.021.003.021.004.022.005.021.006.022.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.023.018.024.019.024.021.024.022.025.023.024.024.025.052.049.056.05.061.051.066.051.07.051.075.051.079.052.084.052.088.052.092.052.097.052.102.051.105.052.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.048.144.049.147.047.152.047.155.047.16.045.163.045.167.043.171.043.176.041.178.041.183.039.187.039.19.037.194.035.197.035.202.033.204.031.209.03.212.029.216.027.219.025.222.024.226.021.23.02.233.018.236.016.24.015.243.012.246.01.249.008.253.005.256.004.259.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.021.224-.024.22-.026.216-.027.212-.028.21-.031.205-.031.202-.034.198-.034.194-.036.191-.037.187-.039.183-.04.179-.04.175-.042.172-.043.168-.044.163-.045.16-.046.155-.046.152-.047.148-.048.143-.049.139-.049.136-.05.131-.05.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.053.083-.051.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.05.023-.024.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.023.01-.022.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.127l-.077.055-.08.053-.083.054-.085.053-.087.052-.09.052-.093.051-.095.05-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.045-.118.044-.12.043-.122.042-.124.042-.126.041-.128.04-.13.04-.132.038-.134.038-.135.037-.138.037-.139.035-.142.035-.143.034-.144.033-.147.032-.148.031-.15.03-.151.03-.153.029-.154.027-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.01-.179.008-.179.008-.181.006-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.006-.179-.008-.179-.008-.178-.01-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.027-.153-.029-.151-.03-.15-.03-.148-.031-.146-.032-.145-.033-.143-.034-.141-.035-.14-.035-.137-.037-.136-.037-.134-.038-.132-.038-.13-.04-.128-.04-.126-.041-.124-.042-.122-.042-.12-.044-.117-.043-.116-.045-.113-.045-.112-.046-.109-.047-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.05-.093-.052-.09-.051-.087-.052-.085-.053-.083-.054-.08-.054-.077-.054v4.127zm0-5.654v.011l.001.021.003.021.004.021.005.022.006.022.007.022.009.022.01.022.011.023.012.023.013.023.015.024.016.023.017.024.018.024.019.024.021.024.022.024.023.025.024.024.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.052.11.051.114.051.119.052.123.05.127.051.131.05.135.049.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.044.171.042.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.022.23.02.233.018.236.016.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.012.241-.015.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.048.139-.05.136-.049.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.051.051-.049.023-.025.023-.024.021-.025.02-.024.019-.024.018-.024.017-.024.015-.023.014-.023.013-.024.012-.022.01-.023.01-.023.008-.022.006-.022.006-.022.004-.021.004-.022.001-.021.001-.021v-4.139l-.077.054-.08.054-.083.054-.085.052-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.044-.118.044-.12.044-.122.042-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.035-.143.033-.144.033-.147.033-.148.031-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.009-.179.009-.179.007-.181.007-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.007-.179-.007-.179-.009-.178-.009-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.031-.146-.033-.145-.033-.143-.033-.141-.035-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.04-.126-.041-.124-.042-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.051-.093-.051-.09-.051-.087-.053-.085-.052-.083-.054-.08-.054-.077-.054v4.139zm0-5.666v.011l.001.02.003.022.004.021.005.022.006.021.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.024.018.023.019.024.021.025.022.024.023.024.024.025.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.051.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.043.171.043.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.021.23.02.233.018.236.017.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.013.241-.014.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.049.139-.049.136-.049.131-.051.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.049.023-.025.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.022.01-.023.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.153l-.077.054-.08.054-.083.053-.085.053-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.048-.105.048-.106.048-.109.046-.111.046-.114.046-.115.044-.118.044-.12.043-.122.043-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.034-.143.034-.144.033-.147.032-.148.032-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.024-.161.024-.162.023-.163.023-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.01-.178.01-.179.009-.179.007-.181.006-.182.006-.182.004-.184.003-.184.001-.185.001-.185-.001-.184-.001-.184-.003-.182-.004-.182-.006-.181-.006-.179-.007-.179-.009-.178-.01-.176-.01-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.023-.162-.023-.161-.024-.159-.024-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.032-.146-.032-.145-.033-.143-.034-.141-.034-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.041-.126-.041-.124-.041-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.048-.105-.048-.102-.048-.1-.05-.097-.049-.095-.051-.093-.051-.09-.052-.087-.052-.085-.053-.083-.053-.08-.054-.077-.054v4.153zm8.74-8.179l-.257.004-.254.005-.25.008-.247.011-.244.012-.241.014-.237.016-.233.018-.231.021-.226.022-.224.023-.22.026-.216.027-.212.028-.21.031-.205.032-.202.033-.198.034-.194.036-.191.038-.187.038-.183.04-.179.041-.175.042-.172.043-.168.043-.163.045-.16.046-.155.046-.152.048-.148.048-.143.048-.139.049-.136.05-.131.05-.126.051-.123.051-.118.051-.114.052-.11.052-.106.052-.101.052-.096.052-.092.052-.088.052-.083.052-.079.052-.074.051-.07.052-.065.051-.06.05-.056.05-.051.05-.023.025-.023.024-.021.024-.02.025-.019.024-.018.024-.017.023-.015.024-.014.023-.013.023-.012.023-.01.023-.01.022-.008.022-.006.023-.006.021-.004.022-.004.021-.001.021-.001.021.001.021.001.021.004.021.004.022.006.021.006.023.008.022.01.022.01.023.012.023.013.023.014.023.015.024.017.023.018.024.019.024.02.025.021.024.023.024.023.025.051.05.056.05.06.05.065.051.07.052.074.051.079.052.083.052.088.052.092.052.096.052.101.052.106.052.11.052.114.052.118.051.123.051.126.051.131.05.136.05.139.049.143.048.148.048.152.048.155.046.16.046.163.045.168.043.172.043.175.042.179.041.183.04.187.038.191.038.194.036.198.034.202.033.205.032.21.031.212.028.216.027.22.026.224.023.226.022.231.021.233.018.237.016.241.014.244.012.247.011.25.008.254.005.257.004.26.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.022.224-.023.22-.026.216-.027.212-.028.21-.031.205-.032.202-.033.198-.034.194-.036.191-.038.187-.038.183-.04.179-.041.175-.042.172-.043.168-.043.163-.045.16-.046.155-.046.152-.048.148-.048.143-.048.139-.049.136-.05.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.05.051-.05.023-.025.023-.024.021-.024.02-.025.019-.024.018-.024.017-.023.015-.024.014-.023.013-.023.012-.023.01-.023.01-.022.008-.022.006-.023.006-.021.004-.022.004-.021.001-.021.001-.021-.001-.021-.001-.021-.004-.021-.004-.022-.006-.021-.006-.023-.008-.022-.01-.022-.01-.023-.012-.023-.013-.023-.014-.023-.015-.024-.017-.023-.018-.024-.019-.024-.02-.025-.021-.024-.023-.024-.023-.025-.051-.05-.056-.05-.06-.05-.065-.051-.07-.052-.074-.051-.079-.052-.083-.052-.088-.052-.092-.052-.096-.052-.101-.052-.106-.052-.11-.052-.114-.052-.118-.051-.123-.051-.126-.051-.131-.05-.136-.05-.139-.049-.143-.048-.148-.048-.152-.048-.155-.046-.16-.046-.163-.045-.168-.043-.172-.043-.175-.042-.179-.041-.183-.04-.187-.038-.191-.038-.194-.036-.198-.034-.202-.033-.205-.032-.21-.031-.212-.028-.216-.027-.22-.026-.224-.023-.226-.022-.231-.021-.233-.018-.237-.016-.241-.014-.244-.012-.247-.011-.25-.008-.254-.005-.257-.004-.26-.001-.26.001z"></path></symbol></defs><defs><symbol id="clock" width="24" height="24"><path transform="scale(.5)" d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.848 12.459c.202.038.202.333.001.372-1.907.361-6.045 1.111-6.547 1.111-.719 0-1.301-.582-1.301-1.301 0-.512.77-5.447 1.125-7.445.034-.192.312-.181.343.014l.985 6.238 5.394 1.011z"></path></symbol></defs><defs><marker id="arrowhead" refX="7.9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto-start-reverse"><path d="M -1 0 L 10 5 L 0 10 z"></path></marker></defs><defs><marker id="crosshead" markerWidth="15" markerHeight="8" orient="auto" refX="4" refY="4.5"><path fill="none" stroke="#000000" stroke-width="1pt" d="M 1,2 L 6,7 M 6,2 L 1,7" style="stroke-dasharray: 0, 0;"></path></marker></defs><defs><marker id="filled-head" refX="15.5" refY="7" markerWidth="20" markerHeight="28" orient="auto"><path d="M 18,7 L9,13 L14,7 L9,1 Z"></path></marker></defs><defs><marker id="sequencenumber" refX="15" refY="15" markerWidth="60" markerHeight="40" orient="auto"><circle cx="15" cy="15" r="6"></circle></marker></defs><g class="actor-man actor-top" name="U"><line id="actor-man-torso0" x1="75" y1="25" x2="75" y2="45"></line><line id="actor-man-arms0" x1="57" y1="33" x2="93" y2="33"></line><line x1="57" y1="60" x2="75" y2="45"></line><line x1="75" y1="45" x2="91" y2="60"></line><circle cx="75" cy="10" r="15" width="150" height="65"></circle><text x="75" y="67.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-man" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="75" dy="0">You</tspan></text></g><text x="225" y="80" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-size: 16px; font-weight: 400;">voice note in</text><line x1="76" y1="113" x2="374" y2="113" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><line x1="76" y1="113" x2="76" y2="113" stroke-width="0" marker-start="url(#sequencenumber)"></line><text x="76" y="117" font-family="sans-serif" font-size="12px" text-anchor="middle" class="sequenceNumber">1</text><text x="509" y="128" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-size: 16px; font-weight: 400;">Opus audio</text><line x1="379" y1="161" x2="638" y2="161" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><line x1="379" y1="161" x2="379" y2="161" stroke-width="0" marker-start="url(#sequencenumber)"></line><text x="379" y="165" font-family="sans-serif" font-size="12px" text-anchor="middle" class="sequenceNumber">2</text><text x="512" y="176" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-size: 16px; font-weight: 400;">raw transcript</text><line x1="641" y1="209" x2="382" y2="209" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><line x1="641" y1="209" x2="641" y2="209" stroke-width="0" marker-start="url(#sequencenumber)"></line><text x="641" y="213" font-family="sans-serif" font-size="12px" text-anchor="middle" class="sequenceNumber">3</text><text x="641" y="224" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-size: 16px; font-weight: 400;">transcript + last 3 turns of</text><text x="641" y="243" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-size: 16px; font-weight: 400;">session context</text><line x1="379" y1="276" x2="902" y2="276" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><line x1="379" y1="276" x2="379" y2="276" stroke-width="0" marker-start="url(#sequencenumber)"></line><text x="379" y="280" font-family="sans-serif" font-size="12px" text-anchor="middle" class="sequenceNumber">4</text><text x="644" y="291" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-size: 16px; font-weight: 400;">polished prompt</text><line x1="905" y1="324" x2="382" y2="324" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><line x1="905" y1="324" x2="905" y2="324" stroke-width="0" marker-start="url(#sequencenumber)"></line><text x="905" y="328" font-family="sans-serif" font-size="12px" text-anchor="middle" class="sequenceNumber">5</text><text x="228" y="339" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-size: 16px; font-weight: 400;">preview · confirm / edit / cancel</text><line x1="377" y1="372" x2="79" y2="372" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><line x1="377" y1="372" x2="377" y2="372" stroke-width="0" marker-start="url(#sequencenumber)"></line><text x="377" y="376" font-family="sans-serif" font-size="12px" text-anchor="middle" class="sequenceNumber">6</text><text x="225" y="387" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-size: 16px; font-weight: 400;">confirm</text><line x1="76" y1="420" x2="374" y2="420" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><line x1="76" y1="420" x2="76" y2="420" stroke-width="0" marker-start="url(#sequencenumber)"></line><text x="76" y="424" font-family="sans-serif" font-size="12px" text-anchor="middle" class="sequenceNumber">7</text><text x="741" y="435" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-size: 16px; font-weight: 400;">execute</text><line x1="379" y1="468" x2="1102" y2="468" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><line x1="379" y1="468" x2="379" y2="468" stroke-width="0" marker-start="url(#sequencenumber)"></line><text x="379" y="472" font-family="sans-serif" font-size="12px" text-anchor="middle" class="sequenceNumber">8</text><text x="744" y="483" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-size: 16px; font-weight: 400;">response</text><line x1="1105" y1="516" x2="382" y2="516" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><line x1="1105" y1="516" x2="1105" y2="516" stroke-width="0" marker-start="url(#sequencenumber)"></line><text x="1105" y="520" font-family="sans-serif" font-size="12px" text-anchor="middle" class="sequenceNumber">9</text><text x="228" y="531" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-size: 16px; font-weight: 400;">markdown reply</text><line x1="377" y1="564" x2="79" y2="564" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><line x1="377" y1="564" x2="377" y2="564" stroke-width="0" marker-start="url(#sequencenumber)"></line><text x="377" y="568" font-family="sans-serif" font-size="12px" text-anchor="middle" class="sequenceNumber">10</text><text x="225" y="579" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-size: 16px; font-weight: 400;">tap to listen</text><line x1="76" y1="612" x2="374" y2="612" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><line x1="76" y1="612" x2="76" y2="612" stroke-width="0" marker-start="url(#sequencenumber)"></line><text x="76" y="616" font-family="sans-serif" font-size="12px" text-anchor="middle" class="sequenceNumber">11</text><text x="843" y="627" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-size: 16px; font-weight: 400;">text</text><line x1="379" y1="660" x2="1306" y2="660" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><line x1="379" y1="660" x2="379" y2="660" stroke-width="0" marker-start="url(#sequencenumber)"></line><text x="379" y="664" font-family="sans-serif" font-size="12px" text-anchor="middle" class="sequenceNumber">12</text><text x="846" y="675" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-size: 16px; font-weight: 400;">Opus audio</text><line x1="1309" y1="708" x2="382" y2="708" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><line x1="1309" y1="708" x2="1309" y2="708" stroke-width="0" marker-start="url(#sequencenumber)"></line><text x="1309" y="712" font-family="sans-serif" font-size="12px" text-anchor="middle" class="sequenceNumber">13</text><text x="228" y="723" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-size: 16px; font-weight: 400;">voice reply out</text><line x1="377" y1="756" x2="79" y2="756" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><line x1="377" y1="756" x2="377" y2="756" stroke-width="0" marker-start="url(#sequencenumber)"></line><text x="377" y="760" font-family="sans-serif" font-size="12px" text-anchor="middle" class="sequenceNumber">14</text><g class="actor-man actor-bottom" name="U"><line id="actor-man-torso5" x1="75" y1="801" x2="75" y2="821"></line><line id="actor-man-arms5" x1="57" y1="809" x2="93" y2="809"></line><line x1="57" y1="836" x2="75" y2="821"></line><line x1="75" y1="821" x2="91" y2="836"></line><circle cx="75" cy="786" r="15" width="150" height="65"></circle><text x="75" y="843.5" dominant-baseline="central" alignment-baseline="central" class="actor actor-man" style="text-anchor: middle; font-size: 16px; font-weight: 400;"><tspan x="75" dy="0">You</tspan></text></g></svg>
@@ -1369,6 +1369,18 @@ async function registerNotificationRoutes(app, inbox) {
1369
1369
  const cleared = await inbox.markAllRead();
1370
1370
  return { success: true, cleared };
1371
1371
  });
1372
+ app.post("/api/notifications/delete-all", {
1373
+ preHandler: [app.authenticate]
1374
+ }, async () => {
1375
+ const deleted = await inbox.deleteAll();
1376
+ return { success: true, deleted };
1377
+ });
1378
+ app.post("/api/notifications/delete-read", {
1379
+ preHandler: [app.authenticate]
1380
+ }, async () => {
1381
+ const deleted = await inbox.deleteAll({ onlyRead: true });
1382
+ return { success: true, deleted };
1383
+ });
1372
1384
  }
1373
1385
 
1374
1386
  // src/rest/routes/telegram.ts
@@ -3479,27 +3491,39 @@ var HookEngine = class extends EventEmitter7 {
3479
3491
  };
3480
3492
 
3481
3493
  // src/notifications/inbox.ts
3482
- import { appendFile as appendFile2, readFile as readFile6, writeFile as writeFile6, mkdir as mkdir6, unlink } from "fs/promises";
3494
+ import { appendFile as appendFile2, readFile as readFile6, writeFile as writeFile6, mkdir as mkdir6, unlink, rename } from "fs/promises";
3483
3495
  import { existsSync as existsSync6 } from "fs";
3484
3496
  import { nanoid as nanoid4 } from "nanoid";
3485
3497
  var NotificationInbox = class {
3486
3498
  configDir;
3487
3499
  inboxPath;
3500
+ tmpPath;
3501
+ /** Tail of the per-instance write chain. Each mutating op chains onto it. */
3502
+ writeChain = Promise.resolve();
3488
3503
  constructor(configDir) {
3489
3504
  this.configDir = configDir;
3490
3505
  this.inboxPath = `${configDir}/notifications.jsonl`;
3506
+ this.tmpPath = `${configDir}/notifications.jsonl.tmp`;
3507
+ }
3508
+ /** Run an async fn under the write mutex; resolves with its result. */
3509
+ withLock(fn) {
3510
+ const next = this.writeChain.then(fn, fn);
3511
+ this.writeChain = next.catch(() => void 0);
3512
+ return next;
3491
3513
  }
3492
3514
  async addNotification(input) {
3493
- await mkdir6(this.configDir, { recursive: true });
3494
- const notification = {
3495
- ...input,
3496
- id: nanoid4(8),
3497
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3498
- read: false
3499
- };
3500
- await appendFile2(this.inboxPath, JSON.stringify(notification) + "\n");
3501
- await this.writeMarker(notification);
3502
- return notification;
3515
+ return this.withLock(async () => {
3516
+ await mkdir6(this.configDir, { recursive: true });
3517
+ const notification = {
3518
+ ...input,
3519
+ id: nanoid4(8),
3520
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3521
+ read: false
3522
+ };
3523
+ await appendFile2(this.inboxPath, JSON.stringify(notification) + "\n");
3524
+ await this.writeMarker(notification);
3525
+ return notification;
3526
+ });
3503
3527
  }
3504
3528
  async writeMarker(notification) {
3505
3529
  try {
@@ -3521,31 +3545,63 @@ var NotificationInbox = class {
3521
3545
  return limit ? all.slice(-limit) : all;
3522
3546
  }
3523
3547
  async markAllRead() {
3524
- const all = await this.readAll();
3525
- const unreadCount = all.filter((n) => !n.read).length;
3526
- if (unreadCount === 0) return 0;
3527
- const marked = all.map((n) => ({ ...n, read: true }));
3528
- await this.writeAll(marked);
3529
- await this.clearMarker();
3530
- return unreadCount;
3548
+ return this.withLock(async () => {
3549
+ const all = await this.readAll();
3550
+ const unreadCount = all.filter((n) => !n.read).length;
3551
+ if (unreadCount === 0) return 0;
3552
+ const marked = all.map((n) => ({ ...n, read: true }));
3553
+ await this.writeAll(marked);
3554
+ await this.clearMarker();
3555
+ return unreadCount;
3556
+ });
3531
3557
  }
3532
3558
  async markRead(id) {
3533
- const all = await this.readAll();
3534
- const notification = all.find((n) => n.id === id);
3535
- if (!notification || notification.read) return false;
3536
- notification.read = true;
3537
- await this.writeAll(all);
3538
- if (!all.some((n) => !n.read)) {
3539
- await this.clearMarker();
3540
- }
3541
- return true;
3559
+ return this.withLock(async () => {
3560
+ const all = await this.readAll();
3561
+ const notification = all.find((n) => n.id === id);
3562
+ if (!notification || notification.read) return false;
3563
+ notification.read = true;
3564
+ await this.writeAll(all);
3565
+ if (!all.some((n) => !n.read)) {
3566
+ await this.clearMarker();
3567
+ }
3568
+ return true;
3569
+ });
3542
3570
  }
3543
3571
  async deleteNotification(id) {
3544
- const all = await this.readAll();
3545
- const filtered = all.filter((n) => n.id !== id);
3546
- if (filtered.length === all.length) return false;
3547
- await this.writeAll(filtered);
3548
- return true;
3572
+ return this.withLock(async () => {
3573
+ const all = await this.readAll();
3574
+ const filtered = all.filter((n) => n.id !== id);
3575
+ if (filtered.length === all.length) return false;
3576
+ await this.writeAll(filtered);
3577
+ return true;
3578
+ });
3579
+ }
3580
+ /**
3581
+ * Delete every notification (or every read notification when
3582
+ * `onlyRead === true`) in a single atomic rewrite — far safer than
3583
+ * fanning out N independent DELETE calls from a remote client, each of
3584
+ * which would race the others through the read-modify-write cycle.
3585
+ */
3586
+ async deleteAll(opts = {}) {
3587
+ return this.withLock(async () => {
3588
+ const all = await this.readAll();
3589
+ let kept;
3590
+ let removed;
3591
+ if (opts.onlyRead) {
3592
+ kept = all.filter((n) => !n.read);
3593
+ removed = all.length - kept.length;
3594
+ } else {
3595
+ kept = [];
3596
+ removed = all.length;
3597
+ }
3598
+ if (removed === 0) return 0;
3599
+ await this.writeAll(kept);
3600
+ if (kept.every((n) => n.read)) {
3601
+ await this.clearMarker();
3602
+ }
3603
+ return removed;
3604
+ });
3549
3605
  }
3550
3606
  async count() {
3551
3607
  const all = await this.readAll();
@@ -3560,19 +3616,50 @@ var NotificationInbox = class {
3560
3616
  } catch {
3561
3617
  }
3562
3618
  }
3619
+ /**
3620
+ * Robust JSONL reader. Skips unparseable lines instead of blackholing
3621
+ * the entire inbox if any single line has been corrupted (e.g., by a
3622
+ * crashed writer that didn't finish flushing). The skipped lines stay
3623
+ * on disk — a later writeAll() rewrites the file from `kept`, dropping
3624
+ * them naturally.
3625
+ */
3563
3626
  async readAll() {
3564
3627
  if (!existsSync6(this.inboxPath)) return [];
3628
+ let content;
3565
3629
  try {
3566
- const content = await readFile6(this.inboxPath, "utf-8");
3567
- return content.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
3568
- } catch {
3630
+ content = await readFile6(this.inboxPath, "utf-8");
3631
+ } catch (err) {
3632
+ console.error("[inbox] failed to read notifications.jsonl:", err.message);
3569
3633
  return [];
3570
3634
  }
3635
+ const lines = content.split("\n").filter(Boolean);
3636
+ const out = [];
3637
+ let badLines = 0;
3638
+ for (const line of lines) {
3639
+ try {
3640
+ out.push(JSON.parse(line));
3641
+ } catch {
3642
+ badLines++;
3643
+ }
3644
+ }
3645
+ if (badLines > 0) {
3646
+ console.error(
3647
+ `[inbox] skipped ${badLines} malformed line(s) in notifications.jsonl (of ${lines.length} total). They will be dropped on next rewrite.`
3648
+ );
3649
+ }
3650
+ return out;
3571
3651
  }
3652
+ /**
3653
+ * Atomic rewrite: stage to `<file>.tmp`, then rename over the
3654
+ * destination. The rename is guaranteed atomic on POSIX, so a reader
3655
+ * sees either the pre-write file or the post-write file — never a
3656
+ * partial / truncated state.
3657
+ */
3572
3658
  async writeAll(notifications) {
3573
3659
  await mkdir6(this.configDir, { recursive: true });
3574
- const content = notifications.map((n) => JSON.stringify(n)).join("\n") + "\n";
3575
- await writeFile6(this.inboxPath, content);
3660
+ const content = notifications.length === 0 ? "" : notifications.map((n) => JSON.stringify(n)).join("\n") + "\n";
3661
+ await writeFile6(this.tmpPath, content);
3662
+ await rename(this.tmpPath, this.inboxPath);
3576
3663
  }
3577
3664
  };
3578
3665
 
@@ -3785,6 +3872,14 @@ var ClaudeBTelegramBot = class extends EventEmitter8 {
3785
3872
  }
3786
3873
  async handleStart(msg) {
3787
3874
  const chatId = String(msg.chat.id);
3875
+ const allowedRaw = process.env.TELEGRAM_ALLOWED_CHAT_IDS?.trim();
3876
+ if (allowedRaw) {
3877
+ const allowed = allowedRaw.split(",").map((s) => s.trim()).filter(Boolean);
3878
+ if (allowed.length > 0 && !allowed.includes(chatId)) {
3879
+ await this.safeSend(chatId, "\u26D4 This bot is private. Your chat is not authorised.");
3880
+ return;
3881
+ }
3882
+ }
3788
3883
  await this.configManager.addChatId(chatId);
3789
3884
  const voiceStatus = this.voicePipeline ? "\u{1F3A4} Voice input: Active" : "\u{1F3A4} Voice input: Not configured";
3790
3885
  const text = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-b",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Background-capable Claude Code with async workflows and REST API",
5
5
  "type": "module",
6
6
  "main": "dist/cli/index.js",
@@ -56,7 +56,7 @@
56
56
  "claude-code"
57
57
  ],
58
58
  "author": "danimoya",
59
- "license": "AGPL-3.0",
59
+ "license": "Apache-2.0",
60
60
  "repository": {
61
61
  "type": "git",
62
62
  "url": "git+https://github.com/danimoya/Claude-B.git"
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env bash
2
2
  # Claude-B installer
3
3
  # Usage:
4
- # curl -fsSL https://cb.danielmoya.cv | bash
5
- # curl -fsSL https://cb.danielmoya.cv | bash -s -- --method npm
4
+ # curl -fsSL https://cb.danimoya.com | bash
5
+ # curl -fsSL https://cb.danimoya.com | bash -s -- --method npm
6
6
  #
7
7
  # Methods (auto-detected, override with --method):
8
8
  # npm — requires node >= 20