@goodandready/dsh-tts 0.4.18 → 0.4.20

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/CHANGELOG.md CHANGED
@@ -2,7 +2,12 @@
2
2
 
3
3
  Notable changes to `@goodandready/dsh-tts`.
4
4
 
5
- ## 0.4.18
5
+ ## 0.4.19
6
+
7
+ ### Fixed
8
+ - **Integration and cache stats reads require a trusted settings request.** `GET /dsh-tts/integrations` and `GET /dsh-tts/stats` now return 403 for a cross-site caller, the same rule as the other settings routes.
9
+ - **Removed the unused Kokoro WAV encoder.** Local Kokoro inference is not bundled, and nothing called `pcmFloat32ToWav`.
10
+
6
11
 
7
12
  ### Fixed
8
13
  - **Settings reachable again on the plugin's own page**: the current DSH core
package/lib/engines/f5.js CHANGED
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process'
2
2
 
3
3
  function collectPing(pythonBin, daemonPath) {
4
4
  return new Promise((resolve) => {
5
- const proc = spawn(pythonBin, [daemonPath, '--ping'])
5
+ const proc = spawn(pythonBin, [daemonPath, '--ping'], { windowsHide: true })
6
6
  let out = ''
7
7
  let settled = false
8
8
  const done = (ok) => {
@@ -37,26 +37,3 @@ export class KokoroEngine {
37
37
  throw new Error('Kokoro synthesis unavailable')
38
38
  }
39
39
  }
40
-
41
- export function pcmFloat32ToWav(samples, sampleRate = 24000) {
42
- const numSamples = samples.length
43
- const buffer = Buffer.alloc(44 + numSamples * 2)
44
- buffer.write('RIFF', 0)
45
- buffer.writeUInt32LE(36 + numSamples * 2, 4)
46
- buffer.write('WAVE', 8)
47
- buffer.write('fmt ', 12)
48
- buffer.writeUInt32LE(16, 16)
49
- buffer.writeUInt16LE(1, 20)
50
- buffer.writeUInt16LE(1, 22)
51
- buffer.writeUInt32LE(sampleRate, 24)
52
- buffer.writeUInt32LE(sampleRate * 2, 28)
53
- buffer.writeUInt16LE(2, 32)
54
- buffer.writeUInt16LE(16, 34)
55
- buffer.write('data', 36)
56
- buffer.writeUInt32LE(numSamples * 2, 40)
57
- for (let i = 0; i < numSamples; i++) {
58
- const s = Math.max(-1, Math.min(1, samples[i]))
59
- buffer.writeInt16LE(s < 0 ? s * 0x8000 : s * 0x7fff, 44 + i * 2)
60
- }
61
- return buffer
62
- }
package/lib/local.js CHANGED
@@ -5,7 +5,7 @@ import path from 'node:path'
5
5
 
6
6
  function spawnCollect(bin, args, { stdinText, timeoutMs } = {}) {
7
7
  return new Promise((resolve, reject) => {
8
- const proc = spawn(bin, args, { stdio: ['pipe', 'pipe', 'pipe'] })
8
+ const proc = spawn(bin, args, { stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true })
9
9
  const out = []
10
10
  const err = []
11
11
  const timer = setTimeout(() => {
package/lib/routes.js CHANGED
@@ -161,6 +161,10 @@ export function registerHttpRoutes(ctx, api) {
161
161
  path: '/dsh-tts/integrations',
162
162
  handler: async (req, res) => {
163
163
  if (req.method !== 'GET') { writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } }); return }
164
+ if (!isTrustedSettingsRequest(req)) {
165
+ writeJson(res, 403, { ok: false, error: { code: 'forbidden', message: 'same-origin only' } })
166
+ return
167
+ }
164
168
  let voiceInstalled = false
165
169
  let messengerInstalled = false
166
170
 
@@ -248,6 +252,10 @@ export function registerHttpRoutes(ctx, api) {
248
252
  path: '/dsh-tts/stats',
249
253
  handler: async (req, res) => {
250
254
  if (req.method === 'GET') {
255
+ if (!isTrustedSettingsRequest(req)) {
256
+ writeJson(res, 403, { ok: false, error: { code: 'forbidden', message: 'same-origin only' } })
257
+ return
258
+ }
251
259
  writeJson(res, 200, { ok: true, total: stats.total, cacheHits: stats.cacheHits, errors: stats.errors, providers: stats.providers, breaker: typeof breakerSnapshot === 'function' ? breakerSnapshot() : [] })
252
260
  return
253
261
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-tts",
3
- "version": "0.4.18",
3
+ "version": "0.4.20",
4
4
  "description": "Text-to-speech for DeepSeek Harness: speak agent replies in the Web UI with a provider fallback chain (OpenAI, ElevenLabs, Google, Azure, Groq, Deepgram, OpenRouter, Edge, Piper, eSpeak).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Sanitized publication to a mirror remote (GitHub).
4
+ #
5
+ # Gitea is the source of truth: branches, tags, issues, PRs and the full history,
6
+ # including build sources and internal tooling. A mirror is a *product* channel, so
7
+ # the published tree carries only product files — never agent instructions, plans,
8
+ # build sources, caches or infrastructure documents.
9
+ #
10
+ # Usage:
11
+ # ./scripts/publish-github.sh --check # read-only: what would go
12
+ # ./scripts/publish-github.sh --push <gitea-ref> # publish a sanitized tree
13
+ #
14
+ # The allowlist is the package's own `files` field (i.e. exactly what npm ships)
15
+ # plus the repository files a reader needs: .gitignore, LICENSE, README*.md,
16
+ # CHANGELOG.md, package.json, cordis.patch.yml.
17
+ #
18
+ # What --push does:
19
+ # 1. builds that tree from <gitea-ref>;
20
+ # 2. refuses to publish when a forbidden path or a required product file is missing;
21
+ # 3. creates ONE commit on top of the mirror branch carrying the tree and pushes it
22
+ # as a normal fast-forward — never a force;
23
+ # 4. prints the Gitea SHA -> mirror SHA correspondence for the release notes.
24
+ #
25
+ # Rewriting mirror history is a destructive operation on a published channel and is
26
+ # deliberately not implemented. Removing a file from *past* mirror history requires
27
+ # recreating the mirror repository and an explicit owner decision.
28
+ #
29
+ # Environment: MIRROR_REMOTE (default github), MIRROR_BRANCH (default main).
30
+ set -euo pipefail
31
+
32
+ MIRROR_REMOTE="${MIRROR_REMOTE:-github}"
33
+ MIRROR_BRANCH="${MIRROR_BRANCH:-main}"
34
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
35
+ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
36
+
37
+ # Repository files a reader needs, on top of the package's npm contents.
38
+ EXTRA_ALLOW=(
39
+ ".gitignore"
40
+ "LICENSE"
41
+ "README.md"
42
+ "README.ru.md"
43
+ "README.zh.md"
44
+ "CHANGELOG.md"
45
+ "package.json"
46
+ "cordis.patch.yml"
47
+ )
48
+
49
+ # Never publish these, even if they sneak into the allowlist.
50
+ FORBIDDEN=(
51
+ "AGENTS.md"
52
+ "index.md"
53
+ "deploy.sh"
54
+ ".gitea"
55
+ ".worktrees"
56
+ ".planning"
57
+ "__pycache__"
58
+ ".ruff_cache"
59
+ ".venv"
60
+ "node_modules"
61
+ )
62
+
63
+ mode=""
64
+ gitea_ref=""
65
+ for arg in "$@"; do
66
+ case "$arg" in
67
+ --check) mode="check" ;;
68
+ --push) mode="push" ;;
69
+ -*) echo "unknown option: $arg" >&2; exit 2 ;;
70
+ *) gitea_ref="$arg" ;;
71
+ esac
72
+ done
73
+
74
+ if [ -z "$mode" ]; then
75
+ echo "usage: ./scripts/publish-github.sh --check | --push <gitea-ref>" >&2
76
+ exit 2
77
+ fi
78
+ if [ "$mode" = "push" ] && [ -z "$gitea_ref" ]; then
79
+ echo "usage: ./scripts/publish-github.sh --push <gitea-ref>" >&2
80
+ exit 2
81
+ fi
82
+
83
+ cd "$REPO_DIR"
84
+
85
+ # The allowlist comes from the package manifest of the ref being published.
86
+ ALLOW=("${EXTRA_ALLOW[@]}")
87
+ while IFS= read -r entry; do
88
+ [ -n "$entry" ] && ALLOW+=("${entry%/}" )
89
+ done < <(git show "${gitea_ref:-origin/main}:package.json" | node -e '
90
+ let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{
91
+ try{const p=JSON.parse(s);for(const f of (p.files||[])) console.log(f.replace(/\/\*\*$/,""));}catch(e){}
92
+ });')
93
+
94
+ is_allowed() {
95
+ local path="$1" entry
96
+ for entry in "${ALLOW[@]}"; do
97
+ if [ "$path" = "$entry" ]; then return 0; fi
98
+ case "$path" in "$entry"/*) return 0 ;; esac
99
+ done
100
+ return 1
101
+ }
102
+
103
+ is_forbidden() {
104
+ local path="$1" entry
105
+ for entry in "${FORBIDDEN[@]}"; do
106
+ if [ "$path" = "$entry" ]; then return 0; fi
107
+ case "$path" in "$entry"/*) return 0 ;; esac
108
+ done
109
+ return 1
110
+ }
111
+
112
+ list_tree() {
113
+ local ref="$1" path
114
+ git ls-tree -r --name-only "$ref" | while IFS= read -r path; do
115
+ if is_forbidden "$path"; then continue; fi
116
+ if is_allowed "$path"; then echo "$path"; fi
117
+ done | LC_ALL=C sort
118
+ }
119
+
120
+ echo "repository : $REPO_DIR"
121
+ echo "mirror : $MIRROR_REMOTE/$MIRROR_BRANCH"
122
+ echo
123
+
124
+ if [ "$mode" = "check" ]; then
125
+ ref="${gitea_ref:-origin/main}"
126
+ echo "WILL BE PUBLISHED (sanitized tree of $ref):"
127
+ list_tree "$ref" | sed 's/^/ /'
128
+ echo
129
+ echo "WILL BE DROPPED (not product):"
130
+ git ls-tree -r --name-only "$ref" | while IFS= read -r path; do
131
+ if is_allowed "$path" && ! is_forbidden "$path"; then continue; fi
132
+ echo " $path"
133
+ done
134
+ exit 0
135
+ fi
136
+
137
+ source_sha="$(git rev-parse "$gitea_ref")"
138
+ mirror_sha="$(git rev-parse "$MIRROR_REMOTE/$MIRROR_BRANCH")"
139
+ echo "gitea ref : $gitea_ref = $source_sha"
140
+ echo "mirror head: $MIRROR_REMOTE/$MIRROR_BRANCH = $mirror_sha"
141
+ echo
142
+
143
+ sanitized_list="$(mktemp)"
144
+ tmp_index="$(mktemp)"
145
+ trap 'rm -f "$sanitized_list" "$tmp_index"' EXIT
146
+ list_tree "$gitea_ref" > "$sanitized_list"
147
+
148
+ if list_tree "$gitea_ref" | while IFS= read -r path; do
149
+ if is_forbidden "$path"; then echo "FORBIDDEN $path"; fi
150
+ done | grep -q FORBIDDEN; then
151
+ echo "refusing to publish: the sanitized list contains a forbidden path" >&2
152
+ exit 3
153
+ fi
154
+
155
+ for required in package.json README.md; do
156
+ if ! grep -qx "$required" "$sanitized_list"; then
157
+ echo "refusing to publish: required product file missing: $required" >&2
158
+ exit 4
159
+ fi
160
+ done
161
+
162
+ echo "files to publish: $(wc -l < "$sanitized_list")"
163
+ sed 's/^/ /' "$sanitized_list"
164
+ echo
165
+
166
+ # A temporary index is used instead of `git mktree`: mktree accepts only single-level
167
+ # entries and refuses nested paths such as lib/client.js, while `update-index
168
+ # --cacheinfo` builds the intermediate trees for us.
169
+ GIT_INDEX_FILE="$tmp_index" git read-tree --empty
170
+ while IFS= read -r path; do
171
+ blob="$(git rev-parse "$gitea_ref:$path")"
172
+ GIT_INDEX_FILE="$tmp_index" git update-index --add --cacheinfo "100644,$blob,$path"
173
+ done < "$sanitized_list"
174
+ new_tree="$(GIT_INDEX_FILE="$tmp_index" git write-tree)"
175
+
176
+ new_commit="$(git commit-tree "$new_tree" -p "$mirror_sha" -m "chore(publish): sanitized tree from ${source_sha:0:8}")"
177
+ echo "new mirror commit: $new_commit"
178
+
179
+ git push "$MIRROR_REMOTE" "$new_commit:refs/heads/$MIRROR_BRANCH"
180
+
181
+ echo
182
+ echo "correspondence to record in the release notes:"
183
+ echo " gitea $source_sha"
184
+ echo " mirror $new_commit"