Ask AI

La línea de comandos

remy, la herramienta que toda app de Remy instala con el paquete: los scripts, la estructura que leen, una app nueva, la publicación de paquetes, las traducciones, Cloudflare y los planes.

La herramienta es remy, el bin de @joeblew999/remy-ui (packages/ui/src/cli, un módulo de TypeScript por comando); una app la obtiene con el paquete (npm install, en la versión del paquete) y la nombra en los scripts de su package.json, los mismos en todas las apps ("dev:change": "remy change"). Una app indica una sola vez, en remy.config.ts, dónde está desplegada y cuáles son sus puertos:

import type { RemyProject } from '@joeblew999/remy-ui/project';

export default {
  origins: { production: 'https://your-app.your-subdomain.workers.dev', docs: 'https://your-app-docs.your-subdomain.workers.dev' },
  ports: { preview: 4173, docsPreview: 4174 },   // the main checkout's; a worktree computes its own from its name
} satisfies RemyProject;

La estructura

Todo repositorio de Remy, remy-auth incluido, tiene los mismos archivos en los mismos lugares; lo que hay dentro de los archivos del producto es diferente. La estructura son las rutas que leen los comandos y el paquete, así que un archivo en cualquier otro lugar simplemente no se encuentra; ninguna comprobación propia lo mantiene.

RutaLeído porContiene
package.json's scripts, remy.config.ts, .node-versionnpm, remyla entrada: todos los comandos; los orígenes, los puertos y el paquete de release; la versión de Node
package.json, package-lock.jsonnpm, remy (workspaces)las dependencias de la app; workspaces cuando es dueña de paquetes
wrangler.jsoncwrangler, el plugin de Vite de Cloudflare, remy cloudflareel nombre del Worker, los bindings y los assets
vite.config.ts, tsconfig.json, playwright.config.tsVite, remy check types, remy testel build, los tipos y las comprobaciones de navegador
fnox.toml, skills-lock.jsonfnox, remy skillslos nombres de los secretos; las skills fijadas
src/routes/, src/routeTree.gen.tsTanStack Start (el árbol se genera)las páginas del producto
src/parts.jsonel remyParts() del paquetelas partes compartidas que lista la app (ninguna sin el archivo; la app en blanco lista seo-routes)
src/api/la ruta api.$ de la applos procedimientos del producto, si tiene API
tests/ (smoke.spec.ts por nombre)remy test, el smoke en vivo tras un desplieguelas comprobaciones compartidas con los ajustes de la app, y las propias
public/Vitefavicon, _headers
dist/ (dist/client/assets)los despliegues, las comprobacionesresultado del build, nunca en commit
docs/ (docs.config.ts, vite.config.ts, tsconfig.json, package.json, content/{users,dev,ui}/ con i18n.json y meta.json, content/questions.json; generados .remy-docs/, dist/)remy docs, remy i18n (I18N_DOCS_DIR)la documentación de la app: su identidad y sus páginas; el Worker es del paquete
packages/<name>/ con README.mdremy plans, remy packageslos paquetes que publica el repositorio, si los hay
messages/, project.inlang/el preset de la app, el script generate de la app, remy i18nlas cadenas propias de la app, si las hay (compiladas en src/paraglide/)
.plans/ (now.md, done/, parked/)remy planslos planes del repositorio
AGENTS.md, .github/workflows/agentes; GitHubel puntero de los agentes a la documentación; CI
packages/ui/src/cli/, template/, fixtures/consumer/el paquete; giget; remy test consumerssolo remy-auth: la propia línea de comandos, la app en blanco desde la que arranca un repositorio nuevo, y el fixture de consumidor que prueba ambos tal como los recibiría otro repositorio

Un consumidor nuevo

La receta única. Una app nueva arranca desde la app en blanco, template/ en remy-auth en la tag de la versión: la estructura de arriba con solo los archivos propios del repositorio dentro (sus nombres, una página de inicio en cada idioma, un índice de documentación por sitio, los archivos de configuración de la estructura con apenas unas líneas cada uno). Todo lo demás es el paquete, que un solo cambio de versión actualiza.

  1. Requisitos: Node según indica .node-version, gh auth login con un token que tenga read:packages, Google Chrome (lo usan las comprobaciones) y wrangler login antes del primer despliegue.
  2. npx -y giget@3.3.1 gh:joeblew999/remy-auth/template#vX.Y.Z <name>, luego cd <name> y git init (las herramientas de traducción y de planes leen el historial de git; npm run setup lo indica si falta).
  3. Nombra la aplicación: sustituye my-app y My app (grep -rn -i "my.app" --exclude-dir=node_modules .): package.json, wrangler.jsonc, src/service.ts, los orígenes de remy.config.ts, docs/docs.config.ts (el nombre y la fuente del producto, que también lee el marco de la app) y messages/en.json.
  4. Node según indica .node-version, luego GITHUB_TOKEN=$(gh auth token) npm install una vez para escribir package-lock.json, luego npm run setup (npm ci, las skills fijadas y la skill remy, el bloque de reglas de AGENTS.md, el registro MCP, el guardián del flujo, las comprobaciones de la instalación).
  5. Haz commit de todo, package-lock.json, skills-lock.json y src/routeTree.gen.ts incluidos. Las cadenas de la app en blanco ya vienen traducidas; cuando el inglés cambie (messages/en.json, la documentación), haz commit de ello y traduce esto una vez a mano, REMY_FLOW=hand npx remy i18n translate, que escribe y hace commit de los demás idiomas; a partir de entonces lo hace dev:land.
  6. El primer despliegue, a mano: npx remy cloudflare deploy, y npx remy cloudflare docs-deploy para la documentación. Una vez que el repositorio está en GitHub (paso 7), el trabajo pasa por el flujo: dev:land para cada cambio terminado, dev:promote para producción y la documentación.
  7. CI: .github/workflows/google.yml llama al flujo de trabajo compartido de remy-auth en la misma tag (todos los idiomas, las auditorías de Google en cada push a main). Da al repositorio nuevo acceso de lectura en la configuración del paquete @joeblew999/remy-ui ("Manage Actions access"), o npm ci falla allí.

Después, la app crece en sus propios archivos: páginas en src/routes/ (una página junto a la ruta de un padre, como videos.$videoId al lado de videos, es videos_.$videoId.tsx: TanStack anida videos.$videoId dentro de videos, que entonces necesita un <Outlet />), sus enlaces en src/remy-app.tsx, cadenas en messages/en.json (m desde src/paraglide/messages.js), una API como paquete de contrato y src/api/ (todo procedimiento que recibe entrada documenta un error, o apiChecks falla: la regla api/coverage del paquete), documentación en docs/content/. Pasa a una versión nueva con npm run upgrade -- <version> (el paquete y la tag del flujo de trabajo de CI, juntos). Dos ejecuciones de navegador locales a la vez comparten PREVIEW_PORT y la segunda falla al iniciar su servidor: dale a cada shell el suyo (PREVIEW_PORT=4232 npm run …).

Un repositorio que publica paquetes

Un repositorio también puede publicar paquetes propios (un contrato, un paquete de UI), como hace remy-auth; eso no cuesta ningún comando propio. Sus paquetes son workspaces de npm (packages/<name>/, workspaces de package.json), y cada uno que no es private se publica mediante remy packages (npm run packages -- …):

ComandoHace
check, upgradeVersiones npm más nuevas de las dependencias propias del repositorio (raíz y workspaces; nunca sus propios paquetes ni @joeblew999/remy-ui), previsualizadas (check) o aplicadas, y luego las comprobaciones de la instalación (upgrade)
packUn tarball de cada paquete publicado, para que una app de prueba lo instale antes de cualquier release
releaseLas puertas de control (traducciones en modo estricto, cada paquete modificado con su versión subida, remy verify, el script release-checks de la app, auditorías de Google, Core Web Vitals), y luego tag; dev:release lo ejecuta
tagSobre un main limpio: etiqueta la versión del paquete de release, hace push y luego publish --release
publishPublica cada paquete cuya versión es nueva en su registro; con --release vX.Y.Z también el release de GitHub a partir de CHANGELOG.md (notes). El job de CI de la tag ejecuta el mismo comando

En remy.config.ts: release.package (el paquete cuya versión da nombre al release) y release.title. El exports de un paquete nombra cada subruta que sus usuarios importan ("./video": "./src/video.ts"); cualquier otra cosa se rechaza en tiempo de build y en Playwright, cuyo mensaje nombra el archivo que importa, no el mapa de exports. Las cadenas propias de un paquete son su propio proyecto inlang, compilado por su build, cuyo runtime sigue el idioma de la plataforma con una sola llamada, followLocale(runtime) (locale); las tareas de i18n lo leen a través de I18N_INLANG. Las comprobaciones que el repositorio añade a un release van en su script release-checks (remy-auth: template:check, ui:verify). remy-auth fija la versión de un release con npm run ui:version -- X.Y.Z, que la escribe en todos los lugares donde aparece (el paquete y los pines de la app en blanco).

Elegir la versión: publicada, de desarrollo o local

La herramienta y el paquete son una sola cosa: el npm run dev:release de remy-auth publica @joeblew999/remy-ui X.Y.Z, bin incluido, y etiqueta ese mismo commit como vX.Y.Z. Quien consume el paquete fija ese único número en su package.json (y en la tag de su flujo de trabajo de CI), y npm run upgrade -- X.Y.Z cambia ambos. Para trabajar contra el checkout propio de remy-auth, npm link ../remy-auth/packages/ui (y npm unlink para volver).

La plataforma aporta todos los paquetes npm que necesitan la app y las tareas, en una sola versión: son dependencias propias de @joeblew999/remy-ui (el framework, la cadena de herramientas, las herramientas de las comprobaciones), así que el package.json de una app nombra el paquete, sus propios paquetes y lo que su producto añada. the check's single-copies part (dentro de remy check) falla cuando un paquete que se rompe si se instala por duplicado (React, TanStack Router y Query, el stringifier de MDX; la lista remy.singleCopy del paquete) está presente: quita el pin propio de la app. Los comandos llaman a los ganchos propios de una app donde los tiene, como scripts de su package.json, que se ejecutan si existen: generate (código generado antes de comprobar los tipos; el de la app en blanco compila su propio catálogo), local (el estado local que una app necesita antes de ejecutarse, que dev, build y dev:start ejecutan primero; remy-auth escribe ahí su .dev.vars y migra su D1 local), release-checks y test:consumers.

El flujo de desarrollo es código, packages/ui/src/cli/flow.ts, con cuatro pasos y un guardián (cómo trabajamos):

PasoScript
Primerodev:status (main con los veredictos de GitHub, los despliegues, los worktrees por delante y por detrás, las traducciones, las pull requests; un hook SessionStart lo ejecuta para los agentes)
Un trabajodev:start -- <name> (un worktree propio desde main, npm ci, puertos propios, el guardián); dev:done lo elimina cuando el trabajo termina y queda fusionado
Cada cambiodev:change (remy change, cada parte una función: planes, copias únicas, rutas cuando cambia un archivo de ruta, tipos, las comprobaciones unitarias, las traducciones como aviso, la documentación cuando cambia)
Trabajo terminadodev:land -- "<message>" (la comprobación, commit, main, push, el despliegue en staging, y luego la traducción cuando está desactualizada; GitHub ejecuta entonces npm run test -- every, google y consumers en paralelo, y una ejecución en rojo comenta en el commit. Las mismas tres en local, a propósito, por una persona: REMY_FLOW=hand npm run test -- <level>)
Produccióndev:promote (se niega ante un commit que GitHub no ha aprobado; luego el despliegue a producción, el despliegue de la documentación, lo que ejecuta cada despliegue)
Un releasedev:release (remy packages release, que ejecuta remy verify)
Un áreanpm run test -- only <words>
El guardiánnpx remy guard lo registra (npm run setup lo hace); npx remy guard --check lo verifica (las comprobaciones de la instalación lo hacen)

npm run test -- every es cada una de nuestras comprobaciones en todos los idiomas (dentro de remy verify). remy verify y CI terminan con remy test consumers, los demás repositorios a los que este sirve, probados tal como los reciben: el script test:consumers de la app, nada por defecto; en remy-auth, el fixture de consumidor, que construye la app en blanco y un paquete propio a partir de la plataforma de este commit (el paquete desde un tarball con versión propia, las tareas desde una copia fuera de cualquier node_modules, el .npmrc de la plantilla) y ejecuta la instalación, las skills (instaladas una vez por lista fijada y luego reutilizadas), los invariantes de las herramientas, la comprobación, las comprobaciones rápidas de navegador, la documentación y una publicación de prueba (dry-run) (~1,5 a 3 min, sobre todo la red). Un cambio que rompería otro repositorio falla ahí, no en ese repositorio. Las pruebas se comprueban con tipos junto con la app (tsconfig.json incluye tests/). El nivel de Google es npm run test -- google (auditorías de Lighthouse, local; CI en cada push y tag) y npm run test -- cwv (Core Web Vitals en un Worker de Cloudflare desechable); el release ejecuta ambas.

Dónde se ejecutan las comprobaciones: el comportamiento propio del paquete compartido se comprueba una vez, en remy-auth, antes de cada release. Una app construida sobre el paquete ejecuta un conjunto de contrato (sus páginas renderizan, las páginas de sitio y de app se mantienen separadas, sus propias funcionalidades funcionan) más el nivel de Google en sus propias páginas de sitio, no toda la suite del paquete de nuevo. Mantén las comprobaciones baratas en lugar de quitarlas (el reloj de Playwright en lugar de esperas reales, una página de navegador por comprobación, workers en paralelo). Las comprobaciones recorren checkedLocales de @joeblew999/remy-ui/checks, que respeta CHECK_LOCALES. Cada paso indica cuánto tardó.

Traducciones

Una misma estructura en cada app, para que los mismos comandos remy i18n funcionen en todas partes (la regla sobre quién traduce es un solo redactor):

QuéInglésTraducción
Docsdocs/content/<site>/<page>.md o .mdx (estructura de Fumadocs)a su lado: <page>.<locale>.md o .mdx
Catálogos de UIel catálogo del idioma base de cada proyecto inlang (<dir>/project.inlang)el catálogo de cada idioma, según el pathPattern propio del proyecto (p. ej. messages/<locale>.json)

Dos flujos, uno para cada tipo de texto, con el mismo patrón en ambos: detectar sin conexión con git, traducir con el Claude Code de la máquina (headless, sin herramientas) y hacer commit. Ambos son TypeScript en el paquete (packages/ui/src/cli/i18n.ts, translate.ts: remy i18n check, remy i18n translate); las tareas de abajo les reenvían.

  • Mensajes de UI (Paraglide): los catálogos, los idiomas y el idioma base vienen del propio settings.json de inlang (I18N_INLANG, o si no, el único proyecto que git conoce). Se detectan: catálogos y claves que faltan (jq), {placeholders} perdidos o inventados (@lingual/i18n-check), claves cuyo inglés cambió desde el último commit del catálogo (git, jq), y las categorías de plural que necesita el idioma (Intl.PluralRules, tasks/i18n/messages/plurals.mjs: ninguna herramienta externa las comprueba).
  • Docs (Fumadocs): la carpeta es I18N_DOCS_DIR (por defecto docs/content); cada sitio con un i18n.json traduce cada página en inglés a cada uno de sus idiomas. Se detectan: páginas que faltan y desactualizadas (git), huérfanas, y el propio texto de Fumadocs UI (ui/<lang>.json frente a ui/en.json).
  • El traductor es el Claude Code en modo headless de la máquina (claude -p), sin herramientas, sin servidores MCP, skills ni ajustes de proyecto, y con salida estructurada: las claves de un catálogo vuelven como JSON y se combinan en el orden de claves del inglés; una página vuelve completa, y solo se escribe cuando es una página (page.ts). Los prompts están en packages/ui/src/cli/prompts.ts. Una llamada por idioma (mensajes) o por página (docs), I18N_JOBS a la vez.
  • Las comprobaciones no necesitan clave, ni red, pero sí el historial completo de git (un clon superficial se rechaza). Traducir usa el login de Claude de la máquina.
  • Una app sin proyecto inlang ni sitios de documentación recibe "nothing to check" y sale con 0.
ComandoHace
remy i18n checkAmbas comprobaciones; un WARNING y salida 0 mientras se programa (la comprobación la ejecuta), salida 1 con I18N_STRICT=1 (el release)
remy i18n check messagesLos huecos de los catálogos, y los idiomas que difieren de los de la plataforma (de unlisted); --list una línea por hueco (es missing nav_more)
remy i18n check docsLos huecos de la documentación; --list una línea por hueco (es stale docs/content/dev/gui.es.md)
remy i18n translateEn main: mensajes y luego documentación, un commit para cada uno; dev:land lo ejecuta, al final; a mano, con REMY_FLOW=hand delante
remy i18n translate messagesLos huecos de los catálogos, un idioma por llamada al agente
remy i18n translate docs [files]Los huecos de la documentación, una página por llamada al agente; los archivos de traducción indicados se rehacen por completo

Ajustes: I18N_MODEL (por defecto sonnet), I18N_JOBS (4), I18N_COMMIT=0 (deja el cambio sin commit para revisarlo), I18N_BRANCH (main).

Cloudflare

npx remy cloudflare <command>; cada uno indica en su comentario de cabecera si es local o remoto (y de producción).

ComandoHace
deployConstruye con DEPLOY_ORIGIN, sube, espera a la nueva versión (the wait for the new version) y luego ejecuta su comprobación smoke en vivo. Sin pruebas antes: dev:promote lo ejecuta después de las comprobaciones del flujo. Se niega cuando un binding de D1, KV o R2 nombra un recurso que no existe: Wrangler crearía uno durante el despliegue, y crear recursos corresponde al propietario (preview se niega del mismo modo)
preview [alias] [--keep]Despliega este commit como un Worker desechable <worker>-check-<commit> (producción intacta), ejecuta el nivel 1 contra él y lo elimina; --keep lo conserva
preview-delete [name]Lista los Workers de comprobación, o elimina uno por nombre; nunca el Worker de producción
urls [origin]Imprime las páginas y /healthz del origen de producción (o de uno dado), para informes
stagingdeploy para el entorno de staging: construye con CLOUDFLARE_ENV=staging (env.staging en la configuración de Wrangler) y sube su propio Worker en origins.staging; producción queda intacta. Lo que staging permite más allá de producción está en la tabla de entornos de la app
versions [origins…]Pregunta a cada despliegue (los orígenes de remy.config.ts, o los orígenes dados) qué está ejecutando: servicio, entorno, commit, cuán lejos está eso de este checkout, cuándo se desplegó. Se pregunta, nunca se recuerda
docs-deployEl Worker de la documentación: construye, despliega y publica sus páginas en el índice de Ask AI; dev:promote lo ejecuta
npm run test -- remote [origin]El nivel 1 contra un despliegue, por una persona (REMY_FLOW=hand): los despliegues del flujo terminan con el smoke en vivo, y GitHub ejecuta la suite
npm run observe -- events|ai-usage|ai-check|gatewayWorkers Logs almacenados, uso de AI Gateway, comprobación de la configuración de IA, los ajustes del gateway (herramientas)
npm run upgrade -- <version>Mueve una app a una misma publicación compartida: la versión del paquete y la tag del flujo de trabajo de CI juntas, y luego las comprobaciones de la instalación

Las credenciales vienen del login de Wrangler, o del llavero a través de fnox (herramientas).

Planes

La regla de los planes (cómo trabajamos) como comandos (npm run plans -- …), igual en todos los proyectos: .plans/now.md es la única lista ordenada, .plans/*.md los pocos archivos de plan abiertos, .plans/done/ y .plans/parked/ el resto, .plans/stability-log.md opcional. Un proyecto sin .plans/ pasa plans check y no tiene nada que listar.

ComandoHace
statusLos elementos abiertos de now.md por sección, los archivos de plan abiertos con su primera línea Status:/Closed/Parked y su último commit (STALE a los 14 días), los planes aparcados. --json para agentes
checknow.md existe, cada enlace relativo a un .md bajo .plans/ resuelve, ningún .md fuera de .plans/, done/ y parked/, cada archivo de plan abierto enlazado desde now.md. Instantánea, sin red; la comprobación la ejecuta primero
close <plan> "<lo publicado>"Añade Closed <hoy>: ... bajo el título, lo mueve a done/ (desde .plans/ o parked/; git mv si está versionado), reapunta los enlaces relativos hacia él en .plans/, docs/, packages/*/README.md y los *.md de la raíz, y sus propios enlaces desde la carpeta nueva, y tacha su elemento en now.md
park <plan> "<por qué>"Lo mismo hacia parked/ con Parked <hoy>: ...
open <plan> "<por qué ahora>"Un plan aparcado que se retoma: lo mismo desde parked/ de vuelta a .plans/ con Opened <hoy>: ...; añade su elemento a now.md tú mismo

close y park cambian archivos y preparan el movimiento, pero nunca hacen commit: lee git diff y luego npm run dev:change.

On this page