diff --git a/CHANGELOG.md b/CHANGELOG.md index 09fd593..48315f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.3.9 — 2026-08-11 — LabWired CLI brand (no OpenCode logo) + +- Replace OpenCode home wordmark with LabWired Agent branding via TUI plugin `labwired.brand`. +- Install and start always ship `plugins/labwired-brand.tsx` + `tui.json` plugin entry. +- User-facing start messages say LabWired CLI (engine stays OpenCode under the hood). + + ## 0.3.8 — 2026-08-10 — Product branding + install URLs - LabWired TUI theme (`branding/themes/labwired.json`) applied on install and agent start. diff --git a/VERSION b/VERSION index 6678432..940ac09 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.8 +0.3.9 diff --git a/bin/labwired-agent b/bin/labwired-agent index c583318..4c46e2c 100755 --- a/bin/labwired-agent +++ b/bin/labwired-agent @@ -211,6 +211,11 @@ cmd_doctor() { else printf '\033[33mwarn\033[0m tui: missing — re-run ./install.sh\n' fi + if [[ -f "$cfg/plugins/labwired-brand.tsx" ]] || [[ -f "$ROOT/plugins/labwired-brand.tsx" ]]; then + say "ok brand-plugin: labwired.brand (replaces OpenCode home logo)" + else + printf '\033[33mwarn\033[0m brand-plugin: missing — re-run labwired agent or ./install.sh\n' + fi # Five primary packs (ship surface). Aliases optional. local skill @@ -460,7 +465,7 @@ labwired_install_hosted_opencode_config() { done cp -R "$ROOT/skills/." "$cfg_dir/skills/" fi - say "OpenCode config: hosted (remote MCP + api.labwired.com/v1) → $cfg_dir/opencode.json" + say "LabWired config: hosted (api.labwired.com) → $cfg_dir/opencode.json" } cmd_logout() { @@ -508,14 +513,14 @@ cmd_whoami() { echo "mcp: remote https://api.labwired.com/mcp (shared labwired_* tools)" } -# Product chrome for the OpenCode TUI substrate (theme, banner, terminal title). -# OpenCode remains the engine; we brand the LabWired product surface. +# Product chrome: LabWired theme, banner, terminal title, and home_logo plugin +# that replaces the OpenCode wordmark. Engine remains OpenCode under the hood. labwired_apply_branding() { local cfg_dir cfg_dir="${OPENCODE_CONFIG_DIR:-$HOME/.config/opencode}" - mkdir -p "$cfg_dir/themes" "$cfg_dir/branding" + mkdir -p "$cfg_dir/themes" "$cfg_dir/branding" "$cfg_dir/plugins" - # Product theme is always refreshed; user banner/logo stay if already present. + # Product theme is always refreshed; user banner assets stay if already present. if [[ -f "$ROOT/branding/themes/labwired.json" ]]; then cp "$ROOT/branding/themes/labwired.json" "$cfg_dir/themes/labwired.json" fi @@ -529,19 +534,59 @@ labwired_apply_branding() { fi done fi - # Default product theme; do not clobber user-chosen themes (tokyonight, etc.). - if [[ ! -e "$cfg_dir/tui.json" ]]; then - if [[ -f "$ROOT/config/tui.json" ]]; then - cp "$ROOT/config/tui.json" "$cfg_dir/tui.json" - else - printf '%s\n' '{' ' "$schema": "https://opencode.ai/tui.json",' ' "theme": "labwired"' '}' >"$cfg_dir/tui.json" - fi - elif grep -Eq '"theme"[[:space:]]*:[[:space:]]*"(system|opencode)"' "$cfg_dir/tui.json" 2>/dev/null; then - if [[ -f "$ROOT/config/tui.json" ]]; then - cp "$ROOT/config/tui.json" "$cfg_dir/tui.json" - fi + + # Always ship the brand plugin next to tui.json (path is relative to that file). + if [[ -f "$ROOT/plugins/labwired-brand.tsx" ]]; then + cp "$ROOT/plugins/labwired-brand.tsx" "$cfg_dir/plugins/labwired-brand.tsx" fi + # Ensure tui.json selects LabWired theme + brand plugin. Preserve other user + # keys (keybinds, attention, …) when a tui.json already exists. + if [[ -f "$cfg_dir/tui.json" ]]; then + python3 - "$cfg_dir/tui.json" <<'PY' 2>/dev/null || true +import json, sys +path = sys.argv[1] +try: + cfg = json.load(open(path)) +except Exception: + cfg = {} +if not isinstance(cfg, dict): + cfg = {} +theme = cfg.get("theme") +if theme in (None, "", "system", "opencode"): + cfg["theme"] = "labwired" +plugins = cfg.get("plugin") +if not isinstance(plugins, list): + plugins = [] +# Normalize plugin specs to strings for membership checks. +def _spec(p): + if isinstance(p, str): + return p + if isinstance(p, list) and p: + return str(p[0]) + return "" +wanted = "./plugins/labwired-brand.tsx" +if not any(_spec(p) == wanted or _spec(p).endswith("labwired-brand.tsx") for p in plugins): + plugins = [wanted] + plugins +cfg["plugin"] = plugins +cfg.setdefault("$schema", "https://opencode.ai/tui.json") +open(path, "w").write(json.dumps(cfg, indent=2) + "\n") +PY + elif [[ -f "$ROOT/config/tui.json" ]]; then + cp "$ROOT/config/tui.json" "$cfg_dir/tui.json" + else + cat >"$cfg_dir/tui.json" <<'JSON' +{ + "$schema": "https://opencode.ai/tui.json", + "theme": "labwired", + "plugin": ["./plugins/labwired-brand.tsx"] +} +JSON + fi + + # Product identity: do not let the engine rename the terminal tab. + export OPENCODE_DISABLE_TERMINAL_TITLE=1 + # Terminal window / tab title (OSC 0). No-op when not a TTY. if [[ -t 1 ]] || [[ -t 2 ]]; then printf '\033]0;LabWired Agent\007' 2>/dev/null || true @@ -597,17 +642,17 @@ labwired_prepare_agent_start() { fi export LABWIRED_PROFILE=hosted export OPENCODE_CONFIG_DIR="$cfg_dir" - say "mode: hosted OpenCode + shared tools (api.labwired.com/mcp)" + say "mode: LabWired CLI (hosted · api.labwired.com)" say "model: ${LABWIRED_MODEL_URL:-}/ id=${LABWIRED_MODEL:-labwired-default}" else labwired_export_sim || true # Local default config if none if [[ ! -f "$cfg_dir/opencode.json" && -f "$ROOT/config/opencode.json" ]]; then cp "$ROOT/config/opencode.json" "$cfg_dir/opencode.json" - say "wrote local OpenCode config → $cfg_dir/opencode.json" + say "wrote LabWired config → $cfg_dir/opencode.json" fi export OPENCODE_CONFIG_DIR="$cfg_dir" - say "mode: local OpenCode (npx @labwired/mcp or LABWIRED_CLI sim)" + say "mode: LabWired CLI (local tools · npx @labwired/mcp or LABWIRED_CLI)" fi } @@ -683,7 +728,7 @@ LabWired Agent — write firmware, check on a twin Usage: labwired agent Start LabWired Agent (golden-path + MCP) - labwired agent opencode … Same start (OpenCode engine alias) + labwired agent opencode … Same start (engine alias) labwired agent login Device-code sign-in → hosted MCP + model gateway labwired agent logout Clear cloud session labwired agent whoami Show signed-in identity / project @@ -706,7 +751,7 @@ Usage: Ship gate (CI / release): ./scripts/ship-gate.sh -OpenCode is the agent runtime. Skills: golden-path · bringup · prove · observe · desk-hw +LabWired CLI. Skills: golden-path · bringup · prove · observe · desk-hw (+ Superpowers process). Knowledge via MCP labwired_part / labwired_datasheet. Env: @@ -1016,7 +1061,7 @@ case "${1:-}" in fi labwired_splash echo "labwired: starting LabWired Agent — skill golden-path; packs bringup/prove/observe/desk-hw" >&2 - echo "labwired: engine OpenCode · twin tools via MCP labwired_* · compose: labwired agent compose …" >&2 + echo "labwired: LabWired CLI · twin tools via MCP labwired_* · compose: labwired agent compose …" >&2 exec opencode "$@" ;; "") diff --git a/bin/labwired-agent.ps1 b/bin/labwired-agent.ps1 index 9472e77..bde8258 100644 --- a/bin/labwired-agent.ps1 +++ b/bin/labwired-agent.ps1 @@ -80,7 +80,7 @@ Usage: labwired agent install-deps Refresh tools into prefix labwired agent help - Alias: labwired agent opencode ... (same start; OpenCode engine) + Alias: labwired agent opencode ... (same start; engine alias) Env: LABWIRED_HOME Install root (default %USERPROFILE%\.labwired) @@ -93,8 +93,10 @@ function Apply-LabWiredBranding { $cfg = if ($env:OPENCODE_CONFIG_DIR) { $env:OPENCODE_CONFIG_DIR } else { Join-Path $env:USERPROFILE ".config\opencode" } $themesDir = Join-Path $cfg "themes" $brandingDir = Join-Path $cfg "branding" + $pluginsDir = Join-Path $cfg "plugins" if (-not (Test-Path $themesDir)) { New-Item -ItemType Directory -Path $themesDir -Force | Out-Null } if (-not (Test-Path $brandingDir)) { New-Item -ItemType Directory -Path $brandingDir -Force | Out-Null } + if (-not (Test-Path $pluginsDir)) { New-Item -ItemType Directory -Path $pluginsDir -Force | Out-Null } $themeSrc = Join-Path $AgentHome "branding\themes\labwired.json" if (Test-Path $themeSrc) { Copy-Item -Force $themeSrc (Join-Path $themesDir "labwired.json") @@ -104,25 +106,48 @@ function Apply-LabWiredBranding { if ((Test-Path $bannerSrc) -and -not (Test-Path $bannerDst)) { Copy-Item $bannerSrc $bannerDst } + $pluginSrc = Join-Path $AgentHome "plugins\labwired-brand.tsx" + if (Test-Path $pluginSrc) { + Copy-Item -Force $pluginSrc (Join-Path $pluginsDir "labwired-brand.tsx") + } $tuiSrc = Join-Path $AgentHome "config\tui.json" $tuiDst = Join-Path $cfg "tui.json" - $writeTui = -not (Test-Path $tuiDst) - if (-not $writeTui -and (Test-Path $tuiDst)) { - $existing = Get-Content -Raw $tuiDst - if ($existing -match '"theme"\s*:\s*"(system|opencode)"') { $writeTui = $true } - } - if ($writeTui) { + $wantedPlugin = "./plugins/labwired-brand.tsx" + if (-not (Test-Path $tuiDst)) { if (Test-Path $tuiSrc) { Copy-Item -Force $tuiSrc $tuiDst } else { - Set-Content -Path $tuiDst -Value @" -{ - "`$schema": "https://opencode.ai/tui.json", - "theme": "labwired" -} -"@ -Encoding utf8 + Set-Content -Path $tuiDst -Value (@{ + '$schema' = 'https://opencode.ai/tui.json' + theme = 'labwired' + plugin = @($wantedPlugin) + } | ConvertTo-Json) -Encoding utf8 + } + } else { + try { + $cfgObj = Get-Content -Raw $tuiDst | ConvertFrom-Json + if (-not $cfgObj.theme -or $cfgObj.theme -in @('system', 'opencode')) { + $cfgObj | Add-Member -NotePropertyName theme -NotePropertyValue 'labwired' -Force + } + $plugins = @() + if ($cfgObj.plugin) { $plugins = @($cfgObj.plugin) } + $hasBrand = $false + foreach ($p in $plugins) { + $s = if ($p -is [string]) { $p } elseif ($p -is [array] -and $p.Count -gt 0) { [string]$p[0] } else { '' } + if ($s -eq $wantedPlugin -or $s.EndsWith('labwired-brand.tsx')) { $hasBrand = $true; break } + } + if (-not $hasBrand) { + $cfgObj | Add-Member -NotePropertyName plugin -NotePropertyValue (@($wantedPlugin) + $plugins) -Force + } + if (-not $cfgObj.'$schema') { + $cfgObj | Add-Member -NotePropertyName '$schema' -NotePropertyValue 'https://opencode.ai/tui.json' -Force + } + ($cfgObj | ConvertTo-Json -Depth 8) | Set-Content -Path $tuiDst -Encoding utf8 + } catch { + if (Test-Path $tuiSrc) { Copy-Item -Force $tuiSrc $tuiDst } } } + $env:OPENCODE_DISABLE_TERMINAL_TITLE = "1" try { $Host.UI.RawUI.WindowTitle = "LabWired Agent" } catch { } @@ -294,7 +319,7 @@ switch ($cmd) { Write-Host "labwired: note - no local sim; hosted MCP verify still works." -ForegroundColor Yellow } Show-LabWiredSplash - Write-Host "labwired: starting LabWired Agent (OpenCode engine)" -ForegroundColor Cyan + Write-Host "labwired: starting LabWired CLI" -ForegroundColor Cyan & opencode @argsRest } "" { @@ -307,7 +332,7 @@ switch ($cmd) { Write-Host "labwired: note - no local sim; hosted MCP verify still works." -ForegroundColor Yellow } Show-LabWiredSplash - Write-Host "labwired: starting LabWired Agent (OpenCode engine)" -ForegroundColor Cyan + Write-Host "labwired: starting LabWired CLI" -ForegroundColor Cyan & opencode @argsRest } default { diff --git a/config/tui.json b/config/tui.json index 70e0b71..e4dcf05 100644 --- a/config/tui.json +++ b/config/tui.json @@ -1,4 +1,5 @@ { "$schema": "https://opencode.ai/tui.json", - "theme": "labwired" + "theme": "labwired", + "plugin": ["./plugins/labwired-brand.tsx"] } diff --git a/install.sh b/install.sh index cdd8ae0..647462d 100755 --- a/install.sh +++ b/install.sh @@ -320,7 +320,7 @@ rsync -a --delete \ --exclude '.DS_Store' \ --exclude '.grok' \ "$SRC/" "$AGENT_HOME/" 2>/dev/null || { - for d in bin lib config skills branding fixtures mcp scripts docs tests share examples; do + for d in bin lib config skills branding plugins fixtures mcp scripts docs tests share examples; do if [[ -d "$SRC/$d" ]]; then mkdir -p "$AGENT_HOME/$d" cp -R "$SRC/$d/." "$AGENT_HOME/$d/" @@ -354,10 +354,10 @@ WRAP chmod 0755 "$_prefix_dispatcher_tmp" mv -f "$_prefix_dispatcher_tmp" "${PREFIX_BIN}/labwired" -# Branding into OpenCode config (product identity). -# Force-refresh LabWired theme + default tui theme. Flat branding files are -# owned only when absent so user banners/custom assets survive reinstall. -mkdir -p "$CFG_DIR/branding" "$CFG_DIR/themes" +# Branding into config (product identity). +# Force-refresh LabWired theme, brand plugin (replaces OpenCode home logo), and +# default tui theme. Flat branding files are owned only when absent. +mkdir -p "$CFG_DIR/branding" "$CFG_DIR/themes" "$CFG_DIR/plugins" if [[ -f "$AGENT_HOME/branding/themes/labwired.json" ]]; then cp "$AGENT_HOME/branding/themes/labwired.json" "$CFG_DIR/themes/labwired.json" grep -Fqx "themes/labwired.json" "$MANIFEST" 2>/dev/null || printf '%s\n' "themes/labwired.json" >>"$MANIFEST" @@ -370,15 +370,47 @@ while IFS= read -r branding_file; do esac _install_owned_file_if_absent "$branding_file" "$CFG_DIR/branding/$rel" done < <(find "$AGENT_HOME/branding" -type f -print 2>/dev/null) +if [[ -f "$AGENT_HOME/plugins/labwired-brand.tsx" ]]; then + cp "$AGENT_HOME/plugins/labwired-brand.tsx" "$CFG_DIR/plugins/labwired-brand.tsx" + grep -Fqx "plugins/labwired-brand.tsx" "$MANIFEST" 2>/dev/null || printf '%s\n' "plugins/labwired-brand.tsx" >>"$MANIFEST" +fi if [[ -f "$AGENT_HOME/config/tui.json" ]]; then + # Always refresh product tui defaults (theme + brand plugin). User keybinds + # survive if already present via merge below. if [[ ! -e "$CFG_DIR/tui.json" && ! -L "$CFG_DIR/tui.json" ]]; then cp "$AGENT_HOME/config/tui.json" "$CFG_DIR/tui.json" - grep -Fqx "tui.json" "$MANIFEST" 2>/dev/null || printf '%s\n' "tui.json" >>"$MANIFEST" - elif grep -Eq '"theme"[[:space:]]*:[[:space:]]*"(system|opencode)"' "$CFG_DIR/tui.json" 2>/dev/null; then - # Upgrade stock OpenCode defaults to LabWired product theme. - cp "$AGENT_HOME/config/tui.json" "$CFG_DIR/tui.json" - grep -Fqx "tui.json" "$MANIFEST" 2>/dev/null || printf '%s\n' "tui.json" >>"$MANIFEST" + else + python3 - "$CFG_DIR/tui.json" "$AGENT_HOME/config/tui.json" <<'PY' 2>/dev/null || cp "$AGENT_HOME/config/tui.json" "$CFG_DIR/tui.json" +import json, sys +dst, src = sys.argv[1], sys.argv[2] +try: + cfg = json.load(open(dst)) +except Exception: + cfg = {} +try: + product = json.load(open(src)) +except Exception: + product = {"theme": "labwired", "plugin": ["./plugins/labwired-brand.tsx"]} +if not isinstance(cfg, dict): + cfg = {} +if cfg.get("theme") in (None, "", "system", "opencode"): + cfg["theme"] = product.get("theme", "labwired") +plugins = cfg.get("plugin") +if not isinstance(plugins, list): + plugins = [] +wanted = "./plugins/labwired-brand.tsx" +def _spec(p): + if isinstance(p, str): return p + if isinstance(p, list) and p: return str(p[0]) + return "" +if not any(_spec(p) == wanted or _spec(p).endswith("labwired-brand.tsx") for p in plugins): + plugins = [wanted] + list(plugins) +cfg["plugin"] = plugins +cfg.setdefault("$schema", product.get("$schema", "https://opencode.ai/tui.json")) +open(dst, "w").write(json.dumps(cfg, indent=2) + "\n") +PY fi + grep -Fqx "tui.json" "$MANIFEST" 2>/dev/null || printf '%s\n' "tui.json" >>"$MANIFEST" fi # Thin user PATH shim — self-contained (no need to source env.sh first) diff --git a/package.json b/package.json index d23180e..943d140 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@labwired/agent", - "version": "0.3.8", + "version": "0.3.9", "description": "Firmware agent with LabWired twin verification", "license": "MIT", "homepage": "https://labwired.com", @@ -61,6 +61,7 @@ "branding/logo.svg", "branding/theme.css", "branding/themes/labwired.json", + "plugins/labwired-brand.tsx", "share/smoke/status-parser-model-verified.json", "share/smoke/status-parser-failed.json", "scripts/profiles/esp32c3-serial.sh", diff --git a/plugins/labwired-brand.tsx b/plugins/labwired-brand.tsx new file mode 100644 index 0000000..764b2b1 --- /dev/null +++ b/plugins/labwired-brand.tsx @@ -0,0 +1,58 @@ +/** + * LabWired TUI brand plugin. + * + * Replaces the OpenCode home_logo slot so the product surface is LabWired CLI, + * not OpenCode. Engine remains OpenCode under the hood. + * + * Loaded via config/tui.json → plugin: ["./plugins/labwired-brand.tsx"] + * (path is relative to the tui.json that declares it; install copies this + * file next to ~/.config/opencode/tui.json as plugins/labwired-brand.tsx). + */ +/** @jsxImportSource @opentui/solid */ +import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui" + +type ColorMap = Record + +function color(map: ColorMap, name: string, fallback: string): unknown { + const value = map[name] + if (typeof value === "string" && value.trim()) return value + if (value != null && typeof value === "object") return value + return fallback +} + +const tui: TuiPlugin = async (api) => { + api.slots.register({ + // Win replace mode against the built-in OpenCode Logo. + order: 0, + slots: { + home_logo(ctx) { + const map = (ctx.theme?.current ?? {}) as ColorMap + const primary = color(map, "primary", "#3d8fd1") + const muted = color(map, "textMuted", "#8b91a3") + const text = color(map, "text", "#e8eaf0") + // Simple, product-owned mark — not the OpenCode wordmark. + const art = [ + " L A B W I R E D", + " ───────────────", + " A G E N T", + ] + return ( + + {art.map((line, i) => ( + {line} + ))} + + Write firmware · check on a twin + + ) + }, + }, + }) +} + +const plugin: TuiPluginModule & { id: string } = { + id: "labwired.brand", + tui, +} + +export default plugin diff --git a/scripts/install.ps1 b/scripts/install.ps1 index e65496d..1ca3742 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -623,7 +623,7 @@ function Install-OpenCodeConfig { if (-not (Test-Path $destination)) { Protect-Mutation $destination; Copy-Safe $_.FullName $destination -Recurse } } } - # LabWired product chrome: always refresh theme; keep user banner if present. + # LabWired product chrome: theme + brand plugin (replaces OpenCode home logo). $themeSrc = Join-Path $agent "branding\themes\labwired.json" if (Test-Path $themeSrc) { $themesDir = Join-Path $cfg "themes" @@ -645,20 +645,47 @@ function Install-OpenCodeConfig { } } } + $pluginSrc = Join-Path $agent "plugins\labwired-brand.tsx" + if (Test-Path $pluginSrc) { + $pluginsDir = Join-Path $cfg "plugins" + Protect-Mutation $pluginsDir + Ensure-Dir $pluginsDir + $pluginDst = Join-Path $pluginsDir "labwired-brand.tsx" + Protect-Mutation $pluginDst + Copy-Safe $pluginSrc $pluginDst + } $tuiSrc = Join-Path $agent "config\tui.json" $tuiDst = Join-Path $cfg "tui.json" if (Test-Path $tuiSrc) { - $shouldWriteTui = -not (Test-Path $tuiDst) - if (-not $shouldWriteTui) { - $existing = Get-Content -Raw $tuiDst - if ($existing -match '"theme"\s*:\s*"(system|opencode)"') { $shouldWriteTui = $true } - } - if ($shouldWriteTui) { - Protect-Mutation $tuiDst + Protect-Mutation $tuiDst + if (-not (Test-Path $tuiDst)) { Copy-Safe $tuiSrc $tuiDst + } else { + # Merge product theme + brand plugin into existing tui.json. + try { + $cfgObj = Get-Content -Raw $tuiDst | ConvertFrom-Json + $product = Get-Content -Raw $tuiSrc | ConvertFrom-Json + if (-not $cfgObj.theme -or $cfgObj.theme -in @('system', 'opencode')) { + $cfgObj | Add-Member -NotePropertyName theme -NotePropertyValue $product.theme -Force + } + $wanted = "./plugins/labwired-brand.tsx" + $plugins = @() + if ($cfgObj.plugin) { $plugins = @($cfgObj.plugin) } + $has = $false + foreach ($p in $plugins) { + $s = if ($p -is [string]) { $p } else { '' } + if ($s -eq $wanted -or $s.EndsWith('labwired-brand.tsx')) { $has = $true; break } + } + if (-not $has) { + $cfgObj | Add-Member -NotePropertyName plugin -NotePropertyValue (@($wanted) + $plugins) -Force + } + ($cfgObj | ConvertTo-Json -Depth 8) | Set-Content -Path $tuiDst -Encoding utf8 + } catch { + Copy-Safe $tuiSrc $tuiDst + } } } - Ok "LabWired config -> $cfg (OpenCode engine)" + Ok "LabWired config -> $cfg" } # -- main ----------------------------------------