Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.8
0.3.9
89 changes: 67 additions & 22 deletions bin/labwired-agent
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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 "$@"
;;
"")
Expand Down
55 changes: 40 additions & 15 deletions bin/labwired-agent.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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")
Expand All @@ -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 { }
Expand Down Expand Up @@ -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
}
"" {
Expand All @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion config/tui.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"$schema": "https://opencode.ai/tui.json",
"theme": "labwired"
"theme": "labwired",
"plugin": ["./plugins/labwired-brand.tsx"]
}
52 changes: 42 additions & 10 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
Expand Down Expand Up @@ -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"
Expand All @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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",
Expand Down
Loading
Loading