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

@meistrari/minuta-nuxt

v1.1.1

Published

Embed do editor de minuta (docx paginado A4) do defesa.ai para apps Nuxt — componente + proxy Nitro, sem segredos no browser

Readme

@meistrari/minuta-nuxt

Embed do editor de minuta do defesa.ai para qualquer app Nuxt: o documento paginado em A4 com edição rica e export DOCX fiel ao preview, renderizado como componente — sem iframe e sem segredos no browser.

Segue a mesma arquitetura do @meistrari/chat-nuxt: o componente fala com rotas Nitro registradas no próprio app hospedeiro (/api/minuta/*), e o proxy encaminha para a API do Case Defense Builder com o JWT Tela do usuário logado — a API valida o token contra a auth central e resolve o workspace.

O pacote mora em packages/minuta-nuxt do repositório case-defense-builder e é publicado no npm pelo workflow Publish @meistrari/minuta-nuxt. O próprio cliente do defesa.ai consome esta mesma versão publicada — a tela /minutas/:id é só um invólucro do <MinutaEmbed>, então bug corrigido aqui chega nos dois.

Instalação

npm install @meistrari/minuta-nuxt
// nuxt.config.ts
export default defineNuxtConfig({
    modules: ['@meistrari/minuta-nuxt'],
    minutaNuxt: {
        // URL da API do Case Defense Builder
        cdbApiUrl: process.env.CDB_API_URL, // backend do defesa.ai
        // opcional: cookie do host que carrega o JWT Tela do usuário
        // (default: event.context.auth.token → cookie `tela-jwt`)
        // authTokenCookie: 'meu-cookie-jwt',
    },
})

Uso

<template>
    <MinutaEmbed
        minuta-id="94a53f33-…"
        style="height: 100vh"
        @saved="onSaved"
    />
</template>

Criando uma minuta a partir do host (composable auto-importado):

const { uploadMinutaFile, createMinuta } = useMinutaApi()

// arquivos de QUALQUER tipo — sobem pro Vault via proxy
const files = await Promise.all([...pickedFiles].map(f => uploadMinutaFile(f)))

const minuta = await createMinuta({
    name: 'Contestação — João da Silva',
    files,
    // contexto livre sobre a peça (tipo, juízo, orientações do escritório…)
    context: 'Contestação para o réu; linha de defesa: culpa exclusiva do consumidor.',
    // agente Tela que escreve a minuta ("organizationName/repository")
    agentId: 'tela/defesa-minuta-avulsa',
})
// navegue para a tela que renderiza <MinutaEmbed :minuta-id="minuta.id" />

Para peça de texto fixo com campos variáveis, sem agente, veja Templates e variáveis.

Templates e variáveis

Nem toda peça precisa de agente. Quando o texto já está decidido e o que muda são alguns campos — petições intermediárias, ofícios, requerimentos de rotina — existe o caminho template: um documento com placeholders, escrito no mesmo editor A4, que vira minuta pronta no mesmo request, sem passar por agente nenhum.

O CDB não guarda template. Quem guarda é o app hospedeiro, que já tem cadastro para isso. O que o embed entrega é o editor e o formato; o que o CDB faz é trocar placeholder por valor.

Escrever o template

<MinutaTemplateEditor
    v-model="cadastro.templateHtml"
    :variables="CATALOGO"
    style="height: 100vh"
    @update:used-variables="keys => cadastro.variaveis = keys"
/>

O catálogo é um array plano, definido pelo host — o CDB não sabe o que significa nenhuma dessas chaves:

const CATALOGO = [
    { key: 'PROCESSO', label: 'Nº do Processo', description: 'De info_processos.processo', group: 'Processo' },
    { key: 'NOME_REU', label: 'Nome do Réu', group: 'Réu' },
]

No documento, o advogado digita / (ou usa o botão { } da barra), busca pelo rótulo e insere. O placeholder é um nó atômico: seleciona inteiro, apaga inteiro, e negrito/fonte aplicam nele por completo. Não dá para digitar uma chave que não existe, e não dá para partir uma pela metade ao formatar — que é o motivo de ser nó, e não #NOME_VARIAVEL no texto.

update:used-variables devolve as chaves usadas, na ordem do documento e sem repetição. É com ela que o host sabe o que perguntar; ele nunca precisa ler o HTML.

Gerar a peça

const minuta = await createMinuta({
    name: 'Requerimento — contrato 123',
    templateHtml: cadastro.templateHtml,
    variables: { PROCESSO: '1234567-89.2026.8.26.0100', NOME_REU: null },
})

minuta.status          // 'READY' — sem agente, sem polling
minuta.missingVariables // ['NOME_REU']

Só texto nos valores: número e data chegam já formatados pelo host, que é quem sabe se 1234.5 é R$ 1.234,50. null significa "não resolveu".

Variável sem valor não some. Ela continua sendo um placeholder vivo dentro do documento, no parágrafo em que está — quem abrir a minuta vê o buraco onde ele fica, em vez de descobrir que falta algo por um botão desabilitado. O host decide o que fazer com missingVariables (abrir pendência, bloquear o protocolo, ou deixar preencher no editor mesmo).

templateHtml e agentId se excluem: mandar os dois é 400.

Props do <MinutaTemplateEditor>

| Prop | Default | Descrição | |---|---|---| | modelValue | '' | HTML do template (v-model) | | variables | [] | catálogo { key, label, description?, group? } | | editable | true | permite editar | | showToolbar | true | barra com o resumo das variáveis usadas | | margins | 2,5cm/3cm | margens da página em px @96dpi | | chrome | null | cabeçalho/rodapé; v-model:chrome para persistir |

Eventos: update:modelValue, update:usedVariables, update:chrome, ready(editor). Slots: toolbar-start, toolbar-end. defineExpose: getHtml(), usedVariables, editor.

Playground: bun run dev e abra /?template.

Props do <MinutaEmbed>

| Prop | Default | Descrição | |---|---|---| | minutaId | — | id da minuta avulsa no CDB | | editable | true | permite editar o documento | | autoSave | true | salva 2s após a última edição | | showToolbar | true | barra com nome, estado de save e export | | margins | 2,5cm/3cm | margens da página em px @96dpi | | renamable | segue editable | nome editável na própria barra | | chatAgentId | null | id de plataforma do agente assistente; sem ele, o chat não aparece |

Eventos: ready(editor), saved(minuta), error(message). Slot: toolbar-start (o host injeta navegação própria, ex.: botão voltar). defineExpose: saveNow(), exportDocx(), editor.

Dois agentes, dois lugares. O que escreve a minuta vai no agentId da chamada de criação acima — é decisão de quem cria, e cada app manda o seu (o mesmo campo existe na Data API, para quem cria do servidor; a env MINUTA_AVULSA_AGENT do CDB é só o default de quem não mandar nada). O que edita pelo chat é prop do componente (chatAgentId), porque vive dentro da tela de edição.

A barra do embed inclui Histórico: versões são criadas automaticamente conforme o documento é editado (snapshot no auto-save, com espaçamento mínimo de 10 min) e podem ser restauradas — o estado atual vira um snapshot antes.

Como funciona

  1. MinutaEmbed busca a minuta em /api/minuta/:id (rota Nitro do módulo).
  2. O proxy resolve o JWT do usuário (contexto de auth do host ou cookie) e encaminha para {cdbApiUrl}/standalone-minuta/:id com Authorization: Bearer.
  3. Enquanto status === 'PROCESSING', o embed faz polling (3s) — o próprio GET no CDB consulta o agente e materializa o resultado.
  4. Com status === 'READY', o HTML entra no editor paginado (Tiptap + motor de paginação A4 próprio). O export DOCX é 100% client-side, com as quebras de página idênticas ao preview.

Desenvolvimento

npm install
npm run build      # nuxt-module-build

O núcleo do editor (MinutaDocxEditor.vue, paginationPlugin, docxExport, editorExtensions) é portado do Case Defense Builder — a intenção é que o CDB passe a consumir este package para existir um único editor mantido.