feat(pine): 1.3.0 manifests — openwakeword member + live node status on the launcher page
Demo images / Build & push demo images (push) Failing after 1m40s

pine-openwakeword manifest (wyoming-openwakeword 2.1.0, :10400, /custom model
dir for the future Yo Archy model). Pine 1.3.0: launcher page gains a live
node-status card fed by /api/pine/status via a same-origin nginx proxy, and
copy for the new intents / Claude fallback / mesh announcements. Catalogs,
app-session config, drift ids regenerated; release catalog embeds 56
manifests (unsigned until the ceremony). Framework PT test plan in docs/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-22 02:59:34 -04:00
co-authored by Claude Fable 5
parent e074a117d2
commit 184257390d
9 changed files with 285 additions and 16 deletions
+70
View File
@@ -0,0 +1,70 @@
app:
id: pine-openwakeword
name: Pine Wake Word (openWakeWord)
version: "2.1.0"
description: Wyoming-protocol openWakeWord wake-word engine. Internal Pine voice-assistant stack member — lets Assist pipelines run wake-word detection on the node (groundwork for the custom "Yo Archy" wake word; stock models like "ok nabu" ship with the image).
category: home
# Hyphen name matches the runtime references (stack member table / startup
# order) so the orchestrator adopts a matching running container instead of
# recreating it.
container_name: pine-openwakeword
container:
image: docker.io/rhasspy/wyoming-openwakeword:2.1.0
pull_policy: if-not-present
network: archy-net
network_aliases: [pine-openwakeword]
# The image entrypoint binds tcp://0.0.0.0:10400. Preload the stock
# "ok nabu" model; /custom is where a trained custom model (yo_archy)
# drops in later — the engine picks up new .tflite files on restart.
custom_args: ["--preload-model", "ok_nabu", "--custom-model-dir", "/custom"]
dependencies:
- storage: 512Mi
resources:
memory_limit: 512Mi
security:
# cap-drop=ALL is applied by the orchestrator. A plain Python Wyoming server
# on an unprivileged port needs no added capabilities.
capabilities: []
readonly_root: false
no_new_privileges: true
network_policy: isolated
ports:
# Published so Home Assistant (on the pasta net) can reach the engine via
# host.containers.internal:10400 (the Wyoming integration endpoint).
- host: 10400
container: 10400
protocol: tcp
volumes:
- type: bind
source: /var/lib/archipelago/pine-openwakeword
target: /custom
options: [rw]
environment: []
health_check:
type: tcp
endpoint: localhost:10400
interval: 30s
timeout: 5s
retries: 5
start_period: 30s
metadata:
author: Rhasspy / Home Assistant
icon: /assets/img/app-icons/pine.svg
website: https://github.com/rhasspy/wyoming-openwakeword
repo: https://github.com/rhasspy/wyoming-openwakeword
license: MIT
tags:
- home
- voice
- wake-word
- wyoming
+59 -5
View File
@@ -1,8 +1,8 @@
app:
id: pine
name: Pine
version: "1.2.0"
description: A private voice assistant for your home. Pine runs speech-to-text (Whisper) and text-to-speech (Piper) on your own node and pairs with a PineVoice satellite speaker, so Home Assistant Assist works locally with nothing sent to the cloud.
version: "1.3.0"
description: A private voice assistant for your home. Pine runs speech-to-text (Whisper), text-to-speech (Piper) and wake-word detection (openWakeWord) on your own node and pairs with a PineVoice satellite speaker, so Home Assistant Assist works locally with nothing sent to the cloud. Ask it about your node — block height, sync, peers, Lightning balance — and, when a Claude API key is set, anything else.
category: home
# The user-facing launcher (app_id + container both "pine", matching the
@@ -30,6 +30,7 @@ app:
dependencies:
- app_id: pine-whisper
- app_id: pine-piper
- app_id: pine-openwakeword
- storage: 128Mi
resources:
@@ -92,6 +93,15 @@ app:
ssl_certificate_key /etc/nginx/tls.key;
root /usr/share/nginx/html;
index index.html;
# Live node facts for the status card — proxied to the node's
# public status tier so the (https) page can fetch same-origin.
location = /node-status {
proxy_pass http://host.containers.internal:80/api/pine/status;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_connect_timeout 5s;
proxy_read_timeout 10s;
}
location / { try_files $uri $uri/ /index.html; }
}
- path: /var/lib/archipelago/pine/index.html
@@ -156,9 +166,16 @@ app:
<div class="card status">
<div class="row"><b>Whisper</b><span>speech-to-text ready on <code>:10300</code></span></div>
<div class="row"><b>Piper</b><span>text-to-speech ready on <code>:10200</code></span></div>
<div class="row"><b>Wake word</b><span>“Hey Jarvis” on the speaker (openWakeWord on <code>:10400</code>)</span></div>
<div class="row"><b>Speaker</b><span>put it in pairing mode — ring LED blinking yellow</span></div>
</div>
<div class="card status">
<div class="row"><b>Node</b><span id="ns-node">checking…</span></div>
<div class="row"><b>Bitcoin</b><span id="ns-btc">—</span></div>
<div class="row"><b>Peers</b><span id="ns-peers">—</span></div>
</div>
<div class="card insecure" id="insecure">
This page isnt running over HTTPS, so the browser blocks Bluetooth.
Open it via its <b>https://…:10380</b> address (accept the self-signed
@@ -177,9 +194,13 @@ app:
<p class="ha">After WiFi joins, one manual step remains — pair the
speaker in Home Assistant: <b>Settings → Devices &amp; services →
Add Wyoming Protocol</b>, host = the speakers IP, port
<b>10700</b>. Whisper, Piper and the Assist pipeline are wired up
automatically when Pine installs. Wake word: <b>“Hey Jarvis.”</b>
You can also ask node things like <i>“whats the block height?”</i></p>
<b>10700</b>. Whisper, Piper, openWakeWord and the Assist pipeline
are wired up automatically when Pine installs. Wake word:
<b>“Hey Jarvis.”</b> Ask node things like <i>“whats the block
height?”</i>, <i>“how many peers?”</i>, <i>“is the node
synced?”</i> or <i>“whats my lightning balance?”</i> — and when a
Claude API key is set on the node, anything else gets answered by
Claude. New mesh messages are announced on the speaker too.</p>
<p class="ha">Troubleshooting: if it hears you (LED reacts) but answers
are silent, unplug and replug the speaker — an interrupted answer can
wedge its audio output until it reboots.</p>
@@ -304,6 +325,39 @@ app:
try { device?.gatt?.disconnect(); } catch {}
} finally { btn.disabled = false; }
});
// Live node status card — public tier of /api/pine/status via the
// same-origin /node-status proxy. Best-effort: failures just show
// "unavailable" and retry on the next tick.
const nsNode = document.getElementById("ns-node");
const nsBtc = document.getElementById("ns-btc");
const nsPeers = document.getElementById("ns-peers");
async function refreshNodeStatus() {
try {
const r = await fetch("/node-status", { cache: "no-store" });
if (!r.ok) throw new Error(String(r.status));
const s = await r.json();
const up = Math.floor((s.uptime_seconds || 0) / 3600);
nsNode.textContent = `Archipelago ${s.version || "?"} — up ${up}h`;
if (s.bitcoin && s.bitcoin.height != null) {
const pct = s.bitcoin.sync_percent;
nsBtc.textContent = `block ${s.bitcoin.height}` +
(pct != null ? (pct >= 99.99 ? " — synced" : ` — ${pct}% synced`) : "");
} else {
nsBtc.textContent = "not running";
}
const btcPeers = s.bitcoin && s.bitcoin.peers != null ? s.bitcoin.peers : "?";
const meshPeers = s.mesh ? s.mesh.peers : 0;
nsPeers.textContent = `${btcPeers} bitcoin` +
(s.mesh && s.mesh.enabled ? `, ${meshPeers} mesh` : "");
} catch {
nsNode.textContent = "node status unavailable";
nsBtc.textContent = "—";
nsPeers.textContent = "—";
}
}
refreshNodeStatus();
setInterval(refreshNodeStatus, 30000);
</script>
</body>
</html>