npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sshepherd

v0.0.10

Published

Framework de agentes que leva um dev sem experiencia de sysadmin do zero absoluto ate uma aplicacao publicada em VPS proprio, com servidor endurecido, deploy versionado e rollback em um comando.

Readme

SSHepherd

Do "não tenho servidor" ao "publiquei e sei voltar atrás", sem virar sysadmin.

SSHepherd é um framework de agentes que instala na pasta da sua aplicação e cuida do seu VPS: escolha do provedor, criação da máquina, chave SSH, endurecimento, provisionamento, deploy versionado, rollback e diagnóstico.

Ele foi feito para uma pessoa específica: quem sabe programar e não sabe administrar servidor.

Instalação

cd sua-aplicacao
npx sshepherd install

Depois, abra seu agente (Claude Code, Codex ou compatível) e digite:

/sshp

Se você ainda não tem servidor, ele começa pelo Dia Zero e te acompanha até o SSH funcionar.

Requisitos: Node 18.20.2 ou superior, cliente OpenSSH (nativo em Windows 10+, macOS e Linux). O alvo remoto precisa ser Ubuntu 22.04/24.04 ou Debian 11/12.

Comandos do instalador

npx sshepherd install     # instala nesta pasta
npx sshepherd update      # atualiza agentes e scripts, preservando seus dados
npx sshepherd status      # estado da instalação, do Dia Zero e dos servidores
npx sshepherd uninstall   # remove, com opção de preservar perfis e logs

Engines suportadas

Claude Code, Codex, Cursor, Gemini CLI, Windsurf, Antigravity, Kiro, Opencode, Cline, Roo Code, GitHub Copilot, Aider e Amazon Q Developer. O instalador detecta quais estão presentes — pela pasta do projeto ou pelo binário no PATH — e já vem com elas marcadas.

Aviso de nova versão

A instalação registra um hook SessionStart no Claude Code que roda .sshepherd/bin/version-notice.js. Ele lê só o cache local, sai em ~130ms e nunca bloqueia a sessão; a checagem de rede acontece em processo destacado, valendo para a sessão seguinte. Quando há versão nova, o agente pergunta se pode atualizar — nunca atualiza sozinho. Nas engines sem hook, a mesma instrução vai no entry file.

O que ele faz

| Comando | O que faz | |---|---| | /sshp | Descobre em que ponto você está e roteia | | /sshp-setup | Dia Zero: provedor → máquina → chave → usuário não-root → servidor fechado | | /sshp-connect | Perfis de servidor, teste de conexão, histórico de auditoria | | /sshp-harden | Auditoria de segurança com nota 0–100 e correções | | /sshp-attack | Olha seu servidor do lado de quem ataca: o que responde e o que vaza | | /sshp-provision | Detecta sua stack e prepara o servidor | | /sshp-secrets | Chaves de API e senhas no cofre do servidor, sem passar pelo chat | | /sshp-deploy | Publica, verifica saúde, volta versão | | /sshp-doctor | Diagnóstico quando algo para de funcionar | | /sshp-agents-help | Catálogo dos agentes |

O que sobe para o servidor

Sua pasta tem coisa que é sua e não do servidor: especificação, prompt de agente, material interno, planilha de preço. Mandar a pasta inteira transforma tudo isso em arquivo público.

Quem decide é o .sshepherdignore na raiz do projeto, criado na instalação, uma regra por linha no formato do rsync:

.env                # segredo
.reversa/           # frameworks de IA: especificacao e prompt
_reversa_*/
.claude/
.sshepherd/
.git/               # historico carrega segredo ja removido
node_modules/       # reconstruido no servidor
/aula/              # seu: material interno

Três coisas:

  • O Dia Zero termina aqui. O estágio 8 do /sshp-setup lista sua raiz, compara com o arquivo e pergunta item por item o que ele não tem como saber — aula/ é parte do site ou é seu material? Fecha mostrando os dois lados, o que vai e o que fica.
  • Existe um piso que você não derruba. .env, .env.*, .git/, .sshepherd/, *.pem, *.key, id_rsa, id_ed25519 são excluídos pelo script sempre, apagar a linha não adianta. Sem o arquivo, o deploy recusa publicar.
  • Na estratégia git ele não vale. O servidor clona do repositório, então quem manda é o .gitignore. Se .reversa/ está versionado, ele chega no servidor. Os agentes avisam antes.

Chaves e segredos

A chave da OpenAI, a senha do banco, o token do Stripe. O risco com um agente de IA operando o servidor não é só alguém de fora: é a chave entrar na conversa, por acidente ou por instrução injetada num log, e dali para onde a conversa for. O SSHepherd é desenhado para que o valor de um segredo nunca entre no contexto do modelo, e isso é feito em camadas que não dependem umas das outras:

  • No servidor, o cofre. /var/lib/sshepherd/secrets/app.env, root:root 600, dentro de uma pasta 700. A aplicação roda como um usuário próprio, sshepherd-app, sem senha e sem SSH, e recebe as variáveis do systemd, que lê o cofre antes de trocar de usuário. O usuário deploy, que é o que o agente usa, não lê o arquivo: só consegue chamar sshepherd-secrets, um executável de root com subcomandos fechados, pela única regra de sudo que recebe para isso. Ele responde com nomes, nunca com valores.
  • A chave entra pelo teclado da pessoa. secrets.sh --action set abre uma sessão ssh -t no terminal dela e o helper pede o valor duas vezes, sem eco. Rodado pelo agente, sem terminal, o script imprime o comando e sai. Não existe --value, não existe get.
  • Segredo que não precisa existir em outro lugar nasce no servidor. generate cria chave de sessão e senha de serviço com /dev/urandom; a senha do banco já vem assim do db.sh. O valor nunca viaja.
  • Aplicar é transacional. apply --health reinicia o serviço, espera ele ficar ativo e estável, bate no endereço de saúde. Com saúde, o cofre vira "o último que funcionou". Sem saúde, volta para o anterior, reinicia de novo e diz apply=revertido. Uma reversão, nunca um laço; lock contra operação concorrente; toda troca por temporário e mv, sem instante sem cofre.
  • Comando livre é de pessoa. ssh-exec.sh --raw e --op X --arg exigem o identificador do perfil digitado no teclado, em qualquer ambiente e com qualquer política. Shell livre no servidor é o que tornaria todo o resto inútil, e regex de proibição não segura shell.
  • Na máquina local, o hook. Além de ssh cru, ele nega Read, Grep, Edit, Write e Bash em .env, .env.*, chave privada, .npmrc, credentials.json, e nega escrita em .sshepherd/scripts/. .env.example passa, porque não tem valor.
  • O que sai do servidor passa pela redação. redact.sh, compartilhado por executor, deploy, db e secrets: nome por sufixo (_KEY, _SECRET, _TOKEN, _PASSWORD, _URL), valor entre aspas, credencial em URL, formatos conhecidos (sk-, ghp_, AKIA, JWT), chave privada em várias linhas. Redige antes de cortar o resumo do log. É a última rede, não a primeira.
  • Backup não leva segredo. O cofre não entra no backup, o .env de compose é excluído, e pull só traz arquivo com nome de backup, só com teclado, só para fora da pasta do projeto.

O fluxo para a pessoa: /sshp-secrets mostra o que existe e o que falta (comparando com o .env.example), entrega o comando para cadastrar, gera o que pode ser gerado, aplica com reinício verificado e confere.

Onde o cofre não chega

  • Código que roda com os segredos pode imprimi-los. Quem publica código que faz console.log(process.env) vê tudo, em base64 se quiser, por fora de qualquer redação. O cofre protege contra leitura direta e vazamento acidental; o código que sobe é responsabilidade de quem aprova o deploy.
  • Compose fica fora. A unidade de compose roda o YAML da release, e YAML de quem publica rodando como root seria root para quem publica. Em compose os segredos continuam em shared/.env, legíveis por deploy.
  • O provisionamento ainda precisa de root por sudo -n bash -s, e o sudoers que o framework gera não concede isso. Num servidor novo, o cofre é instalado no Dia Zero, na sessão de root que o framework já usa. Num servidor antigo, secrets-install.sh --print imprime o comando para colar no console do provedor como root. Se você liberou sudo amplo para deploy para o provisionamento funcionar, a fronteira do cofre passa a depender do teclado no comando livre e das lombadas no --build.
  • Teclado é proteção operacional, não autenticação. Um agente com shell local consegue, em tese, abrir um pseudoterminal. O que segura de verdade é o cofre pertencer a root.
  • O hook existe no Claude Code. Nas outras engines a camada local é a instrução no agente.

O hacker do bem

/sshp-harden pergunta ao servidor como ele está configurado. /sshp-attack não pergunta nada: bate na porta e vê o que responde.

As duas coisas divergem mais do que parece. Firewall ativo e configurado certo, com o Postgres aberto para a internet do mesmo jeito — porque o Docker publica porta por cima do ufw e a regra do firewall não vale para ela. O harden dá nota alta. O attack mostra o banco exposto.

Três lentes:

  1. De fora, sem chave — que portas respondem pela internet. Banco de dados aberto é o achado mais comum e o mais grave; Redis e Mongo abrem sem senha por padrão.
  2. O que a aplicação entrega — /.env, /.git/HEAD, docker-compose.yml, dump de banco, e a documentação do projeto servida como página.
  3. De dentro — permissão do arquivo de segredos, material interno publicado junto com o código, socket do Docker acessível, sudo sem senha.

E o vetor que é novo: a varredura ficou barata. Mapear um alvo era trabalho de horas de gente com paciência; hoje roda sozinho contra milhares de endereços ao mesmo tempo. Por isso um _reversa_sdd/prd.md exposto deixou de ser inofensivo — ele descreve sua regra de negócio em texto escrito para ser entendido. Era papel, virou mapa.

A base de ameaças

O que procurar não é lista fixa — uma lista de dois anos atrás procura buraco que já foi tapado e ignora o que está sendo usado agora. A base fica em .sshepherd/threats.db, uma linha por ameaça, legível a olho nu:

caminho|/.mcp.json|critica|conexoes do seu assistente de IA|Guarda os tokens...|<fonte>|2026-08-06
porta|9000|alta|painel do Portainer|Quem entra ali manda na maquina.|<fonte>|2026-08-06

O agente atualiza pesquisando na web, filtrando pela stack do projeto, e sempre mostra antes de gravar — com a fonte de cada item. Reinstalar não apaga o que ele aprendeu. Quando a base passa de 60 dias, o script avisa.

Só entra o que dá para detectar pedindo um endereço ou batendo numa porta. Nada que explore.

Os limites

Só examina servidor registrado em .sshepherd/servers/, e recusa qualquer outro endereço mostrando quais são os seus. Não explora, não tenta senha, não mede carga, não altera nada.

O pastor acha o buraco na cerca e chama o dono. Não atravessa, e não vai olhar a cerca do vizinho.

O Dia Zero

É a parte que os tutoriais não cobrem e onde a maioria desiste. O agente conduz do zero absoluto, aceita print de tela do painel do provedor para saber onde você está, e valida cada passo por comando — não por "parece que deu certo".

Termina quando cinco condições estão comprovadas:

  1. você entra por chave, como usuário que não é root
  2. login de root pela rede desligado
  3. login por senha desligado
  4. firewall ativo negando entrada
  5. atualizações automáticas de segurança ligadas

Provedores cobertos em detalhe: DigitalOcean, Hostinger, Contabo, Hetzner. Outros recebem o roteiro genérico.

Arquitetura

Três camadas, separadas de propósito:

 npm (bin/, lib/)      →  só instala e atualiza. Não toca em servidor.
 agents/ (markdown)    →  decidem, conversam, julgam contexto.
 scripts (.sshepherd/) →  executam no servidor. Determinísticos e auditáveis.

O agente é bom em julgar contexto e ruim em garantir determinismo. O script é o oposto. Por isso nenhum comando remoto é improvisado pelo modelo: tudo passa por script versionado, que você pode ler antes de rodar.

A trava que faz essa regra valer

Regra escrita no prompt não segura ferramenta. Bastava o agente chamar ssh direto e a camada de controle ficava de fora, sem deixar rastro. Por isso a instalação registra um hook PreToolUse (.sshepherd/bin/guard-hook.js) que recusa ssh, scp, sftp, rsync, ssh-keyscan, ssh-copy-id e ssh-add crus antes de rodarem. Ele quebra a linha nos operadores, inclusive $( e crase, e julga cada trecho sozinho, então esconder o comando depois de um && ou dentro de uma substituição não passa. Um trecho só é "do framework" quando a palavra de comando é bash, sh ou powershell seguida do caminho em .sshepherd/scripts/: o caminho num comentário não libera nada. O mesmo hook nega leitura e escrita de arquivo de segredo local e escrita em .sshepherd/scripts/. Evento ilegível é recusado, não liberado.

Dentro do executor (ssh-exec.sh), quatro freios sustentam o resto:

  • Risco só sobe, nunca desce. Cada operação tem um nível (leitura, alteração, destrutivo) e quem chama pode declarar um nível maior, jamais menor. Comando cru entra com piso alteração.
  • Destrutivo em produção exige teclado. --confirmed é afirmação de quem chama, e quem chama pode ser um agente que alucinou a confirmação. O script abre /dev/tty e espera uma pessoa digitar o identificador do perfil. Sem terminal ligado, ele recusa.
  • Comando livre exige teclado, sempre. --raw e --op X --arg são shell arbitrário no servidor. O agente roda as operações fixas (whoami, osinfo, disk, releases, logs) e os scripts; o resto a pessoa roda, e o script diz isso com o comando pronto.
  • Sudoers sem curinga. O usuário deploy recebe sudo sem senha só para unidades nomeadas, uma lista fixa de pacotes e o helper do cofre, porque systemctl restart * aceitaria stop sshd.service e journalctl * abre shell de root pelo paginador.

Onde essa camada ainda não chega

Sendo honesto sobre o estado atual, porque num framework de segurança a diferença entre o que está pronto e o que está planejado importa mais que a lista de recursos:

  • Os scripts operacionais ainda não passam pelo executor. deploy, harden, provision, db, restore, rollback e backup abrem ssh direto. São scripts versionados, que você lê antes de rodar, e o hook os reconhece. Mas a classificação de risco, o portão de confirmação e o log de auditoria vivem no ssh-exec.sh, e ligar essas operações a ele é trabalho em aberto. Enquanto não estiver feito, o log cobre o que passa pelo executor, não toda operação remota.
  • O hook enxerga a linha de comando. Um agente que escreve um script local com ssh dentro e executa esse script passa por fora (desde que o script não esteja em .sshepherd/scripts/, onde ele não consegue mais escrever); ali quem segura é o sudoers restrito. Para arquivo de segredo vale o mesmo: cat $(ls -a | grep env) passa pelo hook, e o que segura é o cofre pertencer a root no servidor.
  • O hook é registrado no settings.json do Claude Code. Nas outras engines a camada de controle depende da instrução no agente e do sudoers.
  • A paridade com PowerShell é parcial. O ssh-exec.ps1 existe, os demais scripts ainda não têm contrapartida, e ele não lê o config.toml.

As regras que o framework não quebra

  1. Nada muda sem você ver o comando. Toda operação que altera o servidor mostra o comando exato e o alvo antes, e espera confirmação. Leitura e diagnóstico rodam livres.
  2. Comandos destrutivos são recusados sempre. Apagar o disco, parar o SSH, resetar o firewall — não há confirmação que libere.
  3. Nenhum caminho de acesso é fechado antes de o novo estar provado. É o que impede você de ficar trancado para fora do próprio servidor.
  4. A chave privada nunca é lida. O framework passa o caminho ao cliente SSH, nunca o conteúdo.
  5. Tudo fica registrado. Cada comando remoto vai para .sshepherd/logs/, com segredos redigidos — inclusive o que foi recusado ou bloqueado.
  6. root é usado uma única vez. No primeiro acesso, para criar o usuário deploy e instalar o cofre. Depois disso, nunca mais.
  7. O valor de um segredo nunca entra na conversa. Nem pedido, nem aceito, nem lido de .env. Vai do teclado da pessoa para o cofre do servidor, e o agente só vê nomes.

Deploy e rollback

Publicação em releases/<timestamp> com troca atômica do symlink current:

/srv/app/
├── releases/20260805143000/     ← nova (só leitura para a aplicação)
├── releases/20260805120000/     ← anterior, intacta
├── shared/                      ← sobrevive a todas as versões: logs, uploads, --shared-dir
└── current -> releases/20260805143000

/var/lib/sshepherd/secrets/app.env   ← o cofre, root:root 600; a aplicação recebe pelo systemd

Depois de publicar, o framework bate na porta da aplicação até 6 vezes. Se não responder, mostra o log e oferece o rollback como primeira ação. Voltar leva segundos, porque o código antigo já está no servidor.

O que ele não faz

  • Não cria a VPS pela API do provedor — acompanha você no painel
  • Não registra domínio
  • Não administra banco de dados (backup, migração, tuning)
  • Não roda migração de esquema, e o rollback não desfaz banco
  • Não deixa processo rodando no servidor
  • Não orquestra múltiplos servidores, nem Kubernetes

Estrutura instalada

seu-projeto/
├── .sshepherdignore         o que NAO sobe para o servidor (editavel)
├── .sshepherd/
│   ├── state.json           estado e estágio do onboarding
│   ├── config.toml          preferências (editável)
│   ├── servers/<id>.json    um arquivo por servidor
│   ├── logs/audit-*.jsonl   histórico, um evento por linha
│   ├── scripts/sh|ps/       os scripts que executam de fato (secrets.sh, deploy.sh, ...)
│   └── bin/
│       ├── guard-hook.js      recusa ssh cru, .env local e escrita nos scripts (Node puro)
│       └── version-notice.js  aviso de nova versão (Node puro)
├── .claude/skills/sshp*/
└── .agents/skills/sshp*/

servers/ e logs/ já vêm no .gitignore local: contêm endereço de servidor e histórico operacional.

Desenvolvimento

npm test              # 108 testes, executor nativo do node, sem framework de teste
npm run bump:check    # confere se package.json e os 10 agentes estão na mesma versão
npm run bump          # 0.0.2 -> 0.0.3, atualiza os 11 arquivos de uma vez

Os testes cobrem o detector das 13 engines, a estrutura instalada, preservação de dados em reinstalação, estado corrompido, os hooks (criação, merge, não-duplicação, settings inválido, ampliação do matcher antigo), o guard-hook (recusa de binário remoto cru, trecho escondido depois de operador ou dentro de substituição, caminho do framework só como palavra de comando, arquivo de segredo local, escrita nos scripts, evento ilegível), o manifesto SHA-256, created_files, o .sshepherdignore (criação, não-sobrescrita, recusa do deploy sem ele), os guard rails, o cofre (helper em modo de teste, bloco de instalação, redação, argumentos fechados, comando livre sem teclado) e a ausência de CRLF nos scripts.

Versionamento

Só o último número muda. 0.0.1 → 0.0.2 → 0.0.3 → ...

Sem major, sem minor. A versão aparece em package.json e no metadata de cada um dos 10 agentes — npm run bump mantém os onze em sincronia, e npm run bump:check acusa se algum ficou para trás.

Fluxo de release:

npm run bump                          # incrementa
# registre a mudança no CHANGELOG.md
git commit -am "v0.0.7: <o que mudou>"
git tag v0.0.7 && git push --follow-tags

Status

Versão 0.0.8. A 0.0.6 foi executada de ponta a ponta contra um VPS real, do Dia Zero ao deploy. O cofre da 0.0.8 foi validado por teste (helper em modo de teste, bloco de instalação decodificado e conferido, hook, executor, redação, argumentos) e por duas rodadas de revisão adversarial com um segundo modelo; a parte que roda como root no servidor (usuário de serviço, unidade do systemd, sudoers, migração) ainda não foi exercitada contra máquina real. Use uma VPS descartável antes.

Validado por teste: instalador, comandos, os dois hooks, o guard-hook (ssh cru, trecho escondido depois de operador ou em substituição, caminho do framework só como palavra de comando, arquivo de segredo local, escrita nos scripts, evento ilegível), guard rails (bloqueio incondicional, redação de segredo, recusa sem confirmação, piso de risco que só sobe, confirmação de teclado no destrutivo em produção e no comando livre, sudoers sem curinga, trava de alvo do attack), o cofre, sintaxe e fim de linha dos scripts, 108 testes.

Do attack-surface.sh, a sondagem externa e a detecção de arquivo exposto foram exercitadas contra um servidor local montado para isso: portas, /.env, /.git/HEAD, listagem de diretório e banner de versão disparam corretamente. A parte que roda por SSH não foi validada contra máquina real.

Ainda é projeto jovem: um servidor por vez, quatro provedores cobertos em detalhe, e só Ubuntu 22.04/24.04 e Debian 11/12. Use uma VPS descartável antes de apontar para produção. Os únicos que só leem, e portanto não alteram nada, são o audit.sh e o attack-surface.sh.

Uma varredura de conformidade contra a spec, feita em 2026-08-28, reproduziu sete defeitos rodando o código. Cinco foram corrigidos na 0.0.7, cada um com o teste que falha sem a correção. Os dois que sobraram são estruturais e estão descritos acima, em "onde essa camada ainda não chega". O registro completo, com mais 28 achados de menor confiança, fica em _reversa_bugs/ no repositório e não vai no pacote do npm.

Licença

MIT — sandeco