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

@codeharbor-br/atualia-sdk

v0.1.2

Published

Cliente oficial do atualia: chama endpoints de proxy e consulta as collections do cliente com client_id + secret_key.

Readme

@codeharbor-br/atualia-sdk

Cliente oficial do atualia: chama as APIs liberadas para o seu app e consulta as collections dele.

Vem em duas formas — uma lib para usar em código e uma CLI para usar no terminal (ou por um agente de IA).

Se o que você quer é o app que roda dentro do painel do atualia, comece por App embarcado no painel: ali não existe credencial nenhuma a configurar.

npm install @codeharbor-br/atualia-sdk

Os dois transportes

Não é ambiente, é onde o código roda:

| | com credencial | embarcado no painel | | --- | --- | --- | | montagem | criarCliente({...}) | criarClienteEmbarcado() | | autenticação | assinatura HMAC, sempre | nenhuma: a sessão de quem está logado | | onde roda | servidor, CLI, job | iframe do app dentro do painel | | configuração | url, app, client_id, secret_key | nada |

A superfície é a mesma nos dois (api, collection, proxy), de propósito: o mesmo código de tela funciona nos dois.

Não existe modo de browser com credencial. Existiu: client_id público e a origem da página conferida contra uma lista cadastrada no app, para o app exibido fora do painel. Esse caso deixou de existir, e com ele o modo — hoje /api/sdk/** recusa qualquer chamada sem assinatura, e assinar exige a secret, que não cabe num bundle.

Cada credencial tem teto por minuto, e ele conta execução de API e acesso a dado. Listar APIs, ler documentação e ler esquema é livre — é o que um agente faz para descobrir o que existe antes de agir.

Credenciais

A credencial pertence a um app: no painel, abra o app e vá em Acesso. A secret aparece uma única vez.

O escopo é o app — a credencial alcança as collections e as APIs daquele app, e mais nada. Assim, vazar a credencial de um app não expõe os outros.

A secret_key é credencial de servidor, e é a única forma de chamar: sem ela não há assinatura, e sem assinatura a API recusa. Num bundle de front-end ela fica legível para qualquer visitante, então não vá por ali — dentro do painel o app usa criarClienteEmbarcado(), que não precisa de credencial. O SDK avisa no console se detectar uma secret rodando em browser.

export ATUALIA_URL=https://atualia-api.codeharbor.com.br
export ATUALIA_APP_ID=agenda        # id ou slug do app
export ATUALIA_CLIENT_ID=ak_...
export ATUALIA_SECRET_KEY=sk_...

No terminal dá para pular isso: atualia login pergunta os quatro campos um a um, testa a credencial e grava em ~/.atualia/config.json (permissão 600). Variável de ambiente sempre vence o arquivo, para CI e container não dependerem do que está na máquina.

Como a autenticação funciona

Cada chamada é assinada (HMAC-SHA256) com a secret, que nunca trafega. A assinatura cobre método, caminho, timestamp, um nonce e o hash do corpo — então uma requisição capturada não pode ser reenviada nem alterada.

Consequências práticas:

  • relógio importa: mais de 5 minutos de diferença com o servidor derruba a chamada. O erro diz isso explicitamente;
  • cada chamada é única: repetir a mesma assinatura é recusado (nonce repetido);
  • não dá para reproduzir com curl copiado — é o que faz da lib o caminho de uso, e não um detalhe de conveniência.

secretKey é obrigatório: uma chamada sem assinatura é recusada com 401, e a mensagem explica que o caminho do browser é criarClienteEmbarcado().

App embarcado no painel

É o caso normal, e nele não existe credencial:

import { criarClienteEmbarcado } from '@codeharbor-br/atualia-sdk'

// Sem baseUrl, sem appId, sem client_id: nada a configurar.
export const atualia = criarClienteEmbarcado()

// A superfície é a mesma do cliente com credencial:
const { corpo } = await atualia.api.call('core-sql-all', { body: { sql: 'SELECT ...' } })
const { itens } = await atualia.collection('pacientes').limit(20).get()

Como funciona: o app roda num iframe dentro do painel e manda cada pedido ao painel por postMessage. Quem executa é o painel, com a sessão de quem está logado. Três consequências:

  • não há o que vazar: o bundle é público e não carrega segredo nenhum;
  • fora do painel o app não funciona: sem alguém logado, ninguém executa por ele;
  • o alcance é o do app: o painel resolve o app pela sessão, não por algo que o app mande.

O painel também conta ao app onde ele está:

const { app, appNome, cliente, clienteNome, usuario } = await atualia.pronto()
const cancelar = atualia.aoTrocarContexto((c) => setCliente(c.clienteNome))

E o app se recusa a rodar fora do painel:

import { estaEmbarcado, hostQueEmbarca } from '@codeharbor-br/atualia-sdk'

const paineis = import.meta.env.VITE_ATUALIA_PAINEL.split(',')
if (!estaEmbarcado() || hostQueEmbarca(paineis) === false) {
  // mostra o aviso em vez de tentar chamar
}

ancestorOrigins (que é o que hostQueEmbarca lê) é preenchido pelo navegador e a página não consegue falsificar. No Firefox ele não existe: ali a resposta é null e quem decide é o handshake, que sem o painel não completa.

Para o app aparecer na barra lateral, informe o endereço publicado em Apps > o app > Publicação. atualia create monta esse app inteiro para você — ver abaixo.

Lib

import { criarCliente } from '@codeharbor-br/atualia-sdk'

const atualia = criarCliente({
  baseUrl: process.env.ATUALIA_URL!,
  appId: process.env.ATUALIA_APP_ID!,
  clientId: process.env.ATUALIA_CLIENT_ID!,
  secretKey: process.env.ATUALIA_SECRET_KEY!,
})

await atualia.ping() // confere credencial, assinatura e relógio

APIs

O destino real (URL, headers, credencial) fica no servidor. Você manda a chave e os valores dos campos declarados.

A chamada aceita os parâmetros separados por seção, o que deixa o contrato visível no próprio código:

const { corpo } = await atualia.api.call('core-pedido-criar', {
  method: 'POST',                          // conferido contra o cadastro
  headers: { instancia: 'erp_cliente01' }, // vira header na saída
  params: { pagina: 1 },                   // vira query string
  path: { codigo: 42 },                    // entra no caminho da URL
  body: { estmov, estimo },                // vira corpo JSON
})

As seções são headers, params (query), path e body. O method é declarativo: o método real vem do cadastro (é o que impede trocar um GET por um DELETE), e declará-lo faz o servidor conferir — se divergir, a chamada é recusada com mensagem clara em vez de executar a operação que você não quis.

Também aceita a forma plana, quando você não se importa com a divisão:

await atualia.api.call('core-pedido-criar', { instancia: 'erp_cliente01', estmov, estimo })

Quem posiciona de fato é o servidor, a partir do cadastro — a divisão é contrato legível, não roteamento. Um campo declarado em body que o cadastro diz ser de query ainda vai para a query.

Campos que vêm do ambiente do app

Um campo com variavelAmbiente preenchido é obrigatório mas não precisa ser enviado: o valor está configurado no app (painel → o app → Ambiente) e o servidor o aplica na saída. É o caso da instancia do Core, obrigatória em todos os endpoints dele.

const pedido = await atualia.api.get('core-pedido-criar')
pedido?.parametros
  .filter((p) => p.variavelAmbiente)
  .forEach((p) => console.log(p.nome, '← ambiente:', p.variavelAmbiente))
// instancia ← ambiente: instancia

// Com a instância configurada, a chamada não a repete:
await atualia.api.call('core-sql-one', { sql: 'SELECT 1 FROM dual' })

Enviar o valor continua valendo e tem preferência sobre a variável — é assim que se chama em nome de outra instância sem mexer na configuração. atualia.api.tools() já deixa esses campos fora do required, e a CLI os mostra como obrig.: ambiente em vez de pedir o valor.

Descobrindo o que existe:

const endpoints = await atualia.api.list()   // as do app + as compartilhadas
const consulta = await atualia.api.get('core-sql-all')
console.log(consulta?.documentacao)      // documentação completa, em Markdown
console.log(consulta?.exemploResposta)   // forma da resposta, sem precisar chamar

Para dar os endpoints a um modelo como ferramentas:

const tools = await atualia.api.tools() // name, description e input_schema

atualia.proxy é o mesmo objeto, mantido como apelido: o painel de quem publica chama isso de proxy, porque descreve a mecânica. Para quem consome, é API.

Collections

Cada collection é uma tabela real no seu schema no Postgres. Você a endereça só pelo nome: o app vem da credencial, então não existe forma de pedir a collection de outro app.

const pacientes = atualia.collection('pacientes')

const { total, itens } = await pacientes
  .where('ativo', '=', true)
  .where('idade', '>=', 18)
  .orderBy('nome')
  .limit(50)
  .get()

const um = await pacientes.where('cpf', '=', '11111111111').first()
const quantos = await pacientes.count()

await pacientes.insert({ nome: 'Maria Souza', cpf: '11111111111', idade: 40 })
await pacientes.update(id, { idade: 41 })
await pacientes.delete(id)

Operadores: =, !=, >, >=, <, <=, contem, comeca_com, termina_com, em, nulo, nao_nulo.

Campo não declarado, tipo errado ou obrigatório ausente são recusados pelo servidor com mensagem dizendo qual campo — a validação não é só do cliente.

Relatórios

O template é desenhado no Jaspersoft Studio e configurado no painel do app: lá se diz de onde vem cada campo do .jrxml e quais filtros o relatório aceita. Daqui você só escolhe formato e filtros.

const relatorios = await atualia.relatorio.list()
const contrato = await atualia.relatorio.get('faturamento')  // filtros, tipos, formatos

const { url, linhas, expiraEm } = await atualia.relatorio.gerar('faturamento', {
  formato: 'XLSX',                                    // PDF (padrão) ou XLSX
  filtros: { data_de: '2026-01-01', data_ate: '2026-01-31' },
})

A resposta é uma URL assinada e temporária, nunca o arquivo: o download não passa pelo backend, e a URL pode ser entregue a quem pediu. linhas responde "o filtro cortou tudo?" sem baixar nada.

Filtro não declarado é recusado, e obrigatório sem valor também — a mensagem diz qual. Fonte, colunas e ordenação vêm do cadastro; quem chama não escolhe, do mesmo jeito que não escolhe o destino de uma API.

CLI

O pacote instala o binário atualia.

atualia                            # estado atual: APIs e collections
atualia create meu-app             # gera um app React+Vite documentado deste app
atualia login                      # pergunta url, app, client_id e secret_key
atualia config                     # mostra o que está valendo e de onde veio
atualia ping                       # credencial, assinatura e relógio

atualia api list                   # APIs disponíveis
atualia api search pedido          # procura por nome, resumo ou campo
atualia api doc core-sql-all       # documentação, campos por seção e retorno
atualia api doc core-sql-all --full  # documentação inteira, sem truncar
atualia api call core-sql-all --sql 'SELECT cliente, nome FROM cadcli LIMIT 20'

atualia db list                    # collections deste app
atualia db doc pacientes           # campos, tipos, documentação e operadores
atualia db query pacientes --where ativo=true --order nome:asc --limit 20
atualia db insert pacientes --json '{"nome": "Maria"}'
atualia db update pacientes <id> --json '{"idade": 41}'
atualia db delete pacientes <id>

atualia relatorio list             # relatórios publicados neste app
atualia relatorio doc faturamento  # filtros aceitos, tipos e documentação
atualia relatorio publicar relatorios/faturamento.json   # sobe o .jrxml e o de-para
atualia relatorio gerar faturamento --data_de 2026-01-01 --data_ate 2026-01-31
atualia relatorio gerar faturamento --formato xlsx

atualia tools                      # definições de ferramenta (JSON) para um modelo

atualia relatorio publicar é o comando de quem escreveu o template: o .jrxml mais um manifesto JSON com a fonte, o de-para dos campos e os filtros, numa chamada. O servidor compila o template e valida o mapeamento inteiro antes de gravar, e republicar o mesmo identificador substitui — ajustar o layout e rodar de novo não duplica o cadastro. No painel o ciclo continua sendo em dois passos (subir o arquivo, depois mapear), que é o certo para quem desenhou no Jaspersoft Studio e ainda não sabe quais campos o template declara.

atualia api doc <chave> e atualia db doc <collection> são os comandos que respondem o que chamar e o que esperar de cada campo: resumo, documentação, campos agrupados por seção (headers, params, path, body) com tipo, obrigatoriedade, exemplo e valores aceitos, exemplo de retorno e o trecho de código pronto.

Filtros da consulta: --where campo=valor (igualdade) ou --where campo=operador:valor, repetível — os filtros somam com AND. Para em, separe os valores com |. Os operadores nulo e nao_nulo não levam valor. --order campo:desc, --limit e --offset completam a consulta.

atualia create

Gera um projeto React + Vite pronto para o Cloudflare Pages, que nasce sabendo o app e com a mesma stack do painel — shadcn/ui (Tailwind v4), TanStack Query, React Router e axios:

.env.local              o endereço do painel — e nada mais: o app não tem credencial
components.json         config do shadcn: `npx shadcn add <componente>` já funciona
src/components/layout/  a casca: sidebar, barra superior e a lista de telas
src/components/ui/      os do painel (button, card, table, input, select, field...)
                        + combobox.tsx: escolher UM registro buscando, que o registry não tem
src/lib/api.ts          cliente embarcado (sem credencial) + axios + mensagemDoErro
src/lib/contexto.ts     useContexto(): app, cliente e o usuário logado
src/lib/query-client.ts padrões do TanStack Query
src/lib/utils.ts        cn(), que todo componente do shadcn importa
src/paginas/            uma tela por arquivo: inicio, exemplo-cadastro, fora-do-painel
src/App.tsx             o mapa de rotas
src/main.tsx            portão de entrada, providers e router
src/iframe.ts           links para fora e storage dentro do iframe
src/index.css           tema (tokens do painel), tema claro só, altura do frame
public/_redirects       fallback de SPA — sem ele, sub-rota dá 404 depois do deploy
.claude/skills/erp/     como atacar tarefa que toca o legado do cliente
AGENTS.md               instruções para um agente de IA trabalhar no projeto
wrangler.toml           npm run deploy publica no Pages

As duas telas de src/paginas são exemplo, e se declaram exemplo: inicio lista dado real (o ERP do cliente, ou a primeira collection) e exemplo-cadastro é o padrão de tela deste projeto — formulário com react-hook-form + zod, Combobox para escolher um registro e Select para lista fechada. Não há pasta docs/: contrato de API e de collection é dado do servidor, e quem responde o que existe hoje é npx atualia.

A stack não é preferência: o app é exibido dentro do painel, então componente copiado de lá cola aqui e a tela não parece outro produto colado na página. A tela inicial já lista dados reais da primeira collection.

Não há lista de endpoints em arquivo, de propósito: as APIs e as collections mudam no painel sem este código mudar, e uma cópia congelada passaria a mentir. O que o projeto ensina é como perguntar — pela CLI e pelo painel /docs, que lê do servidor a cada abertura.

Não roda npm install: os comandos ficam impressos no fim.

Formatos de saída

| destino | formato | como forçar | | --- | --- | --- | | terminal | tabelas alinhadas, com cor | --humano | | pipe, arquivo, script | JSON | --json | | agente de IA | TOON — ~40% menos tokens | --toon |

A escolha é pelo destino, não por preferência: atualia api list num terminal sai legível, e atualia api list \| jq sai JSON sem precisar de flag. Quem paga token por caractere pede --toon e recebe a mesma informação em menos espaço.

Convenções da CLI

  • sem argumento mostra estado, não manual;
  • erros vão para stdout no mesmo formato dos dados, com o comando que resolve;
  • stderr é só diagnóstico — nada de dado por lá;
  • exit codes: 0 sucesso, 1 erro, 2 erro de uso;
  • prompt só existe com terminal: em pipeline, valor faltando é erro imediato — travar esperando um input que não vem é pior que falhar com mensagem.

MCP

O pacote instala também o binário atualia-mcp: o mesmo catálogo da CLI, servido pelo protocolo MCP para um agente que não tem shell (Claude Code, Claude Desktop, qualquer cliente MCP).

Instalar no Claude Code

1. Instale o pacote. O binário precisa existir no PATH antes do registro — o Claude Code executa o comando que você registrar, não resolve pacote por conta.

npm i -g @codeharbor-br/atualia-sdk
which atualia-mcp   # tem que imprimir um caminho

Não rode atualia-mcp na mão para "testar": ele é um servidor de stdio e fica esperando mensagem no stdin, parado. Quem conversa com ele é o cliente MCP.

2. Configure a credencial. O servidor MCP lê a mesma configuração da CLI, então login uma vez serve para os dois:

atualia login   # pergunta url, app, client_id e secret_key; grava em ~/.atualia/config.json

3. Registre o servidor. Rode de dentro do projeto onde ele deve valer:

claude mcp add atualia -- atualia-mcp

Tudo depois de -- é o comando que o Claude Code executa; sem esse separador, o resto da linha vira argumento do add.

O escopo decide quem enxerga o servidor, e a escolha importa porque a credencial aponta para um app só:

| escopo | onde vale | quando usar | | --- | --- | --- | | --scope local (padrão) | só você, só neste projeto | o normal: um app por projeto | | --scope project | todo mundo, via .mcp.json versionado | time inteiro no mesmo app | | --scope user | você, em todos os projetos | uma credencial que serve para tudo que você faz |

4. Confirme que subiu.

claude mcp list          # atualia deve aparecer como ✔ Connected
claude mcp get atualia   # escopo, comando e variáveis em uso

Dentro de uma sessão, /mcp mostra o mesmo e lista as ferramentas. A partir daí o agente chama atualia_status e segue do catálogo.

Credencial na configuração do servidor

Em CI, container ou máquina compartilhada, onde não há atualia login a rodar, as variáveis vão no próprio registro — elas têm precedência sobre o arquivo:

claude mcp add atualia \
  -e ATUALIA_URL=https://atualia-api.codeharbor.com.br \
  -e ATUALIA_APP_ID=agenda \
  -e ATUALIA_CLIENT_ID=ak_... \
  -e ATUALIA_SECRET_KEY=sk_... \
  -- atualia-mcp

Com --scope project isso entra num .mcp.json que vai para o git, e aí a secret_key literal não pode estar lá. Duas saídas: versionar só ATUALIA_URL e ATUALIA_APP_ID e deixar a credencial de cada pessoa no atualia login, ou usar a expansão que o Claude Code faz no .mcp.json (${VAR} e ${VAR:-padrão}, em command, args, env, url e headers):

{
  "mcpServers": {
    "atualia": {
      "command": "atualia-mcp",
      "env": {
        "ATUALIA_URL": "${ATUALIA_URL:-https://atualia-api.codeharbor.com.br}",
        "ATUALIA_APP_ID": "agenda",
        "ATUALIA_CLIENT_ID": "${ATUALIA_CLIENT_ID}",
        "ATUALIA_SECRET_KEY": "${ATUALIA_SECRET_KEY}"
      }
    }
  }
}

Variável sem valor e sem padrão não impede o arquivo de carregar: o claude mcp list avisa e o texto ${VAR} vai cru para o servidor, que responde erro de credencial.

Outros clientes MCP

Cliente que se configura por arquivo (claude_desktop_config.json, o .mcp.json de um projeto, a configuração de MCP do seu editor) recebe o mesmo servidor:

{
  "mcpServers": {
    "atualia": {
      "command": "atualia-mcp",
      "env": { "ATUALIA_APP_ID": "agenda", "ATUALIA_CLIENT_ID": "ak_..." }
    }
  }
}

Trocar de app, atualizar, remover

atualia login                  # aponta a credencial para outro app
claude mcp remove atualia      # sem -s, remove do escopo em que estiver
claude mcp add atualia -- atualia-mcp

Trocar a credencial não exige reinstalar nada: o servidor lê a configuração ao subir. Depois de atualia login, saia e volte à sessão para o servidor reiniciar.

Quando não conecta

| sintoma | causa provável | | --- | --- | | Status: ✘ Failed to connect | atualia-mcp não está no PATH do processo que abriu o Claude Code — teste com which atualia-mcp no mesmo terminal | | conecta, mas toda ferramenta responde "Configuração ausente" | não há ~/.atualia/config.json nem variáveis no registro; rode atualia login ou passe -e | | ferramentas somem depois de um npm i -g | o link do binário mudou; claude mcp remove e add de novo | | erro 401 em toda chamada | credencial de outro app ou revogada — confira com atualia config e atualia ping |

Para ver o que o servidor fala, claude --debug mostra a conversa e o stderr do processo. Nada além de JSON-RPC vai para o stdout, então não espere log por lá.

As ferramentas

| ferramenta | o que faz | | --- | --- | | atualia_status | catálogo do app: credencial em uso, APIs e collections com resumo | | atualia_api_doc | contrato de uma API: documentação, campos, exemplo de retorno | | atualia_api_call | executa a API pela chave, com os campos declarados | | atualia_db_doc | esquema de uma collection | | atualia_db_query | consulta com filtros, ordenação e paginação | | atualia_db_insert / update / delete | escrita nas collections | | atualia_relatorio_doc | contrato de um relatório: filtros, tipos e formatos | | atualia_relatorio_gerar | gera o relatório e devolve a URL temporária |

atualia_status é a porta de entrada: dele saem as chaves de API, os nomes de collection e os nomes de relatório que as outras ferramentas recebem. Não existe ferramenta de listagem separada porque o status já traz as três listas.

Vale para o MCP tudo o que vale para a CLI: a credencial define o app, o app define o alcance, e a documentação de cada API e collection vem do servidor. A resposta de cada ferramenta é TOON, pela mesma razão da CLI. Erro de execução volta como conteúdo com isError, e não como erro de protocolo, para o modelo poder ler a mensagem e corrigir a chamada.

Documentação vem do servidor

Toda API e toda collection carregam a documentação escrita por quem as cadastrou: documentacao (Markdown), exemploResposta e, por campo, descricao e exemplo. É o que permite descobrir o contrato sem sair da ferramenta — e, na API compartilhada, é a única documentação disponível, já que a URL de destino fica oculta.

Erros

import { AtualiaError } from '@codeharbor-br/atualia-sdk'

try {
  await atualia.api.call('core-pedido-criar', { body: { estimo } }) // falta a instância
} catch (erro) {
  if (erro instanceof AtualiaError) {
    console.error(erro.status, erro.message) // 400 Parâmetro obrigatório ausente: 'Instância'
  }
}

Status do sistema de destino não é exceção: um 404 ou 500 do ERP chega em resposta.status, para você distinguir "a chamada falhou" de "o destino respondeu erro".

Dois status que valem tratar explicitamente:

  • 429 — teto por minuto da credencial. O header Retry-After diz quantos segundos esperar;
  • 401 — credencial, assinatura ou relógio. A mensagem distingue o que é erro de configuração (relógio fora da janela, app divergente, chamada sem assinatura) do que é credencial inválida.