From 9df1496d8b4a1f7163b94d9fc43074e87c6bbdcb Mon Sep 17 00:00:00 2001 From: tobias Date: Sat, 7 Mar 2026 22:50:13 +0100 Subject: [PATCH] scripts: add opencode model listing sync helper Queries lmstudio and ollama /v1/models endpoints to keep opencode.json model entries up to date, preserving manual overrides. Co-Authored-By: Claude Opus 4.6 --- README.md | 3 +- scripts/setup/update-models.sh | 151 +++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100755 scripts/setup/update-models.sh diff --git a/README.md b/README.md index ccf2120..2dd4abd 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ Format: `path | goal | usage`. This section is intentionally compact so `what` c - `scripts/setup/mount_container` | goal: mount or unmount LUKS container files listed in `.containers` manifests | usage: `scripts/setup/mount_container mount` - `scripts/setup/share.sh` | goal: run a local sharing workflow | usage: `scripts/setup/share.sh` - `scripts/setup/terminal-logs.sh` | goal: configure or collect terminal logging | usage: `scripts/setup/terminal-logs.sh` +- `scripts/setup/update-models.sh` | goal: sync lmstudio and ollama model listings into opencode.json | usage: `scripts/setup/update-models.sh [lmstudio|ollama]` - `scripts/windows/Get-ZimmermanTools.ps1` | goal: download Zimmerman forensic tools on Windows | usage: `powershell -File scripts/windows/Get-ZimmermanTools.ps1` - `scripts/windows/getscreen.psm1` | goal: provide PowerShell screen-capture helpers | usage: `Import-Module scripts/windows/getscreen.psm1` - `scripts/windows/sbom.ps1` | goal: generate or inspect SBOM-related data in PowerShell | usage: `powershell -File scripts/windows/sbom.ps1` @@ -298,7 +299,7 @@ Format: `path | goal | usage`. This section is intentionally compact so `what` c - `scripts/proxy/`: proxy environment propagation for apt, bash, and services. - `scripts/display/`: display and touchpad toggles, including named `xrandr` screen-layout presets. -- `scripts/setup/`: host setup helpers such as automounting, sharing, terminal logging, encrypted container mounting, and Ubuntu telemetry disabling. +- `scripts/setup/`: host setup helpers such as automounting, sharing, terminal logging, encrypted container mounting, Ubuntu telemetry disabling, and model listing sync for opencode. - `scripts/windows/`: PowerShell helpers, including Zimmerman tools bootstrap and SBOM-related work. ## `archive/`: Reference Material diff --git a/scripts/setup/update-models.sh b/scripts/setup/update-models.sh new file mode 100755 index 0000000..58126ba --- /dev/null +++ b/scripts/setup/update-models.sh @@ -0,0 +1,151 @@ +#!/usr/bin/env bash +# Updates lmstudio and/or ollama model listings in opencode.json +# by querying their /v1/models endpoints. +# +# Usage: +# ./update-models.sh # update both +# ./update-models.sh lmstudio # update only lmstudio +# ./update-models.sh ollama # update only ollama +# +# Requires: jq, curl + +set -uo pipefail + +CONFIG="${OPENCODE_CONFIG:-${HOME}/.config/opencode/opencode.json}" + +if [[ ! -f "$CONFIG" ]]; then + echo "Error: config not found at $CONFIG" >&2 + exit 1 +fi + +if ! command -v jq &>/dev/null; then + echo "Error: jq is required. Install with: brew install jq" >&2 + exit 1 +fi + +# Map provider name -> base URL (read from config) +get_base_url() { + jq -r ".provider.${1}.options.baseURL // empty" "$CONFIG" +} + +# Map provider name -> display label (read from config) +get_display_name() { + jq -r ".provider.${1}.name // \"${1}\"" "$CONFIG" +} + +# Fetch models from API endpoint and build the opencode models object. +# Preserves any existing entries that have extra metadata (limits, modalities, variants) +# beyond just a name — so your manual overrides aren't lost. +build_models_json() { + local provider="$1" + local base_url="$2" + local display_name="$3" + + local api_response + api_response=$(curl -sf --connect-timeout 5 "${base_url}/models" 2>/dev/null) || { + echo "Error: could not reach ${base_url}/models — is ${provider} running?" >&2 + return 1 + } + + # Get list of model IDs from API + local model_ids + model_ids=$(echo "$api_response" | jq -r '.data[].id' | sort) + + if [[ -z "$model_ids" ]]; then + echo "Warning: no models returned from ${provider}" >&2 + return 1 + fi + + # Get existing models object (to preserve manual overrides) + local existing + existing=$(jq ".provider.${provider}.models // {}" "$CONFIG") + + # Build new models object + local new_models="{}" + while IFS= read -r model_id; do + [[ -z "$model_id" ]] && continue + + # Check if this model has extra config beyond just "name" + local existing_entry + existing_entry=$(echo "$existing" | jq -r --arg id "$model_id" '.[$id] // empty') + + local has_extras="false" + if [[ -n "$existing_entry" ]]; then + local key_count + key_count=$(echo "$existing_entry" | jq 'keys | length') + if [[ "$key_count" -gt 1 ]]; then + has_extras="true" + fi + fi + + if [[ "$has_extras" == "true" ]]; then + # Preserve existing entry with overrides, just update the name + new_models=$(echo "$new_models" | jq \ + --arg id "$model_id" \ + --argjson entry "$existing_entry" \ + --arg name "${model_id} (${display_name})" \ + '.[$id] = ($entry | .name = $name)') + else + # Simple entry with just a name + new_models=$(echo "$new_models" | jq \ + --arg id "$model_id" \ + --arg name "${model_id} (${display_name})" \ + '.[$id] = { "name": $name }') + fi + done <<< "$model_ids" + + echo "$new_models" +} + +update_provider() { + local provider="$1" + + # Check provider exists in config + if ! jq -e ".provider.${provider}" "$CONFIG" &>/dev/null; then + echo "Skipping ${provider}: not configured in opencode.json" >&2 + return 0 + fi + + local base_url + base_url=$(get_base_url "$provider") + if [[ -z "$base_url" ]]; then + echo "Skipping ${provider}: no baseURL configured" >&2 + return 0 + fi + + local display_name + display_name=$(get_display_name "$provider") + + echo "Updating ${provider} models from ${base_url}..." + + local new_models + new_models=$(build_models_json "$provider" "$base_url" "$display_name") || return 1 + + local count + count=$(echo "$new_models" | jq 'keys | length') + + # Update config in-place + local tmp + tmp=$(mktemp) + jq --argjson models "$new_models" \ + ".provider.${provider}.models = \$models" \ + "$CONFIG" > "$tmp" && mv "$tmp" "$CONFIG" + + echo " ✓ ${provider}: ${count} models synced" +} + +# --- Main --- + +targets=("${@:-lmstudio ollama}") +errors=0 + +for target in $targets; do + update_provider "$target" || ((errors++)) +done + +if [[ "$errors" -gt 0 ]]; then + echo "Done with ${errors} error(s). Run 'opencode models' to verify." + exit 1 +else + echo "Done. Run 'opencode models' to verify." +fi