seashell

Servidor MCP que conecta o Claude Desktop ao Wave Terminal

Documentação

SeaShell logo

SeaShell

Retome a sessão do Claude Code de ontem à noite a partir de qualquer terminal, pelo nome.

Licence: MIT Swift Stars M-Pineapple/seashell MCP server

Um servidor MCP + kit de ferramentas de shell que conecta o Wave Terminal, o Claude Desktop e o Claude Code — transformando o trabalho com múltiplas abas de terminal em codificação colaborativa com IA sem interrupções.

Início rápido · Instalação · Comandos · Limitações · FAQ

Listing named Claude Code sessions, then hey continue with myapp resuming last night's conversation

Somente macOS. O SeaShell é feito para macOS (Swift, APIs Darwin) com Wave Terminal e Claude Desktop / Claude Code. Ele não compila nem executa em Linux ou Windows.

Início rápido

# 1. Clone and build
git clone https://github.com/M-Pineapple/seashell ~/Github/seashell
cd ~/Github/seashell && ./build.sh

# 2. Connect Claude and install `hey`
#    Register the MCP server (Desktop config and/or `claude mcp add`. See Installation.)
cd examples/wave-config && ./install.sh
claude auth login

# 3. Resume a session
hey continue with <some-project>

Os hooks, o daemon opcional e o hey what's the latest in <project>? estão em Instalação.

O que é

O SeaShell é um servidor Model Context Protocol (Swift) mais um pequeno conjunto de comandos de shell que juntos oferecem três coisas:

  1. Mensagens assíncronas entre terminais. Deixe uma nota para o Claude a partir de qualquer bloco do Wave Terminal; receba a resposta em outro. seashell-msg, seashell-ask.
  2. Retome qualquer sessão do Claude Code pelo nome. Saia de uma sessão de codificação profunda no modo Code do Claude Desktop ontem à noite, abra o Wave hoje de manhã, digite hey continue with <project> — mesma conversa, mesma memória. hey, seashell-sessions.
  3. Respostas autônomas opcionais via daemon. Sem que você toque em um chat, um pequeno daemon inicia o claude -p no diretório correto do projeto e responde à sua caixa de entrada. Usa sua assinatura do Claude — sem chave de API.

Os três recursos se combinam. Você pode misturá-los e combiná-los.

✨ Recurso principal: hey continue with <project>

$ hey continue with myapp

🔄 Resuming session a1b2c3d4 (project: myapp)...

> What's the latest update on the auth refactor?

We finished extracting the AuthService. Tests pass. Next is wiring it
into the API layer — I have a draft in routes/auth.py on line 142.
Want me to walk through it?

Isso é um terminal novo. Mesmo histórico de conversa da sessão do modo Code do Claude Desktop de ontem à noite. Memória completa do projeto. Correspondência difusa — hey continue with ma também funciona.

Cada projeto tem uma sessão primária fixada em <project>/.seashell-inbox/primary-session.txt, então hey continue with <project> sempre leva você à mesma conversa — mesmo se você tiver iniciado várias sessões paralelas para o mesmo repositório. A fixação é definida automaticamente na primeira vez que você seashell-init (a maior sessão existente vence) ou na primeira inicialização do claude naquele diretório. Re-fixe qualquer sessão a qualquer momento com seashell-sessions promote <id>.

Instalação

1. Instale o Wave Terminal

Baixe o Wave Terminal e abra-o uma vez.

2. Instale o Claude Desktop e/ou a CLI do Claude Code

Você vai querer pelo menos um destes:

  • Claude Desktop — o aplicativo GUI de claude.ai/download. Use para conversas gerais. O modo "Code" aqui persiste sessões em disco e PODE ser retomado pelo SeaShell.
  • CLI do Claude Code — instale via npm: npm install -g @anthropic-ai/claude-code. É isso que o hey continue with realmente invoca (via claude --resume <id>).

A maioria dos usuários quer ambos.

3. Compile o SeaShell

git clone https://github.com/M-Pineapple/seashell ~/Github/seashell
cd ~/Github/seashell
./build.sh

build.sh produz .build/release/seashell (~16 MB).

4. Registre o servidor MCP no Claude

Você precisa registrar o SeaShell em todos os clientes Claude que usar. São registros separados.

a) Claude Desktop

Edite ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "seashell": {
      "command": "/absolute/path/to/seashell/.build/release/seashell",
      "args": ["--port", "9876"]
    }
  }
}

Reinicie o Claude Desktop. As mais de 60 ferramentas do SeaShell aparecem na lista de ferramentas disponíveis.

b) CLI do Claude Code

claude mcp add --scope user seashell \
    /absolute/path/to/seashell/.build/release/seashell -- --port 9879

A porta diferente (9879) evita conflito se você executar invocações do Desktop e da CLI em paralelo. --scope user significa que o registro é global — visível de qualquer diretório.

5. Entre na sua assinatura do Claude

claude auth login

Isso abre um navegador para OAuth na sua conta do Claude.ai. Nenhuma chave de API necessária — claude -p (e o daemon opcional) usa sua assinatura Pro/Max por meio dessa autenticação.

6. (Opcional, mas recomendado) Instale o pacote de exemplo

cd ~/Github/seashell/examples/wave-config
./install.sh

Isso cria um snapshot das suas configurações atuais do fish + Wave em ~/.seashell-backups/<timestamp>/ e depois instala:

  • ~/.local/bin/seashell-msg, seashell-ask, seashell-init
  • ~/.local/bin/hey, seashell-sessions, seashell-setup-hooks
  • ~/.local/bin/seashell-session-start, seashell-post-tool-use (hooks do Claude Code)
  • ~/.config/fish/ (uma configuração funcional do fish com roteamento em linguagem natural, atuin, fastfetch, sincronização de tema)
  • ~/.config/fastfetch/config.jsonc
  • ~/Library/LaunchAgents/dev.seashell.theme-sync.plist (claro/escuro automático para o Wave)

7. (Opcional) Conecte os hooks do Claude Code

seashell-setup-hooks

Adiciona duas entradas ao ~/.claude/settings.json:

  • Hook SessionStart — registra cada REPL interativo do claude em ~/.seashell/sessions/<id>.json com rótulo do projeto e ID do bloco do Wave
  • Hook PostToolUse — anexa cada chamada de ferramenta ao ~/.seashell/sessions/<id>/activity.log e atualiza um ponteiro de "arquivo atual" quando o Claude faz Edit/Write

Os hooks disparam apenas para sessões TTY interativas (um REPL real do claude dentro de um bloco do Wave). Eles NÃO disparam para invocações do claude -p. O comando seashell-sessions do SeaShell funciona independentemente disso, porque recorre à varredura de ~/.claude/projects/.

8. (Opcional) Instale o daemon autônomo

cd ~/Github/seashell/examples/seashell-daemon
./install.sh

O daemon verifica a caixa de entrada de cada projeto a cada 5 segundos. Quando algo chega, ele inicia o claude -p a partir do diretório do projeto para ler e responder. Usa sua assinatura do Claude via autenticação da CLI do passo 5.

Veja examples/seashell-daemon/README.md para detalhes.

Comandos que você usará todos os dias

hey — ponto de entrada em linguagem natural

hey continue with myapp                        # resume project's latest session
hey resume ma                                  # fuzzy match (initials work)
hey let's work on the seashell project        # natural language continuation
hey what's the next thing on the refactor?    # routes to inbox (waits for reply)
swift build 2>&1 | hey "fix these errors"     # pipe + ask

Se as primeiras palavras parecerem uma intenção de retomar sessão (continue, resume, let's work on, go back to, pick up), o hey executa claude --resume <id>. Caso contrário, ele cai para seashell-ask.

seashell-sessions — veja suas sessões do Claude Code

seashell-sessions                  # list everything
seashell-sessions latest <name>    # print the most recent session id matching <name>
seashell-sessions primary <name>   # print the project's pinned primary session id
                                   #   (falls back to latest if none pinned)
seashell-sessions promote          # pin most-recent session as primary for $PWD
seashell-sessions promote <id>     # pin a specific session (project inferred from its cwd)
seashell-sessions show <id>        # full metadata for one session
seashell-sessions resolve <name>   # fuzzy-match and print the best id

Lê de TRÊS fontes:

  • ~/.seashell/sessions/ — sessões registradas por hooks (marcador ★ em list)
  • ~/.claude/projects/<encoded-cwd>/<id>.jsonl — descobertas no sistema de arquivos (todas as sessões, todos os projetos)
  • ~/Library/Application Support/Claude/claude-code-sessions/<account>/<bridge>/local_*.json — wrappers de sessão do Claude Desktop, que contribuem com os títulos amigáveis do Desktop e o sinalizador de arquivado

Essa leitura de três fontes significa que a correspondência difusa também funciona com títulos do Desktop: hey continue with the current trader pro encontrará uma sessão intitulada Current Trader Pro Development Session no Desktop, mesmo que o nome base do diretório de trabalho em disco (ex.: Github) não corresponda. Sessões arquivadas recebem um marcador A em list e são excluídas da correspondência difusa por padrão — para que um experimento arquivado há muito tempo não possa ser ressuscitado por acidente.

A sessão primária fixada fica em <project>/.seashell-inbox/primary-session.txt e é o que o hey continue with <project> retoma.

seashell-msg — deixe uma nota para o Claude

seashell-msg "compare the test output to what we discussed"
swift build 2>&1 | seashell-msg "fix these errors"
seashell-msg --file /tmp/build.log "investigate this"
seashell-msg --global "skip cwd routing"

Roteia automaticamente para o ancestral .seashell-inbox/ mais próximo; recorre a ~/.seashell/inbox.jsonl.

seashell-ask — pergunta bloqueante

seashell-ask "what's the status of the refactoring?"      # blocks up to 600s
seashell-ask --timeout 60 "did the build pass?"           # custom timeout
seashell-ask --no-wait "post and exit"                    # fire-and-forget with reply token

Publica uma mensagem com um reply_token, verifica se há uma resposta correspondente em replies.jsonl, imprime quando encontra.

seashell-mirror-mcp — mantenha os servidores MCP da CLI e do Desktop sincronizados

seashell-mirror-mcp --dry-run     # preview what would be added (env values redacted)
seashell-mirror-mcp --list        # just print the diff
seashell-mirror-mcp               # apply: clone Desktop MCP entries to the CLI (additive)
seashell-mirror-mcp --update      # force re-sync EVERY Desktop server (picks up rotated tokens,
                                  # env-var changes, command edits). Removes + re-adds each entry.
                                  # Doesn't touch CLI-only servers (e.g. seashell).

O Claude Desktop e a CLI do Claude Code usam arquivos de configuração separados para registro do servidor MCP (claude_desktop_config.json vs ~/.claude.json), mesmo compartilhando o mesmo armazenamento de sessão .jsonl. Então, uma sessão retomada via hey continue with <project> pode estar sem ferramentas que o Desktop tem — ex.: você pode perguntar ao Claude sobre Trello no Desktop, mas a sessão CLI retomada não tem MCP do Trello. Este script espelha Desktop → CLI (somente aditivo, nunca remove), fazendo a superfície de ferramentas corresponder. Idempotente e seguro para reexecutar sempre que você adicionar um novo servidor no Desktop.

A execução de teste (dry-run) redige valores de env para chaves que parecem segredos (qualquer coisa que corresponda aos componentes KEY, TOKEN, SECRET, PASSWORD, API, AUTH, CREDENTIAL, PAT). As chamadas reais do claude mcp add usam os valores reais da sua configuração do Desktop.

seashell-init — registre um projeto

cd ~/Github/your-project
seashell-init

Cria .seashell-inbox/, registra o projeto em ~/.seashell/projects.jsonl, escreve (ou estende) CLAUDE.md com uma dica de caixa de entrada.

Superfície de ferramentas MCP (mais de 60 ferramentas)

Agrupadas por nível. Usadas pelo Claude (no Desktop, em REPLs do claude, no daemon) — não digitadas por você.

Caixa de entrada (Nível A — seguro)

  • read_user_inbox, inbox_count, inbox_history, reply_to_user, read_my_replies

Espreitar entre sessões (Nível A — seguro)

  • read_session_transcript — leia os N turnos mais recentes da sessão primária de qualquer projeto (ou qualquer sessão por ID) sem retomá-la. Permite que o Claude Desktop responda "qual é o status do projeto X?" citando a transcrição real em vez de adivinhar.

Configuração direta do Wave (Nível A leitura / Nível B escrita)

  • wave_get_settings, wave_set_setting, wave_get_widgets, wave_create_widget, wave_update_widget, wave_delete_widget, wave_get_ai_presets, wave_set_ai_preset, wave_set_theme, wave_set_appearance, wave_get_backgrounds

Ferramentas de bloco auxiliar do Wave (Nível A leitura / Nível B escrita)

  • wave_list_workspaces, wave_list_blocks, wave_get_scrollback, wave_get_block_meta, wave_set_block_meta, wave_create_block, wave_delete_block, wave_run_in_block, wave_view_file, wave_edit_file

Segredos do Wave (Nível C — aprovação explícita necessária)

  • wave_secret_list, wave_secret_set, wave_secret_get, wave_secret_delete
  • Toda ferramenta de segredos precisa de approved=true e um reason não vazio. wave_secret_get retém o valor em texto puro, a menos que SEASHELL_ALLOW_SECRET_READ=1 esteja definido no ambiente do servidor.

Execução de comandos

  • execute_command, execute_with_auto_retrieve, execute_with_streaming, execute_pipeline, get_command_output, list_recent_commands

Modelos, perfis, ambiente

  • save_template, run_template, list_templates, save_workspace_profile, load_workspace_profile, list_workspace_profiles, get_environment_context, capture_environment, diff_environment

Veja docs/ARCHITECTURE.md para a tabela completa de níveis.

Limitações

Estas não são bugs — são realidades arquiteturais que vale a pena conhecer desde o início.

LimitaçãoPorquê
Você não pode direcionar um chat específico do Claude Desktop com seashell-msg / seashell-ask. O servidor MCP é compartilhado entre todos os chats do Desktop — não há ID de chat exposto ao MCP.A integração MCP do Claude Desktop executa um servidor MCP compartilhado por entrada de configuração, sem identificador por chat. A solução alternativa: use o daemon (Claude com escopo por projeto na caixa de entrada) ou use hey continue with <project> para retomar uma sessão do Claude Code.
A continuidade funciona apenas para sessões do Claude Code. O modo "Chat" e o modo "Co-work" do Claude Desktop não são retomáveis. O modo "Code" do Claude Desktop É — ele persiste no mesmo armazenamento ~/.claude/projects/ que a CLI.As transcrições de Chat e Co-work são internas ao Desktop. O modo Code compartilha o formato aberto .jsonl com a CLI. Use o modo Code se quiser continuidade entre dias e entre clientes.
Os hooks (SessionStart, PostToolUse) disparam apenas para sessões TTY interativas. Eles NÃO disparam para invocações claude -p.Este é um comportamento do Claude Code que aceitamos. O seashell-sessions do SeaShell recorre à varredura de ~/.claude/projects/ para que a retomada por nome funcione de qualquer forma. O log de atividades + blocos laterais só são preenchidos durante sessões interativas.
Os blocos laterais (cauda de atividade + pré-visualização de código ao vivo) só aparecem dentro do Wave Terminal. Eles usam wsh createblock, que requer WAVETERM_JWT.O hook verifica a variável de ambiente antes de invocar wsh. Quando você inicia claude fora do Wave (ex.: Terminal.app comum), os blocos laterais são silenciosamente ignorados; todo o resto continua funcionando.
As sessões claude -p do daemon autônomo não atualizam o chat aberto do Claude Desktop em tempo real.O Claude Desktop mantém seu próprio estado em memória. Novos turnos anexados ao .jsonl a partir de claude --resume -p só ficam visíveis depois que o Desktop atualiza/reabre a sessão. Se você tiver uma sessão aberta E o daemon gravar nela simultaneamente, o último a gravar vence no arquivo. Escolha um ou outro para ser "ao vivo".
Uso em alto volume pode atingir os limites de taxa da assinatura Claude.ai.O daemon agrupa um claude -p por projeto por ciclo de polling (padrão 5 s). Para uso típico, isso fica abaixo dos limites. Para cargas pesadas, aumente SEASHELL_DAEMON_POLL.
A lista de comandos bloqueados é uma proteção contra erros, não uma fronteira de segurança.Ela corresponde ao texto do comando, então um chamador determinado pode reformular ou codificar um comando. Conecte o SeaShell apenas a clientes em que você confia.
Sem sincronização entre máquinas da caixa de entrada ou do registro de sessões.Todo o estado fica em ~/.seashell/ e ~/.claude/projects/. Você pode criar symlinks para o iCloud Drive, mas isso não foi testado.

FAQ

Por que meu hook SessionStart não disparou?

Os hooks disparam apenas para sessões TTY interativas. Se você iniciou o Claude com claude -p "..." (não interativo) ou canalizou stdin para claude, o hook é ignorado. Execute claude normalmente dentro de um bloco do Wave Terminal para dispará-lo.

Qual é a diferença entre o modo "Code" do Claude Desktop e a CLI do Claude Code?

Eles usam o mesmo formato de arquivo de conversa (~/.claude/projects/<encoded-cwd>/<uuid>.jsonl). Qualquer um pode ler/gravar uma sessão. Então você pode:

  • Começar no modo Code do Desktop, retomar no Wave com hey continue with <project> ✅
  • Começar no REPL claude dentro do Wave, visualizar depois na barra lateral do modo Code do Desktop ✅
  • Alternar entre clientes na mesma conversa ✅

Você não pode retomar sessões dos modos "Chat" ou "Co-work" do Desktop — elas são armazenadas em outro lugar e não são expostas.

Por que dois registros de servidor MCP (um para Desktop, um para CLI)?

O Claude Desktop e a CLI do Claude Code mantêm configurações MCP separadas. O Desktop lê ~/Library/Application Support/Claude/claude_desktop_config.json; a CLI lê ~/.claude.json. Registramos em portas diferentes (9876 vs 9879) para que não colidam se ambos iniciarem o SeaShell ao mesmo tempo.

Qual é a diferença entre seashell-msg e hey?

  • seashell-msg "..." sempre envia para a caixa de entrada (fire-and-forget).
  • seashell-ask "..." envia e bloqueia aguardando uma resposta.
  • hey ... é mais inteligente: se o prompt parecer uma solicitação de retomada de sessão, ele claude --resume; caso contrário, age como seashell-ask.

Pense em hey como o guarda-chuva amigável; seashell-msg/seashell-ask são os primitivos de nível mais baixo.

O daemon custa dinheiro?

O daemon inicia claude -p, que usa sua assinatura Claude.ai (Pro ou Max), não a API. Sem cobrança por token. Você atinge os limites de taxa da sua assinatura, não a sua carteira. Se você não tiver uma assinatura, o daemon não funcionará — instale o Claude Code em claude.ai.

Posso usar isso sem o Wave Terminal?

Na maioria dos casos, sim. As ferramentas MCP funcionam em qualquer cliente. Os comandos de shell (hey, seashell-msg, etc.) funcionam em qualquer terminal. Os blocos laterais (cauda de atividade + pré-visualização de código ao vivo) exigem o wsh createblock do Wave — eles são silenciosamente ignorados se executados fora do Wave.

O que acontece se eu iniciar dois REPLs claude no mesmo diretório de projeto?

Cada um recebe seu próprio UUID, seu próprio arquivo .jsonl, sua própria conversa. Eles são independentes. hey continue with <project> retoma o mais recentemente ativo.

Como faço meu comando do Wave Terminal rodar autonomamente sem eu precisar verificar?

Instale o daemon (passo 8). Então qualquer seashell-ask de qualquer bloco será respondido por claude -p em cerca de 5 segundos, sem necessidade de chat.

Como desinstalo?

# Daemon
launchctl unload ~/Library/LaunchAgents/dev.seashell.daemon.plist
rm ~/Library/LaunchAgents/dev.seashell.daemon.plist
rm ~/.local/bin/seashell-daemon

# Theme sync
launchctl unload ~/Library/LaunchAgents/dev.seashell.theme-sync.plist
rm ~/Library/LaunchAgents/dev.seashell.theme-sync.plist

# Shell commands and hooks
rm ~/.local/bin/seashell-* ~/.local/bin/hey

# Hooks from Claude config (manual edit)
# Open ~/.claude/settings.json and remove the SeaShell entries from the "hooks" key

# State
rm -rf ~/.seashell

# MCP server registrations (manual)
# Remove "seashell" entry from ~/Library/Application Support/Claude/claude_desktop_config.json
# claude mcp remove seashell

# Restore old fish config
mv ~/.config/fish ~/.config/fish.removed
mv ~/.seashell-backups/<timestamp>/fish ~/.config/fish

Como posso ver o que o daemon está fazendo agora?

tail -f /tmp/seashell-daemon.log

Você verá cada ciclo de polling, cada claude -p iniciado e o tempo de resposta do Claude.

O daemon pode falar com ele mesmo? (ou seja, loop infinito)

O claude -p do daemon é restrito a quatro ferramentas: inbox_count, inbox_history, read_user_inbox, reply_to_user. Ele não pode postar novas mensagens na caixa de entrada — apenas consumir e responder.

O que faço se meu hook disparou errado / algo quebrou / quero redefinir?

Exclua ~/.seashell/sessions/<bad-id>/ e ~/.seashell/sessions/<bad-id>.json. O próximo claude interativo será registrado do zero. Seu histórico de ~/.claude/projects/ é preservado.

Por que minha caixa de entrada está mostrando mensagens que não enviei?

Provavelmente seu claude -p (ex.: do daemon) chamou read_user_inbox e ele retornou mensagens da caixa de entrada de um cwd diferente. O SeaShell agrega caixas de entrada de todos os projetos conhecidos + global por padrão. Use inbox_count para ver o detalhamento por projeto.

Onde reporto bugs?

Abra uma issue em github.com/M-Pineapple/seashell/issues.

Arquitetura e documentação

  • docs/ARCHITECTURE.md — Visão geral do sistema, camadas, superfície de ferramentas MCP, modelo de objetos do Wave
  • docs/INBOX.md — Layout de armazenamento, regras de roteamento, arquitetura do daemon, solução de problemas
  • docs/WAVE_INTEGRATION.md — Como o SeaShell fala com o Wave: wsh, WAVETERM_JWT, mecanismo RPC
  • docs/HELPER_PROTOCOL.md — Protocolo de rede entre o servidor externo e o helper dentro do Wave

Estrutura do projeto

seashell/
├── README.md                       ← you are here
├── LICENSE                         ← MIT
├── Package.swift                   ← Swift package manifest
├── build.sh / clean.sh             ← build scripts
├── setup.sh                        ← post-clone bootstrap
├── assets/logo.svg, demo.gif       ← logo + README demo (regenerate with docs/demo/record.sh)
│
├── Sources/
│   ├── Seashell/                   ← MCP server, tool handlers, Wave adapter
│   │   ├── InboxHandlers.swift     ← inbox tools (read/count/history/reply/read_my_replies)
│   │   └── …                       ← 30+ other handler files
│   └── ConfigManager/              ← standalone config-management binary
├── Tests/SeashellTests/
│
├── helper/seashell-helper          ← in-Wave Python helper (TCP proxy to wsh)
├── config/                         ← Claude Desktop config template
├── docs/                           ← architecture, INBOX, integration, helper protocol
│   ├── demo.tape                   ← VHS script for assets/demo.gif
│   └── demo/                       ← fixtures + record.sh
├── prompts/                        ← AI-assisted configuration prompts
│
└── examples/
    ├── wave-config/
    │   ├── bin/                    ← shell commands: hey, seashell-msg/ask/init/sessions/setup-hooks
    │   ├── hooks/                  ← Claude Code SessionStart + PostToolUse scripts
    │   ├── fish/                   ← starter fish stack
    │   ├── fastfetch/              ← system-info banner config
    │   ├── wave/                   ← Wave widgets/settings templates
    │   ├── theme-sync/             ← auto light/dark switcher
    │   └── install.sh              ← installs all the above into your $HOME
    └── seashell-daemon/            ← autonomous inbox responder via claude -p

Agradecimentos

Mais servidores MCP por M-Pineapple

Outros servidores MCP neste portfólio:

Se isso economizar seu tempo, uma estrela ajuda outros a encontrar.

💖 Apoie Este Projeto

Se o SeaShell ajudou a melhorar seu fluxo de trabalho de desenvolvimento ou economizou seu tempo, considere apoiar seu desenvolvimento:

Buy Me A Coffee

Seu apoio me ajuda a:

  • Manter e melhorar o SeaShell com novos recursos
  • Manter o projeto open-source e gratuito para todos
  • Dedicar mais tempo para atender solicitações de usuários e correções de bugs
  • Explorar novas integrações e recursos

Obrigado por considerar apoiar meu trabalho! 🙏

Licença

MIT — veja LICENSE.

Construído com 🐚 por Pineapple 🍍