O que lan-direct faz?
Direcione o tráfego do aplicativo para fora pela ISP enquanto você, o agente, permanece na VPN.
Funciona tanto no Claude Code quanto no Codex — o mecanismo é vinculação por soquete, que não tem nada a ver com o invólucro.
O invariante
Nunca modifique a tabela de roteamento, as configurações de proxy do WinINET/sistema ou a configuração do cliente VPN. Apenas:
- defina variáveis de ambiente com escopo de um processo filho, ou
- passe flags de vinculação de origem por ferramenta.
Ambos são por soquete e opt-in, portanto não podem afetar sua própria conexão. Este é o ponto central do skill — se você recorrer a route add, você o quebrou (veja a seção Válvula de escape no final).
Não sugira desconectar a VPN. Esse é o problema que este skill existe para evitar.
Fluxo de trabalho
1. Detectar e verificar
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\lan-detect.ps1
No Linux ou macOS use a contraparte — mesmo JSON, mesmo contrato de código de saída:
bash scripts/lan-detect.sh
Emite JSON: lanIp, gateway, lanDns, subnet, tunnelActive, vpnAdapter, directEgress, lanEgress, fakeIpDns, bypassWorks. Sai com código 0 apenas quando bypassWorks é verdadeiro — ele prova o bypass com duas sondagens ativas, em vez de inferi-lo.
tunnelActive: false-> nenhuma VPN está ativa. Informe e pare; não há nada a ser desviado.bypassWorks: falsecom um túnel ativo -> vá para Solução de problemas abaixo. Não prossiga. O tráfego permaneceria silenciosamente na VPN e você relataria um falso positivo.
2. Escolha o mecanismo
Backend Node (incl. Next.js)? Pule o proxy — vá direto para a receita Node em Receitas por stack. Uma variável de ambiente, sem processo de proxy, e é a única coisa que atinge o fetch nativo.
Todo o resto: inicie o proxy vinculado à LAN. Um processo servidor não tem uma flag --interface, então esta é a maneira de obter um egresso pela ISP.
node scripts/lan-proxy.mjs --bind <lanIp> --port 8899 # executar em segundo plano
node scripts/lan-proxy.mjs --bind <lanIp> --port 8899 --dns <lanDns> # se fakeIpDns: true
Ele escuta apenas em 127.0.0.1 e registra cada host que tunela, para que você possa ver o que o aplicativo realmente chamou.
3. Reinicie o backend apontando para ele
HTTP_PROXY=http://127.0.0.1:8899
HTTPS_PROXY=http://127.0.0.1:8899
NO_PROXY=localhost,127.0.0.1,<subnet>
NO_PROXY é importante: sem ele, as chamadas do backend para seus próprios serviços locais são enviadas através do proxy sem motivo. (Bancos de dados locais em TCP — MySQL, Postgres, Mongo — não são HTTP e não são afetados de qualquer forma.)
Defina essas variáveis no comando de inicialização do backend, não globalmente. Veja Receitas por stack abaixo — HTTP_PROXY não é suficiente para várias stacks.
4. Execute e confirme
Acesse o endpoint cuja chamada de saída estava falhando. Confirme ambos:
- a chamada agora é bem-sucedida, e
- o log do proxy mostra o host que ele discou.
Um sucesso sem uma linha de log correspondente significa que a requisição nunca passou pelo proxy.
5. Encerramento
Pare o proxy, reinicie o backend sem as variáveis de ambiente. Nada mais a desfazer — nenhum estado global foi alterado.
pkill -f lan-proxy do Git Bash não mata um node.exe no Windows. Use:
Get-CimInstance Win32_Process -Filter "Name='node.exe'" |
Where-Object { $_.CommandLine -like '*lan-proxy*' } |
ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
Uma instância remanescente é inofensiva, mas fará a próxima inicialização falhar com conflito de porta.
Receitas por stack
| Stack | Como alimentar o proxy |
|---|---|
Python requests / httpx | variáveis de ambiente, como está |
Go, .NET HttpClient | variáveis de ambiente, como está |
| PHP CLI | libcurl lê http_proxy / https_proxy do ambiente do processo |
| Node (incl. Next.js) | não o proxy — use o pré-carregamento lan-bind.mjs, veja abaixo |
| PHP sob Apache/XAMPP | veja abaixo — o serviço não herda seu shell |
| curl, wget, npm, ssh, git | pule o proxy, vincule diretamente (veja Vinculação direta abaixo) |
Node — use o pré-carregamento, não o proxy
O fetch nativo roda no undici embutido do Node, que ignora HTTP_PROXY no Node <= 23 e não aceita https.Agent. Verificado no Node 22.20: variáveis de ambiente simples, NODE_USE_ENV_PROXY=1 e --use-env-proxy não fazem nada — o log do proxy permanece vazio e o tráfego sai silenciosamente pela VPN. Isso afeta Next.js, o SDK openai e qualquer outra coisa no fetch nativo.
Pule o proxy completamente e use o pré-carregamento, que vincula cada soquete de saída no processo:
LAN_BIND=<lanIp> node --import "file:///<skill-dir>/scripts/lan-bind.mjs" script.js
LAN_BIND=<lanIp> NODE_OPTIONS="--import file:///<skill-dir>/scripts/lan-bind.mjs" npm run dev
<skill-dir> é o diretório base deste skill com barras normais — normalmente C:/Users/<voce>/.claude/skills/lan-direct no Claude Code, ou C:/Users/<voce>/.agents/skills/lan-direct no Codex. --import precisa de uma URL file:// absoluta no Windows; um caminho Windows puro com barras invertidas falha.
NODE_OPTIONS se propaga para workers bifurcados, cobrindo servidores de desenvolvimento Next.js e Vite.
Isso atinge fetch nativo, o SDK openai, axios, node-fetch, got e http/https simples de uma só vez — sem dependência, sem processo de proxy, sem necessidade de HTTP_PROXY. Ele ignora destinos loopback, então um banco de dados local ou servidor de desenvolvimento continua funcionando.
Apenas se o pré-carregamento não estiver disponível: Node >= 24 respeita NODE_USE_ENV_PROXY=1 com as variáveis de ambiente, e axios as respeita em qualquer versão.
PHP sob Apache / XAMPP
SetEnv/PassEnv no httpd.conf apenas preenche $_SERVER; o libcurl lê o ambiente do processo, então não os captará. Use um destes:
- inicie o Apache a partir de um shell que já tenha
http_proxy/https_proxyexportado (ex.:xampp\apache_start.bat), ou putenv('https_proxy=http://127.0.0.1:8899');antes decurl_init(), oucurl_setopt($ch, CURLOPT_PROXY, '127.0.0.1:8899');diretamente.
file_get_contents() não respeita nenhum desses — ele precisa de um stream context com 'proxy' => 'tcp://127.0.0.1:8899'.
Vinculação direta
Não é necessário proxy quando a ferramenta pode vincular seu próprio soquete:
| Ferramenta | Flag |
|---|---|
| curl | --interface <lanIp> |
| wget | --bind-address=<lanIp> |
| npm | --local-address=<lanIp> |
| ssh | -b <lanIp> |
| Chromium / Playwright | não existe flag de vinculação — use o MCP de navegador na porta 8900, veja abaixo |
MCP de Navegador / Playwright
Chromium não tem flag de vinculação de origem, então um navegador atinge a ISP apenas através do proxy. Use lan-browser.mjs: é um servidor MCP Playwright que inicia e possui seu próprio proxy, para que o navegador nunca possa ser iniciado sem um.
Ele é fornecido com este skill — scripts/install.ps1 o registra:
powershell -NoProfile -ExecutionPolicy Bypass -File <skill-dir>\scripts\install.ps1
Quais hosts usam a ISP vem de três fontes mescladas, então isso quase nunca requer reinstalação: lan-hosts.txt no diretório do skill (padrões fornecidos, substituídos no git pull), ~/.lan-direct-hosts (do próprio usuário, nunca sobrescrito), e --lan-hosts a,b na inicialização. Uma regra por linha, comentários # permitidos, correspondência por sufixo.
Quando um usuário desejar que outro host seja roteado via ISP, adicione uma linha a ~/.lan-direct-hosts e peça para ele reiniciar o navegador — não edite lan-hosts.txt, que a próxima atualização sobrescreverá.
Se o usuário pedir para registrá-lo manualmente, observe que -- deve sobreviver ao vinculador de parâmetros do PowerShell — splat os argumentos como um array, ou execute-o a partir do bash:
# a partir de um clone
claude mcp add playwright-split --scope user -- \
node <skill-dir>/scripts/lan-browser.mjs --lan-hosts ".ir"
# a partir do npm — sobrevive à movimentação do checkout, então prefira quando o usuário o tiver instalado
claude mcp add playwright-split --scope user -- npx -y lan-direct browser
Outros subcomandos CLI, quando o pacote está instalado: lan-direct detect escolhe o script de detecção correto para a plataforma, e lan-direct route <host> responde "este host iria pela ISP ou pelo túnel?" sem iniciar um navegador. Recorra a este quando um usuário relatar que um site não carrega — uma regra errada e um site indisponível parecem idênticos externamente.
O servidor detecta o IP da LAN, recusa iniciar se lan-detect disser que o bypass está quebrado, reutiliza um proxy existente se já estiver ouvindo, e desliga o que iniciou quando o MCP é encerrado. Seus próprios logs vão para stderr; stdout é mantido limpo para JSON-RPC.
Ele usa a porta 8900, deliberadamente diferente da 8899 usada para testes de backend acima. A reutilização é apenas por porta, então compartilhar o número permitiria que um proxy de backend que vincula tudo capturasse silenciosamente o navegador e roteasse todo o seu tráfego pela ISP.
Navegação mista funciona em um único navegador. --lan-hosts faz o proxy decidir por host: hosts correspondentes saem pela ISP, todo o resto permanece não vinculado e segue o túnel. Assim, "verificar hoosha e youtube" não requer alternância de ferramentas — hoosha sai pela ISP, youtube pela VPN, mesma sessão. O log do proxy mostra a decisão em cada linha:
CONNECT lan hoosha.com:443
CONNECT vpn youtube.com:443
Mantenha a lista precisa: um host doméstico ausente dela recebe o túnel e falha com uma resposta vazia, e um host estrangeiro adicionado erroneamente recebe a ISP e pode ser filtrado. Quando um site falha de uma forma que parece uma conexão morta, verifique qual rota o log escolheu antes de assumir que o site está indisponível.
Por que uma lista explícita em vez de detecção automática: conectar-se a um host bloqueado através do túnel é bem-sucedido no nível TCP (medido: connect@14ms) e morre apenas ~860ms depois, após o proxy já ter respondido 200 Connection Established. Fazer fallback significaria armazenar em buffer e reproduzir o handshake TLS e pagar esse atraso em cada requisição doméstica.
Playwright roteia loopback através do proxy. O Chrome normalmente ignora localhost, mas o Playwright substitui isso quando um proxy está configurado, então http://127.0.0.1:3000 chega em lan-proxy.mjs em vez de ir direto. Verificado no log do proxy. O guardião de loopback do proxy é o que mantém os servidores de desenvolvimento locais acessíveis — sem ele, cada uma dessas requisições falha com EADDRNOTAVAIL. Não adicione --proxy-bypass=<-loopback>; isso não muda nada aqui e apenas confunde o quadro.
Solução de problemas
directEgress igual a lanEgress com um túnel ativo. A VPN está impondo um kill-switch ou rota estrita (um filtro WFP que bloqueia soquetes vinculados). Corrija no cliente VPN, não ao redor dele: desabilite rota estrita (strict route), ou adicione uma regra de roteamento direta/bypass para os hosts necessários. nekoray/sing-box: desligue strict_route, ou adicione uma regra de saída direct.
No Linux isso também pode acontecer sem kill-switch: um endereço de origem sozinho não substitui a tabela de roteamento lá como faz no Windows, então o roteamento por política mantém o tráfego no túnel. A correção é uma regra enviando tráfego do IP da LAN para fora pelo gateway físico — ip rule add from <lanIp> table <n> mais uma rota padrão nessa tabela. Isso requer root e é uma alteração em todo o sistema, então explique e deixe o usuário decidir. Todo o resto neste skill permanece sem privilégios e com escopo de processo.
fakeIpDns: true. O resolvedor do SO está retornando placeholders 198.18.x.x que só significam algo dentro do túnel. Passe --dns <lanDns> para o proxy para que ele resolva através do DNS da própria LAN.
Sonda de egresso não retorna nada. Nenhuma conectividade com o host da sonda. Verifique o link, execute novamente.
Válvula de escape
Se um backend genuinamente não puder ser proxyzado (um binário compilado sem suporte a proxy), a única opção restante é uma rota estática específica para o host:
route add <targetIp> mask 255.255.255.255 <gateway> metric 1 # precisa de admin
route delete <targetIp> # desfazer, sempre
Isso é global. Puxa todo processo para fora do túnel para aquele destino — incluindo você, se você algum dia contatar o mesmo host. Requer elevação, sobrevive até ser deletado, e é fácil de esquecer. Pergunte antes de usá-lo, e delete-o na mesma sessão.