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/LICENSE +201 -228
- package/README.md +62 -2
- package/RELEASING.md +167 -0
- package/assets/voice-pipeline.mmd +46 -0
- package/assets/voice-pipeline.png +0 -0
- package/assets/voice-pipeline.svg +1 -0
- package/dist/daemon/index.js +132 -37
- package/package.json +2 -2
- package/scripts/install.sh +2 -2
- package/website/DOCKERHUB.md +138 -0
- package/website/Favicon-Claude-B.png +0 -0
- package/website/deploy.sh +6 -6
- package/website/index.html +261 -22
- package/website/install.sh +2 -2
- package/website/sync-dockerhub-readme.sh +52 -0
- package/website/voice-pipeline.png +0 -0
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>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>tspan{fill:#2d2a22;stroke:none;}#container .loopText,#container .loopText>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>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>
|
package/dist/daemon/index.js
CHANGED
|
@@ -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
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
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
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
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
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
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
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
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
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
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.
|
|
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.
|
|
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": "
|
|
59
|
+
"license": "Apache-2.0",
|
|
60
60
|
"repository": {
|
|
61
61
|
"type": "git",
|
|
62
62
|
"url": "git+https://github.com/danimoya/Claude-B.git"
|
package/scripts/install.sh
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Claude-B installer
|
|
3
3
|
# Usage:
|
|
4
|
-
# curl -fsSL https://cb.
|
|
5
|
-
# curl -fsSL https://cb.
|
|
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
|