# Leaga OpenCode la Qwen3.8-Flash-Next de pe PC2 si la uneltele # web search / web fetch / bash / ssh / powershell / fisiere Office. # # irm http://10.11.90.169:8088/setup-fn.ps1 | iex # # Ruleaza-l din nou oricand ca sa actualizezi: aduce ultima versiune a regulilor si a # uneltelor, si actualizeaza OpenCode. Nu strica nimic daca rulezi de zece ori. # # Variabile optionale: $env:FN_SRV, $env:SKIP_SSH, $env:SKIP_PWSH, $env:SKIP_WEB, $env:SKIP_OFFICE function Install-FlashNext { $ProgressPreference = 'SilentlyContinue' try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {} $srv = if ($env:FN_SRV) { $env:FN_SRV } else { 'http://10.11.90.169:8099' } $setup = if ($env:SETUP_SRV) { $env:SETUP_SRV } else { 'http://10.11.90.169:8088' } $prov = 'flashnext' $model = 'qwen38-flash-next' function ok($m) { Write-Host " [ok] $m" -ForegroundColor Green } function rau($m) { Write-Host " [nu] $m" -ForegroundColor Red } function info($m) { Write-Host " $m" -ForegroundColor DarkGray } Write-Host "== OpenCode + Qwen3.8-Flash-Next ==" -ForegroundColor Cyan # ---- 1. serverul de model ---- try { $m = Invoke-RestMethod -Uri "$srv/v1/models" -TimeoutSec 15 ok "serverul de model raspunde: $srv" if (-not ($m.data.id -contains $model)) { rau "nu serveste '$model' ci: $($m.data.id -join ', ')" } } catch { rau "nu ajung la $srv" info "esti pe VPN? masina e pornita? Test-NetConnection $(([Uri]$srv).Host) -Port $(([Uri]$srv).Port)" return } # ---- 2. OpenCode ---- $oc = (Get-Command opencode -ErrorAction SilentlyContinue).Source if (-not $oc) { $cand = Join-Path $HOME '.opencode\bin\opencode.exe' if (Test-Path $cand) { $oc = $cand } } if (-not $oc) { info "instalez OpenCode..." $arch = if ([Environment]::Is64BitOperatingSystem) { 'x64' } else { 'arm64' } $binDir = Join-Path $HOME '.opencode\bin' New-Item -ItemType Directory -Force -Path $binDir | Out-Null $zip = Join-Path $env:TEMP "opencode-windows-$arch.zip" try { Invoke-WebRequest -Uri "https://github.com/anomalyco/opencode/releases/latest/download/opencode-windows-$arch.zip" ` -OutFile $zip -TimeoutSec 600 Expand-Archive -Path $zip -DestinationPath $binDir -Force Remove-Item $zip -ErrorAction SilentlyContinue $oc = Join-Path $binDir 'opencode.exe' # in PATH pentru terminalele viitoare $u = [Environment]::GetEnvironmentVariable('Path','User') if ($u -notlike "*$binDir*") { [Environment]::SetEnvironmentVariable('Path', "$u;$binDir", 'User') info "adaugat in PATH - deschide un terminal NOU ca sa mearga 'opencode'" } } catch { rau "instalarea a esuat: $_"; return } } else { info "actualizez OpenCode..." try { & $oc upgrade 2>&1 | Out-Null } catch {} } if (-not (Test-Path $oc)) { rau "opencode negasit dupa instalare"; return } ok "opencode: $oc ($(& $oc --version 2>&1 | Select-Object -Last 1))" # ---- 3. directorul de configurare: il intrebam pe opencode ---- $cfgDir = $null try { foreach ($l in (& $oc debug paths 2>&1)) { if ("$l" -match '^\s*config\s+(.+?)\s*$') { $cfgDir = $Matches[1]; break } } } catch {} if (-not $cfgDir) { $cfgDir = if ($env:XDG_CONFIG_HOME) { Join-Path $env:XDG_CONFIG_HOME 'opencode' } else { Join-Path $HOME '.config\opencode' } } $mcpDir = Join-Path $cfgDir 'mcp' New-Item -ItemType Directory -Force -Path $cfgDir, $mcpDir | Out-Null $cfg = Join-Path $cfgDir 'opencode.json' # ---- 4. python + unealta powershell ---- $py = (Get-Command python -ErrorAction SilentlyContinue).Source if (-not $py) { $py = (Get-Command python3 -ErrorAction SilentlyContinue).Source } $pwshMcp = $null if ($py -and $env:SKIP_PWSH -ne '1') { $pwshMcp = Join-Path $mcpDir 'pwsh_mcp.py' try { Invoke-WebRequest -Uri "$setup/pwsh_mcp.py" -OutFile $pwshMcp -TimeoutSec 60; ok "unealta powershell instalata" } catch { rau "nu am putut lua pwsh_mcp.py: $_"; $pwshMcp = $null } } elseif (-not $py) { info "python lipseste: sar peste uneltele powershell si ssh" } # ---- 5. uvx, pentru ssh si cautare web ---- $uvx = (Get-Command uvx -ErrorAction SilentlyContinue).Source if (-not $uvx) { $cand = Join-Path $HOME '.local\bin\uvx.exe' if (Test-Path $cand) { $uvx = $cand } } if (-not $uvx -and ($env:SKIP_SSH -ne '1' -or $env:SKIP_WEB -ne '1')) { info "instalez uv (pentru ssh si cautare web)..." try { irm https://astral.sh/uv/install.ps1 | iex $cand = Join-Path $HOME '.local\bin\uvx.exe' if (Test-Path $cand) { $uvx = $cand } } catch { } } if ($uvx) { ok "uvx: $uvx" } else { rau "uvx lipseste: sar peste ssh si cautare web" } # uv, nu doar uvx: office_mcp.py isi declara dependintele in stil PEP 723 $uv = (Get-Command uv -ErrorAction SilentlyContinue).Source if (-not $uv -and $uvx) { $c2 = $uvx -replace 'uvx\.exe$','uv.exe'; if (Test-Path $c2) { $uv = $c2 } } # ---- 5c. unealta pentru fisiere Office (.xlsx, .docx, .pptx) ---- # Serverele gata facute au 29 de unelte (8360 de tokene, ~33 s de prefill in plus # la fiecare cerere), din care 13 de scriere. Astea doua doar citesc. $officeMcp = $null if ($uv -and $env:SKIP_OFFICE -ne '1') { $officeMcp = Join-Path $mcpDir 'office_mcp.py' try { Invoke-WebRequest -Uri "$setup/office_mcp.py" -OutFile $officeMcp -TimeoutSec 60 ok "unealta Office (Excel, Word, PowerPoint)" } catch { rau "nu am putut lua office_mcp.py: $_"; $officeMcp = $null } } elseif ($env:SKIP_OFFICE -ne '1') { rau "uv lipseste: sar peste unealta Office" } # ---- 5b. regulile de lucru (cauta pe internet intai) ---- # Se aduc de fiecare data: o re-rulare a scriptului le actualizeaza. $reguli = Join-Path $cfgDir 'reguli-flashnext.md' try { Invoke-WebRequest -Uri "$setup/reguli-flashnext.md" -OutFile "$reguli.nou" -TimeoutSec 60 $vechi = if (Test-Path $reguli) { Get-Content $reguli -Raw } else { '' } $nou = Get-Content "$reguli.nou" -Raw if ($vechi -eq $nou) { Remove-Item "$reguli.nou"; $starea = 'deja la zi' } else { Move-Item "$reguli.nou" $reguli -Force; $starea = 'actualizate' } $m = Select-String -Path $reguli -Pattern 'versiune:\s*([0-9.\-]+)' | Select-Object -First 1 $v = if ($m) { $m.Matches[0].Groups[1].Value } else { '?' } ok "reguli $starea ($v)" } catch { rau "nu am putut lua regulile: $_"; $reguli = $null } # ---- 6. configuratia ---- # copia de siguranta se face abia dupa ce stim ca fisierul chiar se schimba, # altfel fiecare re-rulare ar lasa in urma inca un .bak $vechiTxt = if (Test-Path $cfg) { Get-Content $cfg -Raw } else { '' } # ConvertFrom-Json da PSCustomObject; convertim in hashtable ca sa putem modifica. # Tipurile simple se intorc INTAI: `$o -is [PSCustomObject]` e adevarat si pentru # siruri (totul e impachetat in PSObject), iar fara verificarea asta fiecare sir # citit dintr-o configuratie existenta devenea un hashtable cu proprietatile lui # ({"Length":25} in JSON) si strica fisierul la re-rulare. function ToHash($o) { if ($null -eq $o) { return $null } if ($o -is [string] -or $o -is [bool] -or $o -is [int] -or $o -is [long] -or $o -is [double] -or $o -is [decimal]) { return $o } if ($o -is [System.Collections.IDictionary]) { $h = @{}; foreach ($k in $o.Keys) { $h[$k] = ToHash $o[$k] }; return $h } if ($o -is [System.Collections.IList]) { return ,@($o | ForEach-Object { ToHash $_ }) } if ($o -is [System.Management.Automation.PSCustomObject]) { $h = @{} foreach ($p in $o.PSObject.Properties) { $h[$p.Name] = ToHash $p.Value } return $h } return $o } # Hashtable-urile nu au ordine stabila, deci JSON-ul iesea altfel la fiecare rulare # si comparatia "s-a schimbat ceva?" zicea mereu DA -- cate un .bak per rulare. # Sortam cheile inainte de serializare: aceeasi intrare, acelasi fisier. function Sorteaza($o) { if ($o -is [System.Collections.IDictionary]) { $d = [ordered]@{} foreach ($k in ($o.Keys | Sort-Object)) { $d[$k] = Sorteaza $o[$k] } return $d } if ($o -is [System.Collections.IList] -and $o -isnot [string]) { return ,@($o | ForEach-Object { Sorteaza $_ }) } return $o } $c = @{} if (Test-Path $cfg) { try { $c = ToHash (Get-Content $cfg -Raw | ConvertFrom-Json) } catch { $c = @{} } } $c['$schema'] = 'https://opencode.ai/config.json' if (-not $c.ContainsKey('provider')) { $c['provider'] = @{} } $c['provider'][$prov] = @{ npm = '@ai-sdk/openai-compatible' name = 'Qwen3.8-Flash-Next (PC2)' options = @{ baseURL = "$srv/v1" } models = @{ $model = @{ name = 'Qwen3.8-Flash-Next 125B-A6B' # fara tool_call:true OpenCode nu trimite deloc uneltele catre model tool_call = $true limit = @{ context = 131072; output = 8192 } } } } # FARA virgula in fata: `,@(...)` e corect doar la `return` dintr-o functie. # Intr-o atribuire creeaza un vector imbricat -> [["flashnext"]], si configuratia # devine invalida ("configuration is invalid"), fara niciun model listat. $c['enabled_providers'] = @($prov) $c['model'] = "$prov/$model" # regulile de lucru: "instructions" accepta cai locale si se combina cu AGENTS.md $inst = @() if ($c.ContainsKey('instructions')) { $inst = @($c['instructions'] | Where-Object { "$_" -notlike '*reguli-flashnext*' }) } if ($reguli) { $inst += $reguli } if ($inst.Count) { $c['instructions'] = @($inst) } if (-not $c.ContainsKey('permission')) { $c['permission'] = @{} } foreach ($k in 'bash','edit','read','webfetch') { $c['permission'][$k] = 'allow' } if (-not $c.ContainsKey('mcp')) { $c['mcp'] = @{} } foreach ($vechi in 'homelab','toolsmith','duckduckgo','morpheus','ssh','powershell','websearch','office') { $c['mcp'].Remove($vechi) | Out-Null } if ($uvx -and $env:SKIP_WEB -ne '1') { $c['mcp']['websearch'] = @{ type='local'; enabled=$true; timeout=120000 command = @($uvx,'duckduckgo-mcp-server','--transport','stdio') } } if ($uvx -and $env:SKIP_SSH -ne '1') { $c['mcp']['ssh'] = @{ type='local'; enabled=$true; timeout=300000 command = @($uvx,'mcp-ssh') } } if ($officeMcp -and $uv) { # `uv run` citeste dependintele din capul scriptului (PEP 723) si le tine in cache $c['mcp']['office'] = @{ type='local'; enabled=$true; timeout=300000 command = @($uv,'run','--quiet',$officeMcp) } } if ($pwshMcp -and $py) { $c['mcp']['powershell'] = @{ type='local'; enabled=$true; timeout=300000 command = @($py, $pwshMcp) } } # WriteAllText cu UTF8Encoding($false): fara BOM, la fel pe 5.1 si pe 7 $nouTxt = (Sorteaza $c) | ConvertTo-Json -Depth 32 if ($vechiTxt -eq $nouTxt) { ok "configuratie: neschimbata" } else { if ($vechiTxt) { $b = "$cfg.bak-$(Get-Date -Format yyyyMMdd-HHmmss)" [System.IO.File]::WriteAllText($b, $vechiTxt, (New-Object System.Text.UTF8Encoding($false))) info "configuratia veche salvata: $b" } # WriteAllText cu UTF8Encoding($false): fara BOM, la fel pe 5.1 si pe 7 [System.IO.File]::WriteAllText($cfg, $nouTxt, (New-Object System.Text.UTF8Encoding($false))) ok "scris: $cfg" } info "model implicit: $($c['model'])" info "unelte MCP: $(($c['mcp'].Keys | Sort-Object) -join ', ')" info "incorporate: bash, read, edit, webfetch" if ($reguli) { info "reguli : $reguli" } # ---- 7. verificare ---- Write-Host "" Write-Host "== verificare ==" -ForegroundColor Cyan $lista = @(& $oc models 2>&1 | Where-Object { "$_" -like "*$prov/*" }) if ($lista.Count) { ok "OpenCode vede doar:"; $lista | ForEach-Object { info "$_" } } else { rau "OpenCode nu listeaza modelul"; info "ruleaza 'opencode --print-logs models'" } if ($reguli) { ok "reguli de lucru incarcate" } Write-Host "" Write-Host "Porneste cu: opencode" Write-Host "Modelul se alege cu /models (e singurul din lista)." Write-Host "Actualizare: ruleaza din nou aceeasi comanda irm." } Install-FlashNext