Pluggable AI providers, relay credit system, picker UX overhaul

Captures roughly forty version bumps (v0.2.6 → v0.2.47) of work that
accumulated without commits.

- Pluggable provider system under server/providers/: gemini, anthropic,
  openai, openai-compatible, ollama, whisper-compatible, relay. Mix and
  match transcription + analysis per request via the picker UI.
- Relay backend integration. Hardcoded relay URL in server/relay-default.js
  (operator-controlled at build time, not user-configurable). New
  /api/relay/{status,policy} endpoints proxy to the relay; balance pings
  populate a cached credit display.
- Per-install identity in server/install-id.js for relay credit accounting.
  Sent to the relay as X-Recap-Install-Id; persists across upgrades, lost
  on a full uninstall + reinstall. Not surfaced in the UI.
- Admin login gate (server/admin-auth.js + setAdminPassword action). Scrypt
  password hash + HMAC-signed session cookie.
- Entitlement scheme rename: pro / max (each paired with subscriptions and
  relay_pro / relay_max), replacing the misleading "core" entitlement
  that conflicted with the user-facing "Core" tier name.
- Activation screen: dynamic credit count pulled from /api/relay/policy,
  "Skip — use free mode" button, accurate paid-feature list.
- Top toolbar: inline credit-balance pill (or "BYO configured" fallback),
  Upgrade + "I have a key" buttons.
- Picker UI: per-provider sections with Save/Test/Delete buttons, sections
  collapsible by chevron, default-collapsed unless currently selected,
  "Use comped credits (reset to relay)" link when the user has strayed,
  green hint under inputs whose values are server-configured.
- Activity log: chevron-collapsible groups per video, refresh-survival via
  localStorage + a 500-entry server-side buffer, explicit Clear button.
- YouTube captions fast-path with user toggle (skips audio download + AI
  transcription when captions are available — uncheck for speaker labels).
- Cancel button: AbortController plumbed through every provider SDK call;
  retryAPI short-circuits on AbortError; cancellation events surface in
  the activity log instead of silent retries.
- Long-video analysis: auto-coalesce transcript entries before building the
  analysis prompt so local-model context windows (32k-ish) don't overflow.
  Original entries preserved for transcript display via an index map; the
  analyzer sees a coarser view but click-to-seek timestamps stay precise.
- StartOS action grouping (Setup / AI Providers) so the actions list is
  navigable.
- Manifest description rewritten to reflect multi-provider support and
  free-tier relay credits.
- Smaller fixes: summarize-button enablement no longer requires a Gemini
  key when other providers are configured; analysis fallback chain handles
  context-length and 503 capacity errors; single-segment expansion for
  providers that don't return per-segment timestamps (Parakeet et al.);
  many other UX polish items.
This commit is contained in:
Keysat
2026-05-11 23:46:20 -05:00
parent 2544cf7dde
commit 373d10595b
79 changed files with 6322 additions and 397 deletions
+44 -2
View File
@@ -24,8 +24,50 @@ import { v_0_2_2 } from './v0.2.2'
import { v_0_2_3 } from './v0.2.3'
import { v_0_2_4 } from './v0.2.4'
import { v_0_2_5 } from './v0.2.5'
import { v_0_2_6 } from './v0.2.6'
import { v_0_2_7 } from './v0.2.7'
import { v_0_2_8 } from './v0.2.8'
import { v_0_2_9 } from './v0.2.9'
import { v_0_2_10 } from './v0.2.10'
import { v_0_2_11 } from './v0.2.11'
import { v_0_2_12 } from './v0.2.12'
import { v_0_2_13 } from './v0.2.13'
import { v_0_2_14 } from './v0.2.14'
import { v_0_2_15 } from './v0.2.15'
import { v_0_2_16 } from './v0.2.16'
import { v_0_2_17 } from './v0.2.17'
import { v_0_2_18 } from './v0.2.18'
import { v_0_2_19 } from './v0.2.19'
import { v_0_2_20 } from './v0.2.20'
import { v_0_2_21 } from './v0.2.21'
import { v_0_2_22 } from './v0.2.22'
import { v_0_2_23 } from './v0.2.23'
import { v_0_2_24 } from './v0.2.24'
import { v_0_2_25 } from './v0.2.25'
import { v_0_2_26 } from './v0.2.26'
import { v_0_2_27 } from './v0.2.27'
import { v_0_2_28 } from './v0.2.28'
import { v_0_2_29 } from './v0.2.29'
import { v_0_2_30 } from './v0.2.30'
import { v_0_2_31 } from './v0.2.31'
import { v_0_2_32 } from './v0.2.32'
import { v_0_2_33 } from './v0.2.33'
import { v_0_2_34 } from './v0.2.34'
import { v_0_2_35 } from './v0.2.35'
import { v_0_2_36 } from './v0.2.36'
import { v_0_2_37 } from './v0.2.37'
import { v_0_2_38 } from './v0.2.38'
import { v_0_2_39 } from './v0.2.39'
import { v_0_2_40 } from './v0.2.40'
import { v_0_2_41 } from './v0.2.41'
import { v_0_2_42 } from './v0.2.42'
import { v_0_2_43 } from './v0.2.43'
import { v_0_2_44 } from './v0.2.44'
import { v_0_2_45 } from './v0.2.45'
import { v_0_2_46 } from './v0.2.46'
import { v_0_2_47 } from './v0.2.47'
export const versionGraph = VersionGraph.of({
current: v_0_2_5,
other: [v_0_2_4, v_0_2_3, v_0_2_2, v_0_2_1, v_0_2_0, v_0_1_18, v_0_1_17, v_0_1_16, v_0_1_15, v_0_1_14, v_0_1_13, v_0_1_12, v_0_1_11, v_0_1_10, v_0_1_9, v_0_1_8, v_0_1_7, v_0_1_6, v_0_1_5, v_0_1_4, v_0_1_3, v_0_1_2, v_0_1_1, v_0_1_0],
current: v_0_2_47,
other: [v_0_2_46, v_0_2_45, v_0_2_44, v_0_2_43, v_0_2_42, v_0_2_41, v_0_2_40, v_0_2_39, v_0_2_38, v_0_2_37, v_0_2_36, v_0_2_35, v_0_2_34, v_0_2_33, v_0_2_32, v_0_2_31, v_0_2_30, v_0_2_29, v_0_2_28, v_0_2_27, v_0_2_26, v_0_2_25, v_0_2_24, v_0_2_23, v_0_2_22, v_0_2_21, v_0_2_20, v_0_2_19, v_0_2_18, v_0_2_17, v_0_2_16, v_0_2_15, v_0_2_14, v_0_2_13, v_0_2_12, v_0_2_11, v_0_2_10, v_0_2_9, v_0_2_8, v_0_2_7, v_0_2_6, v_0_2_5, v_0_2_4, v_0_2_3, v_0_2_2, v_0_2_1, v_0_2_0, v_0_1_18, v_0_1_17, v_0_1_16, v_0_1_15, v_0_1_14, v_0_1_13, v_0_1_12, v_0_1_11, v_0_1_10, v_0_1_9, v_0_1_8, v_0_1_7, v_0_1_6, v_0_1_5, v_0_1_4, v_0_1_3, v_0_1_2, v_0_1_1, v_0_1_0],
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_10 = VersionInfo.of({
version: '0.2.10:0',
releaseNotes: {
en_US: 'YouTube captions fast path: when YouTube provides captions for a video (manual or auto-generated), Recap uses them directly and skips the audio download + AI transcription entirely. Much faster, much cheaper. Falls back to AI transcription if captions are missing or unusable. Richer transcription prompt: when we do transcribe, Recap now feeds Gemini the channel name, video description, and YouTube chapter markers — dramatically improving speaker name extraction so transcripts say "Dax:" and "Kristen:" instead of "Host:" and "Guest:". Other 0.2.10 changes: Gemini 2.5 Flash added as a transcription fallback when Gemini 3 Flash gets overloaded; new Test button on each provider in Settings (sends a 3-word ping to confirm your API key works); Ollama and OpenAI-Compatible providers now have a Models field in Settings — type comma-separated model names there and they appear as dropdown options in the picker.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_11 = VersionInfo.of({
version: '0.2.11:0',
releaseNotes: {
en_US: 'YouTube captions toggle: new checkbox in Settings → AI Providers lets you turn off the captions fast-path and force a full AI transcription. Default is on (faster); switch off when you want speaker labels (captions don\'t have them) or higher-quality text. Whisper as a separate transcription provider: configure any OpenAI-Audio-Transcription-API-compatible endpoint (whisper.cpp, faster-whisper-server, Groq\'s Whisper, etc.) via the new "Set Whisper Endpoint" StartOS action — gives you a free local-or-self-hosted transcription option alongside Gemini and OpenAI cloud Whisper. Model fallback hint: the Analysis dropdown now notes that the chosen model falls back through the remaining list if it fails — already worked, now visible.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_12 = VersionInfo.of({
version: '0.2.12:0',
releaseNotes: {
en_US: 'Fixes two bugs from 0.2.11: (1) the in-flight banner\'s poll loop was doing a full re-render every 5 seconds, which was wiping the activity log + YouTube embed and causing the visible flashing — now updates just the elapsed counter in place, with a smooth 1-second tick. (2) Auto-generated YouTube captions are fragmented into 1-3-word entries every 1-3 seconds; for a 30-minute video that\'s 900+ segments, which overwhelmed the analyzer prompt. Captions now get coalesced into ~15-second chunks before analysis, dropping segment counts ~5x while keeping timestamps accurate. Should resolve the "Error in input stream" failures on caption-fast-path videos.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_13 = VersionInfo.of({
version: '0.2.13:0',
releaseNotes: {
en_US: 'Fixes the activity-log flashing bug from 0.2.12 (a stale transition check in the poll loop was triggering a full re-render every 5 seconds — now only re-renders on real presence/cancellation transitions). When processing errors out mid-stream, the YouTube embed now stays visible alongside the error so you can still watch the video and one-click retry. Server-side error logging now captures the full stack + cause + provider names so we can finally see what\'s behind generic messages like "Error in input stream".',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_14 = VersionInfo.of({
version: '0.2.14:0',
releaseNotes: {
en_US: 'Three fixes around the Whisper / Parakeet path: (1) the activity log now says "Whisper at <hostname> (<model>)" instead of the misleading "OpenAI Whisper" when a custom endpoint is configured. (2) The user-defined Models field in Settings now actually reaches the server — previously the frontend kept it client-side only, so the server fell back to whisper-1 in its fallback chain (and tried whisper-1 against your Parakeet wrapper, which doesn\'t have it). Fallback chain now respects your model list. (3) retryAPI now logs the full error body/cause/status to the StartOS service logs on every failed attempt, so generic "500 status code (no body)" failures finally surface what the upstream actually returned.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_15 = VersionInfo.of({
version: '0.2.15:0',
releaseNotes: {
en_US: 'Activity log now uses the actual model name and host for custom transcription endpoints — "Uploading audio to parakeet-tdt-0.6b-v3 at 192.168.1.87:8000…" — instead of misleadingly calling everything "Whisper".',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_16 = VersionInfo.of({
version: '0.2.16:0',
releaseNotes: {
en_US: 'Critical fix: the server has been silently crashing every time the analysis step threw an error, since 0.2.9. Root cause was a JavaScript scoping bug — a constant declared inside the try block was referenced from the catch block, putting it in the Temporal Dead Zone, so the catch handler crashed itself the moment any error reached it. dumb-init then restarted Recap, which is why "Error in input stream" kept appearing — that was the SSE connection being killed by the dying process, not the actual error. Fixed by hoisting the constant to handler scope. After updating, analysis errors will be handled gracefully (logged with full context, surfaced to the UI), not by crashing the daemon.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_17 = VersionInfo.of({
version: '0.2.17:0',
releaseNotes: {
en_US: 'Hotfix for 0.2.16: the captions fast-path was failing with "analysisModel is not defined" because transcriptionModel + analysisModel were still block-scoped inside the audio-download branch — same scoping flavor as the CANCELLED_MARK bug. Hoisted them to handler scope so the captions path (which skips the audio block) can read them. Captions-based summaries should now work cleanly with Gemini.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_18 = VersionInfo.of({
version: '0.2.18:0',
releaseNotes: {
en_US: 'UX cleanup in Settings → API Keys & Endpoints. No more screen flashing on every keystroke — input changes update state silently in the background, no re-render. Each provider section now has its own Save button that flashes a green "✓ Saved" pill for 2.5 seconds so you actually know it landed. Save click is also what refreshes the model dropdown above with any new model names you typed in the Models field — so the flow is: type, click Save, see your new model appear in the picker. Renamed "Whisper (custom endpoint)" to "OpenAI/Whisper-Compatible Endpoint" — clearer that it\'s the wire format (and works for Parakeet, whisper.cpp, etc.), not literally Whisper.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_19 = VersionInfo.of({
version: '0.2.19:0',
releaseNotes: {
en_US: 'Hotfix: Test buttons in Settings → API Keys & Endpoints were returning "license_required" for unlicensed users because /api/providers/* wasn\'t in the license-gate allowlist. Now anyone can test connectivity to their LLM providers (and the provider auto-discovery endpoint) without a license — necessary for trying the app before buying.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_20 = VersionInfo.of({
version: '0.2.20:0',
releaseNotes: {
en_US: 'Settings UX overhaul: provider sections in API Keys & Endpoints are now individually collapsible (click the chevron next to the provider name) — defaults expand only the active transcription + analysis providers plus any with saved data, so the panel stays scannable instead of being one giant wall. Save button is now surgical — it updates the button into a green ✓ Saved pill in place + refreshes only the affected model picker dropdowns up top, without re-rendering the whole settings panel (no more full-screen flash). Whisper/Parakeet endpoint hardening: when the rich transcription request (verbose_json + segment timestamps) fails with a 4xx/5xx, Recap now retries automatically with a bare request shape — handles wrappers that don\'t implement OpenAI\'s optional params. The retry\'s success is logged so you know which path worked.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_21 = VersionInfo.of({
version: '0.2.21:0',
releaseNotes: {
en_US: 'Switching transcription provider to Whisper-compatible (or any provider with a dynamic catalog like Ollama / OpenAI-compatible) now pre-fills the model dropdown from your saved Models field instead of showing the "model name" placeholder. Previously it only looked at the provider\'s hardcoded model list, which is empty for these custom-endpoint providers.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_22 = VersionInfo.of({
version: '0.2.22:0',
releaseNotes: {
en_US: 'Better feedback + faster recovery when Gemini analysis hits an overloaded model. Three changes: (1) the activity log now prints "Retrying analysis... (attempt 2/2)" when a retry actually starts, not just when the previous attempt fails — so the log doesn\'t look frozen while a retry is in flight. (2) Analysis HTTP timeout shortened from 15 minutes to 2 minutes — text analysis shouldn\'t take that long, and we want to move to the next fallback model fast when the chosen one is overloaded. (3) Per-model retry count for analysis dropped from 2 to 1 — model-overload 503s don\'t clear in 5 seconds, so the outer fallback chain (Pro → Pro older → Flash → Flash 2.5) walks through models quickly instead of double-retrying each one.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_23 = VersionInfo.of({
version: '0.2.23:0',
releaseNotes: {
en_US: 'Reverts the aggressive 2-minute analysis timeout from 0.2.22. Long transcripts can legitimately take 3-5+ minutes to analyze, and the 2-min cap was cutting off real work (showing "code:499 The operation was cancelled" — that was us cancelling, not Gemini failing). The actual time-wasting bug from earlier (sitting on an overloaded model for 4 minutes) was already fixed by 0.2.22\'s retries=1 change, which makes 503s fast-fail and the fallback chain walk to the next model in seconds. Analysis now gets the 15-minute SDK default — succeeds on slow-but-working calls, still moves on quickly when a model is truly overloaded.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_24 = VersionInfo.of({
version: '0.2.24:0',
releaseNotes: {
en_US: 'Parakeet (and any Whisper-API endpoint that returns plain text without per-segment timestamps) now produces usable summaries. Previously, the entire transcript landed as one entry at [0:00] — which (a) tripped the truncation detector ("covers 0:00 of 2:15") and triggered a wasted chunked-transcription retry, and (b) gave the analyzer one giant blob so it could only output a single topic. Now Recap detects the single-entry case and splits the transcript into synthetic sentence-based entries with interpolated timestamps. Sentence boundaries (. ! ?) drive the split; very short sentences ("Yeah.") get coalesced into neighbors; timestamps are distributed proportionally by character count across the audio duration. Result: a 2-minute Parakeet transcript becomes ~10-20 entries that the analyzer can carve into proper topic sections.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_25 = VersionInfo.of({
version: '0.2.25:0',
releaseNotes: {
en_US: 'Cancel button now actually interrupts in-flight provider API calls (AbortController wired through transcription + analysis), so cancellation is immediate instead of waiting for the next pipeline checkpoint.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_26 = VersionInfo.of({
version: '0.2.26:0',
releaseNotes: {
en_US: 'Open up /api/process/current and /api/process/cancel for unlicensed users so the in-flight banner clears after the pipeline finishes (previously the banner kept counting forever for free-tier users).',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_27 = VersionInfo.of({
version: '0.2.27:0',
releaseNotes: {
en_US: 'Activity log: each video\'s entries can now be collapsed via the chevron on the group header. After a browser refresh during an in-flight free-tier job, the activity log is repopulated from the server\'s buffered log entries instead of starting blank.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_28 = VersionInfo.of({
version: '0.2.28:0',
releaseNotes: {
en_US: 'Auto-coalesce transcript before analysis on long videos (>400 segments) so the prompt fits in local-model context windows. Transcript display keeps full granularity — only the analyzer sees the coarser view. Flagged to watch for analysis-quality regressions.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_29 = VersionInfo.of({
version: '0.2.29:0',
releaseNotes: {
en_US: 'Activity log now persists across browser refreshes via localStorage (capped at 2000 entries) and has a Clear button in the drawer header. Server-side log rehydrate merges in any in-flight job entries the client missed during disconnection.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_30 = VersionInfo.of({
version: '0.2.30:0',
releaseNotes: {
en_US: 'Gemini transcription picker now lists 3-flash-preview, 2.5-flash, and 2.0-flash. Server fallback chain walks the same order when a model is overloaded.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_31 = VersionInfo.of({
version: '0.2.31:0',
releaseNotes: {
en_US: 'YouTube captions toggle no longer triggers a full re-render — checkbox state was already correct, the render flashed the entire settings screen for no UI benefit.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_32 = VersionInfo.of({
version: '0.2.32:0',
releaseNotes: {
en_US: 'Persistent install-ID: each Recap install now mints a UUID on first boot (stored at /data/install-id) that the upcoming relay backend will use to track comped + paid credits. Surfaced in Settings → Install ID with a Copy button, and at /api/install-id for programmatic access.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_33 = VersionInfo.of({
version: '0.2.33:0',
releaseNotes: {
en_US: 'Relay scaffolding: new "Relay (comped credits)" provider in the picker, /api/relay/status endpoint, server-side credit cache, /data install-id auto-attached to relay calls, X-Recap-Job-Id pairs transcribe + analyze into one credit charge per summary, and a "Set Relay URL" StartOS action. Inactive until the operator sets the relay base URL.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_34 = VersionInfo.of({
version: '0.2.34:0',
releaseNotes: {
en_US: 'Relay endpoint is now hardcoded in server/relay-default.js and no longer user-configurable. Removed the "Set Relay URL" StartOS action and the relay_base_url config field — operator controls relay routing entirely through Recap version updates. End users see only credit balance + tier.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_35 = VersionInfo.of({
version: '0.2.35:0',
releaseNotes: {
en_US: 'Activation screen copy now accurately reflects free tier (library + 5 relay credits + unlimited BYO-key usage; no more "no library" line). Each provider in API Keys & Endpoints has a Delete button that clears credentials from BOTH localStorage AND the StartOS server config; appears only when there\'s a stored value to clear. Save + Delete hidden entirely for providers with no user-configurable fields (e.g. Relay).',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_36 = VersionInfo.of({
version: '0.2.36:0',
releaseNotes: {
en_US: 'Picker UI now shows a per-field "✓ Server-configured" hint under any empty input whose value has been set via the StartOS action — so you can tell at a glance that the key is wired up server-side without it being exposed. Delete button correctly appears whenever EITHER the browser OR the server has a stored value (previously hidden when only the server had it).',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_37 = VersionInfo.of({
version: '0.2.37:0',
releaseNotes: {
en_US: 'Relay balance banner now populates on first page load via a server-side ping to GET /relay/balance (returns credits + tier without charging). Short timeout — if the relay is unreachable the UI falls back to "balance unknown" instead of hanging.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_38 = VersionInfo.of({
version: '0.2.38:0',
releaseNotes: {
en_US: 'Activation copy updated: free tier now offers 10 relay credits (was 5). Pairs with recap-relay 0.2.3, which splits Core lifetime budget into 5 Gemini-served credits + 5 operator-hardware-served credits.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_39 = VersionInfo.of({
version: '0.2.39:0',
releaseNotes: {
en_US: 'StartOS actions are now grouped (Setup / AI Providers) for easier operator navigation. Setup contains Admin Password + Recap License together at the top; AI Providers contains all the per-provider key/endpoint actions.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_40 = VersionInfo.of({
version: '0.2.40:0',
releaseNotes: {
en_US: 'Updated the StartOS package description to reflect current state: multi-provider AI (Gemini, Claude, OpenAI, OpenAI-compatible, Ollama, Whisper/Parakeet, all mix-and-matchable per request), free relay credits on first install, BYO-key for unlimited use, paid-tier feature list aligned with the current subscriptions + auto-queue + monthly-credit model. Install alert rewritten to point new users at relay credits or BYO key rather than Gemini-only setup.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_41 = VersionInfo.of({
version: '0.2.41:0',
releaseNotes: {
en_US: 'Renamed the paid-tier entitlement from "core" to "pro" (with "max" recognized as a parallel). Resolves the naming collision where "Core" is the user-facing name for the free tier but "core" was the server-side flag for paid status. Pro licenses now ship pro + subscriptions + relay_pro; Max licenses ship max + subscriptions + relay_max. No installs in the wild had core-entitled licenses yet, so this is a clean rename — no compat alias needed.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_42 = VersionInfo.of({
version: '0.2.42:0',
releaseNotes: {
en_US: 'Fixes: 1) Relay status pings now surface errors visibly — when /api/relay/status fails to populate (e.g. install-id not initialized, network error), the failure is logged + recorded so the UI shows a real error instead of "balance unknown". 2) Clicking Test on the Relay provider no longer burns a credit — it now hits the relay\'s /balance endpoint and reports "Connected · Tier: X · N credits remaining". 3) Test click auto-expands the provider\'s section so the result is actually visible (previously the result rendered inside a collapsed body and was invisible). 4) Provider sections in API Keys & Endpoints now only auto-expand if the provider is currently selected for transcription or analysis — having saved credentials no longer keeps them sprawled open.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_43 = VersionInfo.of({
version: '0.2.43:0',
releaseNotes: {
en_US: 'Fix: "Unknown provider: relay" error in the relay status pill. resolveProviderOpts() checked PROVIDER_KEY_FIELDS for the provider name first and threw before reaching the relay-specific baseURL/install-id injection. Added an empty relay entry to PROVIDER_KEY_FIELDS so the lookup passes; the injection still happens at the bottom of the function as before.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_44 = VersionInfo.of({
version: '0.2.44:0',
releaseNotes: {
en_US: 'Fresh installs now default to the Relay provider for both transcription and analysis (was Gemini). Adds a one-click "↺ Use comped credits (reset to relay)" link in Settings → AI Providers that swaps both pickers back to relay without touching your saved keys — useful when you previously selected Whisper / Ollama / etc. and want to return to the out-of-the-box experience. Auto-detected Ollama is still surfaced for users who actively want it, just no longer the default.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_45 = VersionInfo.of({
version: '0.2.45:0',
releaseNotes: {
en_US: 'Fix: Summarize button was disabled when Relay was the selected provider but no Gemini key was entered. The submit-disabled check now correctly recognizes Relay (and any other provider with a valid config — including auto-detected Ollama and server-configured keys) instead of insisting on a Gemini key specifically.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_46 = VersionInfo.of({
version: '0.2.46:0',
releaseNotes: {
en_US: 'Removed the Install ID display from Settings. Showing it telegraphs the uninstall-and-reinstall workaround for resetting credits, and there\'s no real user-facing need to surface it. The underlying mechanism is unchanged — Recap still generates an install ID on first boot and sends it to the relay for credit accounting, just doesn\'t display it in the UI.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_47 = VersionInfo.of({
version: '0.2.47:0',
releaseNotes: {
en_US: 'Fix: Summarize button no longer stays disabled after pasting a URL with Relay selected. The previous check waited for the async /api/relay/status call to flip state.relayStatus.configured, which could race against user input. Since the relay URL is hardcoded into the build, the client can assume Relay is runnable the moment it\'s selected — actual reachability errors surface inline at submit time via SSE.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_6 = VersionInfo.of({
version: '0.2.6:0',
releaseNotes: {
en_US: 'Add admin login gate: a new "Set Admin Password" StartOS action puts a username/password screen in front of everything (including the activation screen), so the web UI is no longer wide-open on LAN or clearnet. Leave the password blank to disable the gate.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_7 = VersionInfo.of({
version: '0.2.7:0',
releaseNotes: {
en_US: 'Plumb pluggable AI providers: Claude, OpenAI + Whisper, OpenAI-compatible (DeepSeek/Together/Groq/etc.), and local Ollama join Gemini. Transcription and analysis can independently target any provider per request. New "Set <Provider> Key" StartOS actions for each. Ollama is an optional StartOS dependency — the action auto-pre-populates http://ollama.startos:11434 when Ollama is installed on the same server. Web UI still defaults to Gemini; per-provider picker comes next.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_8 = VersionInfo.of({
version: '0.2.8:0',
releaseNotes: {
en_US: 'Hotfix: Dockerfile now copies server/providers/ into the runtime image — the 0.2.7 build was missing this directory, causing the service to crash on startup with ERR_MODULE_NOT_FOUND. Also lands the new "AI Providers" picker UI in Settings (Transcription + Analysis provider/model dropdowns + per-provider API keys & endpoints) — old single-key flow still works.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})
+12
View File
@@ -0,0 +1,12 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_9 = VersionInfo.of({
version: '0.2.9:0',
releaseNotes: {
en_US: 'Tier restructure: library + saved history are now free for everyone (the app felt broken without them). The "Free mode" upgrade pitch now leads with auto-queue + clips + relay credits. Refresh-survives-processing: when a free-tier job is in flight, a status banner at the top of the app shows what\'s running + an elapsed-time counter + a Cancel button — even after closing the tab and reloading. Ollama auto-detect: the Settings panel pre-fills http://ollama.startos:11434 when the Ollama StartOS package is installed alongside Recap. Cleaner error messaging when a second free-tier job is submitted while one is in flight (shows what\'s processing). New "Watch on YouTube" link beneath the embed so videos with embedding-disabled (a per-channel YouTube setting) still have a one-click fallback.',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})