Harness MaxxingDocumentação
📚 Todas as páginas

🧩 Estender · 8 min de leitura

🧩Widgets do Painel

O Painel é uma grade de cartões. Arraste para reordenar, oculte os que você não precisa (menu de layout) e atualize um cartão com o botão de atualizar dele.

Os cartões se montam sozinhos conforme aparecem: os números sobem até o valor final, listas e tabelas surgem linha por linha, as barras dos gráficos crescem e as linhas se desenham, as barras de progresso se preenchem, e a Agenda de hoje abre a linha de cada tarefa e coloca as execuções nela, da esquerda para a direita. Um cartão mais abaixo na página espera você rolar até ele, então você também o vê se montando. Quando o painel de notificações abre ou fecha, os cartões deslizam para o novo lugar. Tudo fica parado quando seu sistema pede movimento reduzido.

Uma instalação nova mostra Atividade, Servidor, Agenda de hoje, Tarefas e Faça do seu jeito. Os outros ficam ocultos até você precisar deles (Editar layout mostra todos); o Shopify aparece sozinho assim que uma loja é conectada.

Faça do seu jeito é o cartão do final: clique em um exemplo ("Vendas de hoje no Shopify", "Visitantes do meu site"...) e uma nova sessão abre em system/harness com o pedido já digitado - envie, e o Harness escreve o widget nas suas extensões. Qualquer pedido funciona do mesmo jeito em qualquer sessão: "adicione um widget que mostre ...". Oculte o cartão com Editar layout quando já tiver o seu.

Widgets integrados

Widget O que mostra
Atividade sessões recentes, turnos em andamento, respostas não lidas (antes da primeira sessão: por onde começar)
Tarefas em andamento, para hoje, atrasadas
Agenda de hoje o dia das suas tarefas agendadas em uma linha de 00:00 a 24:00: uma linha por tarefa, execuções concluídas (verde, vermelho se falhou), execuções que ainda vão acontecer (vazadas), a que está rodando agora (pulsando) e uma linha de "agora"; passe o mouse sobre um ponto para ver o horário e a duração
Tarefas agendadas próximas execuções e falhas recentes
Servidor CPU, memória, disco, tempo ligado, problemas de saúde
Backup da memória última execução, idade, repositório
Telegram status do bot e da conexão
Shopify faturamento de hoje e dos últimos 7 dias (em comparação com a semana anterior), pedidos de hoje, pedidos a enviar, gráfico de faturamento dos últimos 14 dias, últimos pedidos — depois que uma loja é conectada (veja abaixo)
Faça do seu jeito como ter seus próprios widgets, com exemplos que abrem uma sessão pronta para enviar
Uso do Claude desativado por padrão (Configurações -> Recursos): janelas de uso do plano, as mesmas das barras na parte de baixo da barra lateral (lido da última resposta do Claude; quando nenhuma chega em 10 minutos, consulta um endpoint não documentado da Anthropic, que pode parar de funcionar; quando esse endpoint falha ou pede para esperar, os últimos números lidos do Claude continuam sendo exibidos).

Conectar uma loja Shopify

O widget do Shopify mostra um cartão "Conecte sua loja" até você adicionar duas chaves:

  1. No admin do Shopify: Configurações → Apps e canais de vendas → Desenvolver apps → Criar um app, depois Configurar escopos da Admin API → marque read_orders → Instalar app → revele o token de acesso da Admin API (shpat_…, exibido uma única vez).
  2. No Harness Maxxing: Configurações → Chaves → adicione SHOPIFY_STORE = myshop.myshopify.com e SHOPIFY_ADMIN_TOKEN = o token.

O widget é atualizado a cada 2 minutos (ou pelo botão de atualizar) e apenas lê pedidos. Outra loja (WooCommerce, Stripe, PrestaShop…)? Peça um widget ao Claude: ele cria uma extensão do mesmo jeito (veja abaixo).

Escrevendo seu próprio widget

Um widget é uma pasta em <workspace>/extensions/<id>/ com dois arquivos. Você escreve dados, não HTML: o cartão é desenhado pelo design system do app, então ele sempre fica bonito nos modos escuro e claro, no computador e no celular. (Precisa de desenho livre ou interação - um jogo, um canvas? Veja Widgets HTML abaixo.)

O jeito mais rápido: peça ao Claude em uma sessão, "crie um widget de painel que mostre ...". A skill maxxing-extension conhece tudo o que está abaixo.

1. Crie a pasta

/root/maxxing/extensions/disk-usage/
  manifest.json
  api.py

O id (nome da pasta) é um slug em minúsculas: letras, dígitos, hifens. Ids reservados: activity, tasks, day, crons, server, backup, telegram, shopify, usage, customize.

2. manifest.json

{"id": "disk-usage", "name": "Disk usage", "type": "widget", "icon": "hard-drive",
 "version": "1.0.0", "api": 1, "span": 1, "ttl": 60}
Chave Significado
type widget
api versão do contrato de extensões para a qual ele foi escrito (padrão 1); uma versão que deixe de oferecer suporte a ela é avisada pela verificação prévia da atualização antes de você atualizar
icon um nome de ícone em kebab-case (gauge, chart-line, shopping-cart, users, receipt...) do conjunto de ícones lucide do app; um nome que o app não inclui mostra uma peça de quebra-cabeça
span largura em colunas da grade: 1, 2 ou 3
ttl segundos em que o resultado fica em cache (padrão 300). O botão de atualizar ignora o cache.

3. api.py

import shutil


def card(ctx):
    du = shutil.disk_usage("/")
    pct = round(100 * (du.total - du.free) / du.total)
    return {
        "title": "Disk usage",
        "status": "error" if pct > 95 else "warn" if pct > 85 else "ok",
        "blocks": [
            {"type": "stat", "label": "Used", "value": f"{pct}%", "hint": f"{du.free / 1e9:.1f} GB free"},
            {"type": "progress", "label": "/", "value": pct, "max": 100},
        ],
    }

card(ctx) roda dentro do servidor: use apenas a biblioteca padrão do Python e mantenha as chamadas de rede curtas (timeout ≤ 10 s). O código das extensões roda em 4 threads próprias: um código que nunca retorna (uma chamada de rede sem timeout) só bloqueia as outras extensões ("as extensões estão ocupadas" até ele retornar), nunca o resto do Harness. Um api.py precisa carregar em menos de 10 s (sem chamada de rede na importação), senão a extensão mostra um erro. O ctx contém:

Chave Valor
id o id da extensão
workspace caminho do workspace
state_dir uma pasta reservada para esta extensão (cache, dados)
secret(name) valor de uma chave de Configurações -> Chaves, ou None
config {"timezone", "language", "public_url"}

Nunca escreva um token direto no código: guarde-o em Configurações -> Chaves e leia com ctx["secret"]("MY_TOKEN").

4. O esquema do Card

Card  = {title?, status?: "ok"|"warn"|"error", link?, blocks: [Block], updated?}
Block =
  {type: "stat", label, value, hint?, delta?, tone?: "ok"|"warn"|"error"}
| {type: "stats", items: [stat, ...]}                      several stats in a row
| {type: "list", items: [{title, subtitle?, value?, href?, status?, icon?, emoji?}], empty?, grid?}
                                                            grid: true = tiles side by side (shortcuts)
                                                            icon: an icon name (below), drawn like the app's
| {type: "chart", kind: "bar"|"line", series: [{name, points: [[x, y], ...]}], unit?}
| {type: "table", columns: [str], rows: [[cell, ...]]}
| {type: "progress", label, value, max, hint?}
| {type: "markdown", text}

link torna o título do cartão clicável (uma rota do app como /tasks ou uma URL https://). Tipos de bloco desconhecidos são ignorados.

Exemplo de lista + gráfico:

def card(ctx):
    orders = [("#1042", "Alice", 89.0), ("#1041", "Bob", 34.5)]
    return {
        "title": "Shop",
        "link": "https://shop.example.com/admin",
        "blocks": [
            {"type": "stats", "items": [{"type": "stat", "label": "Today", "value": "123.50 €"},
                                        {"type": "stat", "label": "Orders", "value": "2"}]},
            {"type": "chart", "kind": "bar", "unit": "€",
             "series": [{"name": "Sales", "points": [["Mon", 80], ["Tue", 120], ["Wed", 123.5]]}]},
            {"type": "list", "empty": "No orders yet",
             "items": [{"title": o, "subtitle": c, "value": f"{v:.2f} €"} for o, c, v in orders]},
        ],
    }

5. Teste e depois carregue

Teste a função diretamente no servidor:

cd /root/maxxing/extensions/disk-usage && python3 -c "
import json, api, tempfile
ctx = {'id': 'disk-usage', 'workspace': '/root/maxxing', 'state_dir': tempfile.mkdtemp(),
       'secret': lambda n: None, 'config': {'timezone': 'UTC', 'language': 'en', 'public_url': ''}}
print(json.dumps(api.card(ctx), indent=2))"

Depois vá em Configurações -> Extensões -> Recarregar. O widget aparece no painel. Se algo estiver errado (manifesto inválido, erro de importação, exceção em card), o erro é mostrado na linha da extensão e no cartão; o resto do painel continua funcionando.

As extensões ficam no seu workspace: as atualizações nunca mexem nelas e o backup as inclui.

Widgets HTML (jogos, desenhos personalizados)

Quando um Card não basta - um joguinho, um desenho em canvas, uma biblioteca de gráficos própria - escreva o widget em HTML. Mesma pasta, com widget.html no lugar de api.py (ou junto com ele):

/root/maxxing/extensions/snake-widget/
  manifest.json     {"type": "widget", "name": "Snake", "icon": "gamepad-2", "height": 300}
  widget.html       one self-contained file: inline <style> and <script>
  api.py            optional: handle(ctx, method, path, query, body) for data it saves or fetches

O painel desenha o widget.html em um frame isolado (sandbox): os scripts rodam, mas o frame não pode ler o app, seus cookies nem sua API - um widget nunca pode agir como você. Ele conversa com o próprio api.py por meio de uma pequena ponte que é injetada para você:

No widget.html O que faz
await maxxing.api("best") · maxxing.api("best", {method: "PUT", body: {...}}) chama handle() no api.py desta extensão (/api/ext/<id>/best) e retorna o JSON dele
maxxing.resize(400) define a altura do frame em px (o height do manifesto, de 120 a 800, é o padrão)
maxxing.theme · maxxing.onTheme(fn) "dark" ou "light", e um callback quando muda
class="ui-card", ui-btn, ui-input, ui-badge, ui-stat... o kit de UI do app (veja Extensões de página), já carregado no frame
var(--card) --foreground --muted-foreground --border --primary... (e os mais antigos --mx-fg --mx-muted --mx-border --mx-accent --mx-card --mx-bg) as cores do app, atualizadas com o tema e a cor de destaque

Regras: apenas um arquivo (nenhum outro arquivo da pasta é entregue ao frame - coloque tudo inline, ou carregue bibliotecas de um CDN com <script src="https://...">); sem localStorage/cookies (o sandbox não tem: salve por meio do api.py e de ctx["state_dir"]). O botão de atualizar do cartão recarrega o frame. Exemplo completo: examples/extensions/snake-widget/ no diretório do código.

💡 Travou? Abra uma sessão no topic system › help e pergunte com suas palavras: o Claude lê esta documentação no seu servidor e responde.✏️ Melhorar esta página