ArtifactSmith
Servidor MCP de código abierto (MIT) que convierte la salida del agente en páginas web, PDFs, documentos de Word y hojas de cálculo que puedes compartir y revocar. Funciona con cualquier endpoint de modelo compatible con OpenAI. Autohospedado con Docker.
Documentación
ArtifactSmith
ArtifactSmith es un servidor de artefactos de código abierto para agentes de IA.

Los agentes lo llaman para convertir su trabajo en documentos: páginas web, PDFs, archivos de Word, hojas de cálculo y Markdown. Cada artefacto tiene versiones y permanece privado hasta que alguien lo comparte, y un enlace compartido puede revocarse en cualquier momento.
Es para personas que ejecutan sus propios agentes y quieren artefactos como los de Muse en sus propios servidores.
Pruébalo antes de instalarlo: https://artifactsmith.neuralami.ai/try. Ejecuto una copia a la que puedes apuntar tu cliente MCP (envía un correo para obtener un token, con un límite de 20 builds, se ejecuta en mi servidor).
Ejemplos, sin registro: página de comparación · página de estado con un gráfico · lista de verificación en PDF
Qué hace
ArtifactSmith ofrece a los agentes herramientas para crear, editar, exportar (PDF, Word, Excel, Markdown), compartir y revocar documentos. Funciona con cualquier endpoint de modelo compatible con OpenAI. La idea proviene de los artefactos Muse de Meta.

Requisitos: Docker Engine con el plugin de Compose. Se necesita una clave de modelo (API compatible con OpenAI o endpoint local) para construir páginas; publicar Markdown o HTML ya preparado funciona sin una.
Inicio rápido
cp .env.example .env
# Set OPENAI_API_KEY (or AM_LLM_KEY). Change AM_LLM_BASE / AM_DEFAULT_MODEL
# only if you are not using the default OpenAI host and model.
# Object-store keys: leave them empty and run ./scripts/ensure-local-env.sh,
# or paste your own AM_STORE_KEY and AM_STORE_SECRET.
./scripts/ensure-local-env.sh
docker compose up -d --build
# Create a token (prints the raw value once; only its hash is stored).
# --workspace is a label that scopes every artifact the token can see
# (e.g. alpha, team-a). Tokens cannot cross workspaces.
docker compose exec server artifactsmith token add --name agent --workspace alpha
publish almacena una página que ya escribiste y no llama al modelo, por lo que el siguiente paso funciona cuando la clave está vacía. Pega el token impreso arriba y luego ejecuta esto. Imprime una URL para compartir.
export AM_TOKEN='paste-the-token'
docker compose exec -T -e AM_TOKEN server python - <<'PY'
import asyncio
import json
import os
from datetime import timedelta
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
URL = "http://127.0.0.1:8780/mcp"
PAGE = """# Pilot store brief
The pilot store code is HARBOR-17. It is the identifier used on inventory, support, and rollout reports.
"""
def parse(result):
if result.structuredContent is not None:
return result.structuredContent
return json.loads(result.content[0].text)
async def main():
headers = {"Authorization": "Bearer " + os.environ["AM_TOKEN"]}
async with streamablehttp_client(URL, headers=headers) as (read, write, _session):
async with ClientSession(read, write) as session:
await session.initialize()
created = parse(
await session.call_tool(
"publish",
{"display_name": "Pilot store brief", "markdown": PAGE},
read_timeout_seconds=timedelta(seconds=120),
)
)
done = parse(
await session.call_tool(
"status",
{"job_id": created["job_id"], "wait": 90},
read_timeout_seconds=timedelta(seconds=120),
)
)
if done.get("status") != "done":
raise SystemExit(json.dumps(done))
shared = parse(
await session.call_tool(
"share",
{"artifact_id": created["artifact_id"]},
read_timeout_seconds=timedelta(seconds=120),
)
)
print(shared["url"])
asyncio.run(main())
PY
El servidor lee AM_LLM_BASE, AM_LLM_KEY o OPENAI_API_KEY, y AM_DEFAULT_MODEL desde .env. La base predeterminada es https://api.openai.com. Una barra final /v1 en la base está bien; el cliente añade /v1/chat/completions por sí mismo. create necesita una clave o un endpoint local compatible con OpenAI. Con una clave vacía, un create fallido informa que OPENAI_API_KEY / AM_LLM_KEY está vacío. Ejecuta docker compose up -d de nuevo para que el cambio surta efecto.
Los puertos del host se vinculan a 127.0.0.1:
http://127.0.0.1:8780/mcpes la API MCP (se requiere token bearer)http://127.0.0.1:8780/app/es Mis páginas (pega el mismo token; el navegador obtiene una cookie de sesión)http://127.0.0.1:8781/es el origen de vista previa y uso compartido sin cookies (/p/…,/s/…)
Usar la imagen publicada
ghcr.io/vrijsinghani/artifactsmith:0.1.0 se publica para linux/amd64 en cada lanzamiento. Compilar desde el código fuente (los pasos de Compose anteriores) sigue siendo la opción predeterminada.
Conectar un cliente
Claude.ai: Personalizar → Conectores → + Añadir → Añadir conector personalizado. Pega https://artifactsmith.neuralami.ai/mcp,, selecciona Conectar e inicia sesión con tu correo electrónico. El conector también funciona en las aplicaciones de escritorio y móvil de Claude. Los planes gratuitos permiten un conector personalizado. En los planes Team y Enterprise, un propietario lo añade primero.
github.com/vrijsinghani/Artifactsmith es la copia que ejecutas tú mismo. Claude Desktop, Claude Code y Cursor usan un token allí. El endpoint MCP en esta máquina es http://127.0.0.1:8780/mcp. Envía el token de artifactsmith token add como Authorization: Bearer YOUR_TOKEN. El valor del encabezado comienza con Bearer .
ChatGPT Free, Go y Plus no pueden añadir un servidor MCP personalizado. Pro puede añadir uno solo con acceso de lectura y fetch, por lo que no puede construir páginas. Un administrador de ChatGPT Business, Enterprise o Edu puede añadirlo en la web; inicia sesión con OAuth.
Claude Desktop almacena la configuración en claude_desktop_config.json (Configuración → Desarrollador → Editar configuración). El fragmento ejecuta npx y mcp-remote, lo que requiere Node.js. Reinicia Claude Desktop después de guardar.
{
"mcpServers": {
"artifactsmith": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://127.0.0.1:8780/mcp", "--header", "Authorization:${AUTH_HEADER}"],
"env": { "AUTH_HEADER": "Bearer YOUR_TOKEN" }
}
}
}
Claude Code almacena .mcp.json, o ejecuta:
claude mcp add --transport http artifactsmith http://127.0.0.1:8780/mcp --header "Authorization: Bearer YOUR_TOKEN"
{
"mcpServers": {
"artifactsmith": {
"type": "http",
"url": "http://127.0.0.1:8780/mcp",
"headers": { "Authorization": "Bearer YOUR_TOKEN" }
}
}
}
Cursor almacena ~/.cursor/mcp.json, o .cursor/mcp.json en el proyecto.
{
"mcpServers": {
"artifactsmith": {
"url": "http://127.0.0.1:8780/mcp",
"headers": { "Authorization": "Bearer YOUR_TOKEN" }
}
}
}
Ejemplo de prompt: Crea un resumen de una página llamado Resumen de la tienda piloto. Pasa esto como source_content: El código de la tienda piloto es HARBOR-17. Es el identificador utilizado en los informes de inventario, soporte y despliegue.
artifactsmith stdio puede listar herramientas en stdin y stdout y no escucha. No puede usar el almacén de objetos ni el modelo, por lo que no puede construir ni publicar una página.
Servir en tu red o detrás de un proxy
Compose publica API (8780) y vista previa/compartir (8781) en AM_BIND_ADDRESS (predeterminado 127.0.0.1). Las tarjetas y enlaces usan AM_API_URL, AM_PREVIEW_URL y AM_SHARE_URL (un AM_SHARE_URL vacío recurre a AM_PREVIEW_URL). Exponer los puertos sin TLS pone los tokens bearer y el contenido de los artefactos en el cable en texto claro.
Acceso LAN
Vincula en todas las interfaces (o en una IP específica de NIC) y apunta las variables de URL pública a la dirección que abrirán los clientes:
# .env (192.0.2.10 is documentation-range; substitute your LAN IP)
AM_BIND_ADDRESS=0.0.0.0
AM_API_URL=http://192.0.2.10:8780
AM_PREVIEW_URL=http://192.0.2.10:8781
AM_SHARE_URL=http://192.0.2.10:8781
AM_ALLOWED_HOSTS=192.0.2.10:8780,127.0.0.1:8780,localhost:8780
Luego docker compose up -d --build. Los clientes abren http://192.0.2.10:8780/mcp y los enlaces de vista previa/compartir usan 192.0.2.10, no 127.0.0.1. Los clientes MCP basados en navegador también necesitan AM_ALLOWED_ORIGINS configurado al origen de la página.
Las URL de compartir y vista previa se construyen desde AM_SHARE_URL / AM_PREVIEW_URL cuando el servidor las devuelve. Los enlaces que copiaste antes de cambiar a servir en LAN aún usan el host base antiguo; llama a share de nuevo (o copia la URL desde inspect / status) después de cambiar esos ajustes.
Proxy inverso con TLS
Mantén el enlace del host en loopback y termina TLS en nginx o Caddy. Configura las URL públicas y las listas de permitidos al nombre de host externo:
# .env
AM_BIND_ADDRESS=127.0.0.1
AM_API_URL=https://artifacts.example.com
AM_PREVIEW_URL=https://preview.artifacts.example.com
AM_SHARE_URL=https://preview.artifacts.example.com
AM_ALLOWED_HOSTS=artifacts.example.com
AM_ALLOWED_ORIGINS=https://artifacts.example.com
Ejemplo mínimo de Caddy (API en 443, vista previa en un segundo nombre de host):
artifacts.example.com {
reverse_proxy 127.0.0.1:8780
}
preview.artifacts.example.com {
reverse_proxy 127.0.0.1:8781
}
Fragmento mínimo de nginx para el host de API:
server {
listen 443 ssl;
server_name artifacts.example.com;
# ssl_certificate / ssl_certificate_key omitted
location / {
proxy_pass http://127.0.0.1:8780;
proxy_set_header Host $host;
proxy_set_header Authorization $http_authorization;
# status wait can be up to 90s; nginx defaults cut at 60s.
proxy_read_timeout 120s;
proxy_send_timeout 120s;
}
}
Cuando AM_ALLOWED_HOSTS lista solo el nombre de host público, un curl http://127.0.0.1:8780/mcp local obtiene HTTP 421 (protección contra rebinding de DNS). Apunta los clientes al nombre de host con proxy en su lugar. Los clientes MCP basados en navegador envían un encabezado Origin; configura AM_ALLOWED_ORIGINS a ese origen o la solicitud se rechaza.
Cliente MCP en otra máquina
Apunta el cliente a {AM_API_URL}/mcp y envía el token como Authorization: Bearer <token> (el valor impreso una vez por artifactsmith token add). El valor del encabezado comienza con Bearer . artifactsmith stdio puede listar herramientas en stdin y stdout y no escucha. No puede usar el almacén de objetos ni el modelo, por lo que no puede construir ni publicar una página. Ejemplo:
URL: http://192.0.2.10:8780/mcp
Header: Authorization: Bearer asmb_…
Cómo funciona
Los agentes hablan con ArtifactSmith a través de MCP. Pasan la solicitud del usuario como verbatim_request y cualquier investigación como source_content o source_files. create inicia una compilación privada y no publica un enlace para compartir. ¿Quieres una página con estilo? Pasa Markdown a publish y se estiliza automáticamente (omite format cuando pases markdown y no html; estilo de casa por defecto, aplicado cuando se abre la página). Aún puedes pasar format=markdown. O llama a create con source_content. El HTML sin procesar en publish es solo para una página que diseñaste tú mismo, y se muestra exactamente como se proporciona.
El servidor llama a un endpoint compatible con OpenAI (hasta dos intentos cuando las comprobaciones fallan). El modelo escribe el contenido. Un renderizador fijo lo convierte en HTML, Markdown, PDF, DOCX o XLSX. publish omite el modelo y ejecuta las mismas comprobaciones y el renderizador. Llama a status (o wait hasta 90 segundos) para obtener una vista previa privada.
share crea un enlace público para una versión exacta. unshare hace que ese enlace devuelva 404. Los tokens pertenecen a un espacio de trabajo y llevan un conjunto de permisos.
Los documentos pueden citar fuentes públicas http(s) como enlaces clicables. El servidor elimina scripts, rechaza hosts de enlaces privados y posibles secretos, y mantiene imágenes, CSS, fuentes e iframes autocontenidos para que una descarga se abra sin conexión y no haga llamadas externas. Se aplican límites de tamaño. El proceso no envía datos de uso a ningún lugar.
Formatos de salida
| Formato | create(..., format=) | Archivo principal | Renderizador |
|---|---|---|---|
| HTML | html (predeterminado) | index.html | sanitizar, luego almacenar |
| Markdown | markdown | document.md | texto UTF-8 |
pdf | document.pdf | WeasyPrint (HTML/CSS a PDF) | |
| DOCX | docx | document.docx | python-docx |
| XLSX | xlsx | document.xlsx | openpyxl |
HTML usa un estilo visual (de casa por defecto) y almacena una página completa. Los otros formatos piden Markdown al modelo y luego un renderizador lo convierte. publish acepta el HTML o Markdown terminado y usa ese mismo renderizador. PDF, DOCX y XLSX también almacenan content.md para que las ediciones posteriores tengan una base de texto.
PDF pasa por WeasyPrint, que necesita Pango y Cairo. La imagen de Docker no incluye Chromium. Los detalles están en docs/formats.md.
Estilos
create y edit aceptan un style opcional para HTML: house, bold, editorial, playful, terminal o swiss. El predeterminado es AM_DEFAULT_STYLE (generalmente house). En markdown, PDF, DOCX y XLSX, el prompt del modelo no usa ese estilo visual. Una página markdown publicada aún se muestra en el estilo registrado cuando se abre. Omite style en edit para mantener el último estilo.
create también acepta mode: full (predeterminado, AM_BUILD_MODE) hace que el modelo escriba HTML. fast hace que el modelo escriba Markdown y el servidor renderice ese estilo. fast es solo una primera compilación HTML. Las ediciones y otros formatos permanecen en la ruta completa. AM_FAST_STYLES (vacío por defecto) lista estilos cuyo mode omitido es fast; un mode explícito aún gana. Un documento HTML completo en la fuente, o una solicitud para publicar ese HTML tal cual, permanece en full cuando mode se omite. Mencionar HTML junto a "sin cambios" no lo hace.
house: la página tranquila de hoy — blanco, un acento, negrita en un solo lugar.bold: héroe azul marino a violeta oscuro, enorme titular blanco, tarjetas y chips saturados.

editorial: artículo de revista — papel blanco roto, gran titular serif, reglas finas, un acento rojo profundo.

playful: página crema, tarjetas redondeadas de colores dulces, contornos gruesos y sombras desplazadas.

terminal: modo oscuro, monoespaciado, verde neón y cian, secciones como ventanas de terminal falsas.

swiss: cuadrícula blanca, tipografía grotesca negra enorme, naranja de señal solo en números y reglas.

Herramientas MCP
| Herramienta | Permiso | Qué hace |
|---|---|---|
create | create | Poner en cola una compilación privada. format, style y mode opcionales. No crea un enlace para compartir. |
publish | publish | Almacenar Markdown para obtener una página con estilo, o almacenar HTML que diseñaste tú mismo. Sin llamada al modelo. |
edit | edit | Nueva versión desde la última versión completada. Rechaza un base_version desactualizado. |
status | read | Consultar, o wait (0 a 90 segundos), hasta que esté hecho, fallido o necesite_input. |
list_artifacts | read | Catálogo para el espacio de trabajo del token. |
inspect | read | Manifiesto, historial, estado de uso compartido. include_content opcional devuelve el texto almacenado de esa versión, limitado al límite de caracteres de publicación. |
export | export | Enlace de descarga para el archivo renderizado, válido 15 minutos. |
share | share | Enlace público para una versión exacta. |
unshare | share | Revocar el enlace público inmediatamente. |
revoke_previews | export | Expirar enlaces de vista previa privados /p/ para un artefacto (versión opcional). |
delete | delete | Purga en dos pasos. La primera llamada devuelve un token de confirmación. |
Argumentos de create
Requeridos:
verbatim_request: las palabras exactas del usuario. Ese es todo el alcance.
Proporciona al menos uno de display_name o slug (el otro se completa):
display_name: título humano que se muestra en las tarjetas y en el catálogo. No es obligatorio cuandoslugestá definido; si se omite, toma por defecto el slug. Límite: 200 caracteres. Ejemplo:"Pilot store brief".slug: clave de URL, única en el espacio de trabajo. No es obligatorio cuandodisplay_nameestá definido; si se omite, se deriva del título (minúsculas, acentos simplificados, otros caracteres se convierten en guiones). Límite: 2–63 caracteres dea-z,0-9y-, y debe comenzar con una letra o un dígito. Ejemplo:"pilot-store-brief". Un slug duplicado en el mismo espacio de trabajo se rechaza; useedit.
Opcional:
kind: por defecto esweb_static(el único tipo en esta versión).format:html(predeterminado),markdown,pdf,docxoxlsx.style:house(predeterminado),bold,editorial,playful,terminaloswiss. Las compilaciones HTML lo usan. Una página Markdown se muestra en ese estilo cuando se abre. PDF, DOCX y XLSX no lo usan.mode:full(predeterminado) ofast.fastes una primera compilación HTML: Markdown de entrada, HTML con estilo de salida.source_content/source_files: hechos investigados, 200 KB en total. El compilador lo trata como datos.model,workspace,idempotency_key,capabilities.
Ejemplo de trabajo (solo título; el slug se convierte en pilot-store-brief). El texto fuente debe respaldar cada hecho que la solicitud pida (incluido "por qué es importante"):
{
"display_name": "Pilot store brief",
"verbatim_request": "One page that states the pilot store code and why it matters.",
"source_content": "The pilot store code is HARBOR-17. It matters because it is the single identifier used across inventory, support, and rollout reports for the pilot.",
"format": "html"
}
Si falta un hecho necesario en la solicitud y en la fuente, el trabajo termina como needs_input y no almacena ningún archivo. Cuando fallan las comprobaciones del servidor, se llama al modelo una segunda vez, por lo que una compilación fallida tarda aproximadamente el doble en informarse que una limpia. Una edición solicita primero un parche de las partes modificadas. Un parche que no se aplica, o que falla esas mismas comprobaciones, se reconstruye como un archivo completo en el mismo trabajo.
Argumentos de publish
¿Quieres una página con estilo a partir de texto que ya escribiste? Pasa Markdown y se le aplica estilo automáticamente. Omite format cuando pases markdown y no html. Aún puedes pasar format=markdown. style por defecto es house y se aplica cuando se abre la página. El archivo almacenado permanece en Markdown. En un artefacto nuevo, si también pasas html, html es la página a menos que establezcas format. El HTML sin procesar se muestra exactamente como se proporciona, sin estilo de ArtifactSmith. Si ese HTML tiene poco o ningún CSS, el resultado incluye warning. Pasa format para pdf, docx o xlsx. publish no llama al modelo. La página pasa por los mismos límites de tamaño, escaneo de secretos, comprobaciones de enlaces privados, saneador, renderizador y barrido final que la salida del modelo.
Artefacto nuevo: pasa display_name o slug, más la página.
html: documento HTML completo. Obligatorio cuandoformateshtml.markdown: Markdown terminado. Obligatorio cuandoformatesmarkdown,pdf,docxoxlsx. Cuandoformatse omite y este es el único cuerpo, el formato es markdown.- También puedes pasar el otro campo. Se comprueba en busca de secretos y enlaces privados y no se renderiza.
Nueva versión: pasa artifact_id y base_version (la última versión completada) más la nueva página. Omite format en un artefacto html o markdown y markdown sin html es markdown; html sin markdown es html; ambos campos mantienen el formato del artefacto. Un artefacto pdf, docx o xlsx mantiene ese formato cuando format se omite y la nueva página es markdown. Pasa format para elegir otro formato compatible. Pasa display_name para renombrar la tarjeta cuando esta versión termine. Omite display_name y un título de página cambiado (el <title> HTML, o el primer encabezado Markdown) se convierte en el título de la tarjeta. El id y el slug del artefacto permanecen iguales. La página de compartir más reciente usa ese nombre para <title> y og:title. Las versiones anteriores mantienen su propio formato. Una página markdown compartida se abre como HTML con estilo; ?raw=1 o document.md en esa URL descarga el Markdown almacenado.
summary y assumptions son campos de tarjeta opcionales (8000 caracteres cada uno) y se escanean como la salida del modelo. Un resumen o suposición más largo se rechaza cuando llega la llamada, con metadata exceeds N characters, y no se almacena nada. note es opcional. Inspección lo muestra junto a Published a finished page.. Una nota demasiado larga, o que falla la comprobación de secretos, enlaces privados o lista de permitidos, se rechaza antes de que exista una fila de versión. En markdown, style cambia la página abierta (house por defecto). En HTML, style se registra y la página enviada no se reestiliza. PDF, DOCX y XLSX no usan la hoja de estilo HTML.
Devuelve artifact_id, version y job_id. Llama a status hasta que done o failed. Una página rechazada es failed, y error nombra la comprobación que falló. Cuando el trabajo está hecho, status y inspect incluyen safety_changes, la lista de ediciones de seguridad que cambiaron la página enviada. La publicación HTML no convierte una URL desnuda en un enlace. publish no crea un enlace para compartir. Cuenta para la cuota de compilación por token y necesita el permiso publish.
export
Permiso: export.
Argumentos:
artifact_id(obligatorio): qué artefacto descargar.version(opcional): una versión completada. Omítelo para usar la última versión completada.
Devuelve download_url (válido 15 minutos), file, version, sha256, self_contained y expires_in ("15m").
{ "artifact_id": "art_example", "version": 1 }
share
Permiso: share.
Argumentos:
artifact_id(obligatorio): qué artefacto publicar.version(opcional): la versión completada exacta. Omítelo para usar la última versión completada.ttl_days(opcional): vida útil en días. Por defecto esAM_SHARE_TTL_DAYS. CuandoAM_SHARE_TTL_DAYSes0,ttl_daysse ignora y el enlace dura hasta que se revoque. De lo contrario, la vida útil está limitada porAM_SHARE_TTL_MAX_DAYS.
Devuelve state ("shared"), url, version, expires_at, lifetime y reused. Cualquiera con la URL puede abrir esa versión.
{ "artifact_id": "art_example" }
unshare
Permiso: share.
Argumentos:
artifact_id(obligatorio): qué artefacto revocar.version(opcional): revocar una versión. Omítelo, o pasa0, para revocar cada versión.
Devuelve artifact_id, revoked_links (cuántos enlaces se revocaron) y state. state es "shared" cuando un enlace público sigue activo, con url y follows ("latest" o "pinned"). Es "not_shared" solo cuando no queda ningún enlace público. Revocar un pin deja el enlace de seguir-última versión activo. Una URL revocada devuelve 404 inmediatamente.
{ "artifact_id": "art_example" }
delete
Permiso: delete.
Argumentos:
artifact_id(obligatorio): qué artefacto purgar.confirm_token(omitir en la primera llamada): token de la primera respuesta. Expira en 10 minutos.
La primera llamada no elimina nada. Devuelve step ("confirm"), confirm_token, title, versions, expires_in ("10m") y una advertencia. La segunda llamada purga cada versión almacenada, enlace para compartir y fila de base de datos. Devuelve deleted (true), artifact_id y purged_object_versions.
{ "artifact_id": "art_example" }
{ "artifact_id": "art_example", "confirm_token": "paste-the-token" }
Configuración
Cada ajuste también tiene una variante NAME_FILE que lee el valor de un archivo.
| Variable | Valor por defecto | Propósito |
|---|---|---|
OPENAI_API_KEY / AM_LLM_KEY | vacío | Credencial LLM para el endpoint configurado. |
AM_LLM_API | chat | chat (Chat Completions) o responses. |
AM_LLM_BASE | https://api.openai.com | Raíz del host compatible con OpenAI. Con o sin una /v1 final. |
AM_LLM_TEMPERATURE | sin definir | Temperatura de Chat Completions. Sin definir envía 0; none omite el campo. |
AM_DEFAULT_MODEL | gpt-4o-mini | Nombre del modelo enviado al LLM. |
AM_DEFAULT_STYLE | house | Estilo visual HTML cuando create omite style. |
AM_BUILD_MODE | full | full o fast cuando create omite mode. fast es solo HTML de primera compilación. |
AM_FAST_STYLES | vacío | Estilos separados por comas (house, bold, …) que por defecto usan fast cuando create omite mode. Vacío mantiene AM_BUILD_MODE para cada estilo. Un mode explícito aún gana. Una fuente HTML terminada, o una solicitud para publicar ese HTML tal cual, permanece full cuando mode se omite. Mencionar HTML junto a "sin cambios" no lo hace. Otros formatos y ediciones permanecen full. Un nombre desconocido falla al inicio. |
AM_SHARE_TTL_DAYS | 30 | 0 significa hasta que se revoque. |
AM_SHARE_TTL_MAX_DAYS | 365 | Límite máximo para TTL positivos. |
AM_STORE_ENDPOINT | (obligatorio) | Almacén de objetos compatible con S3. |
AM_STORE_BUCKET | artifacts | Nombre del bucket. |
AM_STORE_KEY / AM_STORE_SECRET | (obligatorio) | Credenciales del almacén de objetos. |
AM_BLOCK_PRIVATE_LINKS | true | Rechazar hosts de localhost, RFC1918 y link-local en la salida. |
AM_ALLOWED_LINK_DOMAINS | vacío | Si se define, cada host restante debe coincidir. |
AM_MAX_OUTPUT_BYTES | 50 MiB | Límite de salida renderizada. |
AM_RENDER_TIMEOUT | 120 | Segundos para una llamada de renderizador. |
AM_BUILD_TIMEOUT | 900 | Segundos para todo el trabajo. |
AM_EDIT_PATCH_TIMEOUT_S | 45 | Segundos para la llamada de parche de edición. Un tiempo de espera agotado recurre a una reescritura completa. |
AM_MAX_BUILDS | 2 | Trabajadores concurrentes. Una prueba alojada usa como máximo uno de estos espacios. |
AM_BUILDS_PER_HOUR | 20 | Cuota por token para tokens internos. Los tokens de prueba usan AM_TRIAL_BUILDS_PER_HOUR. |
AM_TRIAL_ENABLED | false | Abrir la página de prueba alojada (/try, antigua /trial) y permitir sus compilaciones. false cierra ambas inmediatamente. |
AM_TRIAL_EMAIL_VERIFY | true | Enviar por correo un enlace de un solo uso antes de mostrar el token. false lo muestra al enviar y registra la dirección como no verificada. |
AM_TRIAL_BUILD_LIMIT | 20 | Compilaciones de por vida para un token de prueba. |
AM_TRIAL_FREE_NEEDS_INPUT | 5 | Trabajos needs_input por token alojado que no usan una compilación. Los siguientes sí. |
AM_TRIAL_BUILDS_PER_HOUR | 10 | Límite por hora para un token de prueba. No cambia AM_BUILDS_PER_HOUR. |
AM_TRIAL_DAILY_USD | 10 | Límite global de gasto de prueba por día UTC. Los tokens internos no tienen límite. |
AM_TRIAL_PRICE_IN / AM_TRIAL_PRICE_OUT | 0.10 / 0.50 | USD por 1M de tokens de entrada / salida, usado para fijar el precio de la retención. |
AM_TRIAL_MODEL | anthropic/claude-haiku-5-5 | Modelo para cada compilación de prueba. El nombre del modelo del llamante se ignora. |
AM_TRIAL_MAX_OUTPUT_TOKENS | 16000 | Límite de salida enviado en llamadas de modelo de prueba. |
AM_TRIAL_SIGNUPS_PER_IP / AM_TRIAL_SIGNUP_WINDOW_S | 3 / 86400 | Intentos de registro por dirección IPv4 o /48 IPv6 por ventana. |
AM_TRIAL_SIGNUPS_PER_DAY | 300 | Registros emitidos por día UTC. Después de eso, /try dice que no hay más tokens alojados hoy, de vuelta a las 00:00 UTC. |
AM_TRIAL_LINK_TTL_S | 1800 | Vida útil del enlace mágico, en segundos. |
AM_TRIAL_RETENTION_DAYS | 30 | Eliminar un espacio de trabajo de prueba este número de días después de su última compilación. |
AM_TRIAL_ABUSE_EMAIL | vacío | Mailto en el pie de página de la página de prueba. Vacío omite el enlace. |
AM_SMTP_HOST / AM_SMTP_PORT / AM_SMTP_FROM | vacío / 587 / vacío | Correo de enlace mágico. Host o remitente vacío falla de forma segura. |
AM_SMTP_USER / AM_SMTP_PASSWORD | vacío | Inicio de sesión SMTP. La contraseña acepta AM_SMTP_PASSWORD_FILE. |
AM_SMTP_TLS | true | Iniciar TLS al enviar el enlace mágico. |
AM_HOST | 127.0.0.1 | Dirección de escucha para artifactsmith serve. Compose/imagen establece 0.0.0.0 en el contenedor. |
AM_BIND_ADDRESS | 127.0.0.1 | Enlace de publicación del host para los puertos 8780 y 8781 en compose.yaml. |
AM_API_PORT / AM_PREVIEW_PORT | 8780 / 8781 | Puertos de escucha. |
AM_API_URL / AM_PREVIEW_URL / AM_SHARE_URL | http://127.0.0.1:8780 / :8781 | URLs escritas en tarjetas y enlaces. AM_SHARE_URL vacío usa AM_PREVIEW_URL. Una URL de API https:// marca la cookie de Mis páginas como Secure. |
AM_COOKIE_SECURE | sigue a AM_API_URL | true o false fuerza la marca Secure de la cookie de Mis páginas. Déjalo sin definir en HTTP local. |
AM_TRUSTED_PROXIES | vacío | IPs o CIDRs separados por comas que pueden establecer X-Real-IP o X-Forwarded-For para los límites de inicio de sesión de Mis páginas y los límites de registro de prueba. Vacío ignora esos encabezados. |
AM_LOGIN_LIMIT / AM_LOGIN_PREFIX_LIMIT / AM_LOGIN_WINDOW_S | 8 / 8 / 900 | Inicios de sesión fallidos por dirección IPv4 o /64 IPv6, y por prefijo de token con hash, dentro de la ventana (segundos). |
AM_ALLOWED_HOSTS | 127.0.0.1:<api_port>,localhost:<api_port> | Valores de Host separados por comas para la protección contra el rebinding de DNS de MCP (activada por defecto). |
AM_ALLOWED_ORIGINS | vacío | Valores de Origin separados por comas para la protección contra el rebinding de DNS de MCP. |
AM_DATA_DIR / AM_SECRETS_DIR | /data / /secrets | SQLite y la clave de firma HMAC. |
Copia .env.example para un archivo local. ./scripts/ensure-local-env.sh llena AM_STORE_KEY y AM_STORE_SECRET vacíos. La imagen de almacenamiento es rustfs/rustfs:1.0.1 (fijada por digest en compose.yaml).
Ejecutar una página de prueba alojada
Establece AM_TRIAL_ENABLED=true y apunta AM_API_URL al origen público. Con AM_TRIAL_EMAIL_VERIFY dejado en true, /try envía por correo un enlace de un solo uso (AM_SMTP_HOST y AM_SMTP_FROM son obligatorios; si alguno está vacío, el registro falla de forma segura). GET /trial redirige a /try. POST /trial y /trial/verify permanecen para que un formulario abierto o un enlace ya enviado aún funcione. El dominio Message-ID es el dominio de AM_SMTP_FROM. Abrir el enlace no lo gasta. El botón "Mostrar mi token" muestra el token del espacio de trabajo una vez. Establece AM_TRIAL_EMAIL_VERIFY=false para mostrar esa misma página al enviar y no enviar correo; la fila de registro almacena verified=0. De cualquier manera, la página incluye configuración para Claude Desktop (mcp-remote), Claude Code (HTTP), Cursor y un cliente MCP genérico. El fragmento de Desktop pone el valor del portador en AUTH_HEADER para que un espacio no sea un argumento. Cursor usa una URL y encabezados, sin argumentos de comando. Cada fragmento dice YOUR_TOKEN. La página también nombra la ruta Personalizar → Conectores → Agregar conector personalizado de Claude Desktop, con esa misma URL pública de MCP y un encabezado de solicitud Authorization. El valor del encabezado comienza con Bearer. Nombra claude_desktop_config.json, .mcp.json y ~/.cursor/mcp.json, el comando claude mcp add para esa URL pública de MCP y un ejemplo de prompt que pasa notas como source_content. La página indica el límite de compilación de AM_TRIAL_BUILD_LIMIT. Cuando AM_API_URL es un origen https público, esa misma página lleva una tarjeta Open Graph y Twitter para {AM_API_URL}/try. La imagen es {AM_API_URL}/static/og-trial.png y no necesita token. Un AM_API_URL de bucle local o privado omite las etiquetas. La página permanece noindex. Una dirección normalizada obtiene un token. Las direcciones de Gmail ignoran puntos y +tags. Los dominios desechables se rechazan. Los intentos de registro desde una red IPv6 comparten un /48. El inicio de sesión de Mis páginas sigue siendo un /64. Como máximo AM_TRIAL_SIGNUPS_PER_DAY tokens se emiten por día UTC (por defecto 300). Después de eso, /try dice que no hay más tokens alojados hoy, de vuelta a las 00:00 UTC.
Las compilaciones alojadas siempre usan AM_TRIAL_MODEL y no pueden llamar a publish. La lista de herramientas de un token alojado omite publish. Las respuestas incluyen builds_remaining. Hasta AM_TRIAL_FREE_NEEDS_INPUT (por defecto 5) trabajos needs_input por token no usan una compilación; después de eso sí. Un trabajo fallido aún lo hace. El límite de gasto diario aún cuenta los tokens de modelo de ese trabajo, y el límite por hora aún cuenta el intento. Eliminar un artefacto no devuelve esas compilaciones. Cuando se alcanza el límite de gasto diario, el error es the hosted copy hit today's limit, back at 00:00 UTC. Un token agotado apunta al repositorio de GitHub. Los enlaces compartidos para un token alojado duran como máximo 30 días. La página servida agrega noindex incluso cuando la página ya tiene una meta de robots que no dice noindex, y un pie de página que enlaza el repositorio y permanece visible si la página oculta pies de página. Los espacios de trabajo alojados inactivos se eliminan después de AM_TRIAL_RETENTION_DAYS (por defecto 30). Ese barrido se ejecuta al inicio y luego cada hora.
Seguridad
Los tokens se emiten por espacio de trabajo y se almacenan como hashes SHA-256. Conocer un id de artefacto no otorga acceso.
Los permisos son create, read, edit, publish, export, share y delete.
Las compilaciones permanecen privadas hasta que llamas a share. Eso crea una URL pública /s/… que sigue la última versión terminada. Pasa version o pin para congelar una versión en su propia URL. unshare hace que devuelva 404. unshare con una versión revoca solo ese pin.
Las páginas de vista previa y compartidas se ejecutan en su propio puerto, no envían cookies y establecen CSP script-src 'none' con sandbox allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation.
Mis páginas (/app/ en el origen de la API) es un catálogo de solo lectura para el espacio de trabajo del token. El inicio de sesión pega un token en un formulario. La respuesta establece una cookie de sesión HttpOnly, SameSite=Strict en la ruta /app y no pone el token en la URL. La cookie es Secure cuando AM_API_URL es https (o cuando AM_COOKIE_SECURE=true). Cada solicitud carga el token nuevamente, por lo que una revocación elimina la sesión inmediatamente. El id de artefacto de otro espacio de trabajo es un 404. La lista muestra 50 páginas a la vez. Las horas usan America/New_York a menos que el visor establezca otra zona IANA; la página nombra esa zona. Detrás de un proxy inverso, establece AM_TRUSTED_PROXIES a ese proxy para que un inicio de sesión fallido se cuente para el cliente (X-Real-IP, o el salto X-Forwarded-For más a la derecha) y para un hash del prefijo del token. Los clientes IPv6 comparten un /64. Cada dirección IPv4 es su propio bucket, incluida una dirección IPv6 mapeada a IPv4. Otros pares no pueden proporcionar esos encabezados. El origen de vista previa no sirve /app/.
Los scripts se eliminan. Los enlaces públicos http(s) a hosts globales se mantienen (con rel="noopener noreferrer nofollow" y target="_blank" en HTML). Los anclajes # dentro de la página permanecen en la página. Las comprobaciones de host usan urllib más una aproximación de estilo WHATWG; garantizan que los enlaces aceptados no lleguen a hosts privados, no que cada enlace aceptado sea válido en el navegador. Las citas //host relativas al protocolo se actualizan a https:// solo cuando el host es público; los destinos // privados o peligrosos se neutralizan, no se actualizan. Los hosts privados, de bucle local y link-local fallan la compilación, al igual que javascript:, vbscript:, data: y file:. Una URL de imagen remota se convierte en un enlace en el que se puede hacer clic a esa URL (texto alternativo como etiqueta) en cada formato: nada carga la imagen al abrir. CSS url(), fuentes e iframes permanecen locales. Los secretos fallan la compilación. Se aplican límites de tamaño y tiempo.
El proceso no envía datos de uso a ningún lugar. docs/threat-model.md y SECURITY.md contienen el resto.
Tokens
Un workspace es un espacio de nombres para artefactos (por ejemplo, alpha o team-a). Cada token pertenece exactamente a un workspace y solo puede crear o leer artefactos en ese workspace. Conocer el id de un artefacto de otro workspace no otorga acceso.
artifactsmith token add --name NAME --workspace WS [--perms create,read,edit,publish,export,share,delete]
artifactsmith token list
artifactsmith token revoke --id ID | --name NAME
artifactsmith token perms --id ID | --name NAME
artifactsmith token perms --name NAME --add publish
artifactsmith token perms --name NAME --remove publish
token perms cambia un permiso en un token existente. El valor del token permanece igual. Cada adición o eliminación agrega una línea al registro de auditoría. Listar permisos no lo hace. Si el nombre coincide con más de un token, pasa --id.
Desinstalación / restablecimiento
# Stop containers, delete compose volumes, and remove images created for this project
# (server, storage-init, and the rustfs/rustfs pull). Requires store keys in .env like other compose commands.
docker compose down --volumes --rmi all
Copia .env.example nuevamente y vuelve a ejecutar el inicio rápido para comenzar desde cero.
Desarrollo
python3 -m venv .venv && source .venv/bin/activate
make install
make ci # lint, types, unit tests, denylist
Ejecutar las pruebas
make test ejecuta la suite de pruebas unitarias con el mismo comando pytest que usa CI. No inicia Docker.
Las pruebas de extremo a extremo usan compose.test.yaml, que agrega un modelo de chat falso de tests/support/.
./scripts/ensure-local-env.sh
docker compose -f compose.yaml -f compose.test.yaml up -d --build
python -m tests.e2e.test_end_to_end
python -m tests.e2e.test_formats
python -m tests.e2e.test_bind_address # AM_BIND_ADDRESS=0.0.0.0 via non-loopback IP
make smoke ejecuta la misma pila y scripts. Los comandos de Compose necesitan un .env con AM_STORE_KEY y AM_STORE_SECRET configurados (incluyendo docker compose down), porque esas variables son requeridas por compose.yaml.
docs/architecture.md, docs/operations.md, CONTRIBUTING.md.
Cómo se construyó esto
ArtifactSmith fue codificado por vibración. Una persona estableció la dirección y tomó las decisiones, y los agentes de IA escribieron y revisaron el código:
- Grok Bot dirigió el proyecto. Planificó el trabajo, revisó cada solicitud de extracción archivo por archivo y decidió qué debía corregirse antes de fusionar cualquier cosa.
- Cursor los agentes en la nube escribieron el código, las pruebas y la documentación, y corrigieron lo que encontraron las revisiones.
- Hermes los agentes proporcionaron verificaciones independientes. Uno revisó el código como ingeniero senior y aprobó cada ronda. Otro lo instaló desde cero, siguiendo solo este README, como lo haría un nuevo usuario.
Antes del lanzamiento pasó por:
- Varias rondas de revisión de código línea por línea, cada una repetida hasta que no quedaron problemas bloqueantes.
- Una auditoría de seguridad independiente con Semgrep, Bandit, gitleaks, pip-audit y Trivy, además de un modelo de amenazas escrito.
- Pruebas prácticas de ataque al limpiador de HTML, manejo de enlaces y formatos de exportación.
- Compilaciones reales con un modelo en vivo en los cinco formatos, verificando que los enlaces sigan siendo clicables y que nada se cargue desde fuera al abrir un documento.
- Instalaciones limpias en una máquina separada, incluido servirlo a otras máquinas en una red local.
- Verificaciones automatizadas en cada cambio: lint, verificación estricta de tipos, pruebas unitarias en Python 3.11 y 3.12, compilación de Docker, escaneo de dependencias y escaneo de secretos.
El informe de auditoría está en docs/security-audit-2026-10-09.md. Si encuentras algo que pasamos por alto, consulta SECURITY.md.
Licencia
MIT. Las reglas de estilo y visuales de la casa están adaptadas de Humanizer (MIT) y Anthropic frontend-design (Apache-2.0). La atribución está en NOTICE; los textos de licencias de terceros están bajo licenses/. La imagen de Docker incluye esos archivos en /app.