agent-cloud-kit 0.0.0-stage → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/state.js ADDED
@@ -0,0 +1,147 @@
1
+ /**
2
+ * @file Machine fingerprint and the per-repo state file that records what setup last did.
3
+ */
4
+ import { createHash } from "node:crypto";
5
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
6
+ import { basename, join } from "node:path";
7
+ import { TOOLS } from "./detect.js";
8
+ /** Directory, inside the tools dir, that holds one state file per repo. */
9
+ const STATE_DIR = "state";
10
+ /** Prefix of every fingerprint. */
11
+ const FINGERPRINT_PREFIX = "sha256-";
12
+ /** Hash algorithm used for fingerprints. */
13
+ const HASH_ALGORITHM = "sha256";
14
+ /** Indentation of the state file. */
15
+ const JSON_INDENT = 2;
16
+ /**
17
+ * Serializes a value as JSON with object keys sorted recursively; arrays keep their order.
18
+ *
19
+ * @param value - Any JSON-compatible value.
20
+ * @returns A canonical JSON string.
21
+ */
22
+ function stableJson(value) {
23
+ if (Array.isArray(value))
24
+ return `[${value.map(stableJson).join(",")}]`;
25
+ if (typeof value === "object" && value !== null) {
26
+ const entries = Object.entries(value)
27
+ .filter(([, v]) => v !== undefined)
28
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
29
+ .map(([k, v]) => `${JSON.stringify(k)}:${stableJson(v)}`);
30
+ return `{${entries.join(",")}}`;
31
+ }
32
+ return JSON.stringify(value) ?? "null";
33
+ }
34
+ /**
35
+ * Hashes everything that decides how the machine is built, so a change asks for a rebuild.
36
+ * `session`, `requiredEnv` and `notes` are left out: changing them does not need a rebuild.
37
+ *
38
+ * @param config - Repo config.
39
+ * @param versions - Pinned tool versions.
40
+ * @param images - Docker images to pull.
41
+ * @param packageVersion - Version of this package.
42
+ * @returns `sha256-<hex>`.
43
+ * @example
44
+ * fingerprint(config, { node: "20.1.0" }, ["postgres:16"], "1.0.0"); // "sha256-ab12..."
45
+ */
46
+ export function fingerprint(config, versions, images, packageVersion) {
47
+ const { version, aptRepos, apt, docker, setup } = config;
48
+ const json = stableJson({ version, aptRepos, apt, docker, setup, versions, images, packageVersion });
49
+ return FINGERPRINT_PREFIX + createHash(HASH_ALGORITHM).update(json).digest("hex");
50
+ }
51
+ /**
52
+ * Locates the state file of a repo.
53
+ *
54
+ * @param toolsDir - Tools directory.
55
+ * @param repo - Repo root path.
56
+ * @returns `<toolsDir>/state/<basename(repo)>.json`.
57
+ * @example
58
+ * statePath("/opt/agent-cloud-kit", "/home/user/app"); // "/opt/agent-cloud-kit/state/app.json"
59
+ */
60
+ export function statePath(toolsDir, repo) {
61
+ return join(toolsDir, STATE_DIR, `${basename(repo)}.json`);
62
+ }
63
+ /**
64
+ * Writes the state file atomically (temp file in the same dir, then rename).
65
+ *
66
+ * @param toolsDir - Tools directory.
67
+ * @param repo - Repo root path.
68
+ * @param state - State to record.
69
+ * @example
70
+ * writeState("/opt/agent-cloud-kit", "/home/user/app", state);
71
+ */
72
+ export function writeState(toolsDir, repo, state) {
73
+ const file = statePath(toolsDir, repo);
74
+ mkdirSync(join(toolsDir, STATE_DIR), { recursive: true });
75
+ const temp = `${file}.${process.pid}.tmp`;
76
+ writeFileSync(temp, `${JSON.stringify(state, null, JSON_INDENT)}\n`);
77
+ renameSync(temp, file);
78
+ }
79
+ /**
80
+ * Tells whether a value is a plain JSON object.
81
+ *
82
+ * @param value - Any parsed JSON value.
83
+ * @returns True for non-null, non-array objects.
84
+ */
85
+ function isRecord(value) {
86
+ return typeof value === "object" && value !== null && !Array.isArray(value);
87
+ }
88
+ /**
89
+ * Tells whether a parsed value has the shape of a `StepResult`.
90
+ *
91
+ * @param value - Any parsed JSON value.
92
+ * @returns True for a string `name`, a boolean `ok`, a finite numeric `ms` and an optional string `error`.
93
+ */
94
+ function isStepResult(value) {
95
+ if (!isRecord(value))
96
+ return false;
97
+ return (typeof value.name === "string" &&
98
+ typeof value.ok === "boolean" &&
99
+ typeof value.ms === "number" &&
100
+ Number.isFinite(value.ms) &&
101
+ (value.error === undefined || typeof value.error === "string"));
102
+ }
103
+ /**
104
+ * Tells whether a parsed value is a map of known tools to version strings.
105
+ *
106
+ * @param value - Any parsed JSON value.
107
+ * @returns True when every key is a tool and every value a string.
108
+ */
109
+ function isVersionMap(value) {
110
+ if (!isRecord(value))
111
+ return false;
112
+ return Object.entries(value).every(([k, v]) => TOOLS.includes(k) && typeof v === "string");
113
+ }
114
+ /**
115
+ * Tells whether a parsed value has the shape of a `MachineState`; a missing `resolved` is accepted.
116
+ *
117
+ * @param value - Any parsed JSON value.
118
+ * @returns True when all required fields are present with the right types.
119
+ */
120
+ function isMachineState(value) {
121
+ if (!isRecord(value))
122
+ return false;
123
+ return (typeof value.fingerprint === "string" &&
124
+ typeof value.packageVersion === "string" &&
125
+ typeof value.finishedAt === "string" &&
126
+ Array.isArray(value.steps) &&
127
+ value.steps.every(isStepResult) &&
128
+ (value.resolved === undefined || isVersionMap(value.resolved)));
129
+ }
130
+ /**
131
+ * Reads a repo's state file; never throws because it runs at session start.
132
+ *
133
+ * @param toolsDir - Tools directory.
134
+ * @param repo - Repo root path.
135
+ * @returns The state, or undefined when the file is missing, unreadable, invalid or incomplete.
136
+ * @example
137
+ * readState("/opt/agent-cloud-kit", "/home/user/app"); // undefined before the first setup
138
+ */
139
+ export function readState(toolsDir, repo) {
140
+ try {
141
+ const parsed = JSON.parse(readFileSync(statePath(toolsDir, repo), "utf8"));
142
+ return isMachineState(parsed) ? { ...parsed, resolved: parsed.resolved ?? {} } : undefined;
143
+ }
144
+ catch {
145
+ return undefined;
146
+ }
147
+ }
package/dist/steps.js ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @file Step results: runs one unit of work and reports its outcome without ever throwing.
3
+ */
4
+ /**
5
+ * Runs a step, timing it and converting any failure into a result.
6
+ *
7
+ * @param name - Name reported in the result.
8
+ * @param fn - Work to run.
9
+ * @returns The outcome; this promise never rejects.
10
+ * @example
11
+ * await step("pull images", async () => pull()); // { name: "pull images", ok: true, ms: 1200 }
12
+ */
13
+ export async function step(name, fn) {
14
+ const start = Date.now();
15
+ try {
16
+ await fn();
17
+ return { name, ok: true, ms: Date.now() - start };
18
+ }
19
+ catch (err) {
20
+ const error = err instanceof Error ? err.message : String(err);
21
+ return { name, ok: false, ms: Date.now() - start, error };
22
+ }
23
+ }
24
+ /** Prefix of the name given to a lane whose function rejected. */
25
+ const LANE_NAME_PREFIX = "lane ";
26
+ /**
27
+ * Runs lanes of steps concurrently and joins their results in lane order.
28
+ *
29
+ * @param laneFns - Lane functions, each resolving to the results of its own steps.
30
+ * @returns Results ordered by lane, then by position within the lane; never rejects.
31
+ * @example
32
+ * await lanes(async () => [await step("a", fa)], async () => [await step("b", fb)]);
33
+ */
34
+ export async function lanes(...laneFns) {
35
+ const groups = await Promise.all(laneFns.map(async (laneFn, index) => {
36
+ const start = Date.now();
37
+ try {
38
+ return await laneFn();
39
+ }
40
+ catch (err) {
41
+ const error = err instanceof Error ? err.message : String(err);
42
+ return [{ name: `${LANE_NAME_PREFIX}${index}`, ok: false, ms: Date.now() - start, error }];
43
+ }
44
+ }));
45
+ return groups.flat();
46
+ }
package/package.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
2
  "name": "agent-cloud-kit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0-rc.1",
4
+ "description": "Zero-dependency CLI that prepares Claude Code cloud sessions.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/ramon-sg/agent-cloud-kit.git"
10
+ },
11
+ "homepage": "https://github.com/ramon-sg/agent-cloud-kit#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/ramon-sg/agent-cloud-kit/issues"
14
+ },
15
+ "keywords": [
16
+ "cloud",
17
+ "sandbox",
18
+ "setup",
19
+ "session-start",
20
+ "cli"
21
+ ],
22
+ "bin": {
23
+ "agent-cloud-kit": "dist/cli.js"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "schema.json",
28
+ "skills",
29
+ "README.md",
30
+ "LICENSE"
31
+ ],
32
+ "engines": {
33
+ "node": ">=20"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public",
37
+ "provenance": true
38
+ },
39
+ "scripts": {
40
+ "build": "tsc",
41
+ "typecheck": "tsc -p tsconfig.test.json",
42
+ "test": "node --test 'test/**/*.test.ts'"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "20.19.43",
46
+ "typescript": "7.0.2"
47
+ }
48
+ }
package/schema.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "agent-cloud-kit config",
4
+ "description": "Contents of .claude/cloud.json for agent-cloud-kit.",
5
+ "type": "object",
6
+ "required": ["version"],
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "$schema": { "type": "string", "description": "Path or URL of this schema." },
10
+ "version": {
11
+ "type": "string",
12
+ "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.-]+)?(\\+[0-9A-Za-z.-]+)?$",
13
+ "description": "Exact agent-cloud-kit version to run (no ranges)."
14
+ },
15
+ "aptRepos": {
16
+ "type": "array",
17
+ "items": { "type": "string" },
18
+ "default": [],
19
+ "description": "HTTPS URLs of setup scripts that add APT repositories (piped to bash)."
20
+ },
21
+ "apt": {
22
+ "type": "array",
23
+ "items": { "type": "string" },
24
+ "default": [],
25
+ "description": "APT packages to install."
26
+ },
27
+ "docker": {
28
+ "type": "object",
29
+ "additionalProperties": false,
30
+ "default": { "pull": "auto" },
31
+ "properties": {
32
+ "pull": {
33
+ "description": "\"auto\" detects images, false disables pulling, or list images explicitly.",
34
+ "oneOf": [
35
+ { "const": "auto" },
36
+ { "const": false },
37
+ { "type": "array", "items": { "type": "string" } }
38
+ ],
39
+ "default": "auto"
40
+ }
41
+ }
42
+ },
43
+ "setup": {
44
+ "type": "array",
45
+ "items": { "type": "string" },
46
+ "default": [],
47
+ "description": "Shell commands run once when the environment is set up."
48
+ },
49
+ "session": {
50
+ "type": "array",
51
+ "items": { "type": "string" },
52
+ "default": [],
53
+ "description": "Shell commands run at the start of every session."
54
+ },
55
+ "requiredEnv": {
56
+ "type": "array",
57
+ "items": { "type": "string" },
58
+ "default": [],
59
+ "description": "Environment variables that must be set; a warning is shown when missing."
60
+ },
61
+ "domains": {
62
+ "type": "array",
63
+ "items": { "type": "string", "pattern": "^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)+$" },
64
+ "default": [],
65
+ "description": "Extra hosts the cloud environment must allow; doctor probes them and the manual lists them."
66
+ },
67
+ "notes": {
68
+ "type": "string",
69
+ "description": "What does and does not run in the cloud: free text, or a path, relative to the repo, of a text file inside it whose contents are used instead. The session prints it at start and the manual shows it."
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,202 @@
1
+ ---
2
+ name: cloud-ready
3
+ description: Prepara o verifica un repo Node/TypeScript para las sesiones en la nube de Claude Code (claude.ai/code). Usa `configure` en local, con el usuario, para escribir `.claude/cloud.json` y decidir qué corre en la nube; usa `verify` dentro de una sesión en la nube para comprobar la preparación, arreglarla y reportar en el PR.
4
+ argument-hint: "configure | verify"
5
+ metadata:
6
+ version: "1.0.0"
7
+ ---
8
+
9
+ # cloud-ready
10
+
11
+ Deja el repo listo para la nube de Claude Code y lo comprueba en la nube real. No impone una
12
+ forma de testear: se respeta lo que el repo ya hace. Un repo sin tests solo necesita la máquina
13
+ preparada.
14
+
15
+ - **Modo:** el primer argumento. `configure` corre en local con el usuario; `verify`, en la nube.
16
+ Sin argumento: `verify` si `CLAUDE_CODE_REMOTE=true`, si no `configure`.
17
+ - **Antes de empezar:** lee `reference/knowledge.md` (lo que se sabe de la nube) y, si el repo
18
+ tiene tests, `reference/compat.md` (cómo hacerlos compatibles).
19
+ - **El archivo de `notes`:** el que diga `notes` en `.claude/cloud.json`; si no hay, usa
20
+ `.claude/cloud-notes.md` y pon `"notes": ".claude/cloud-notes.md"` en la config. Se muestra al
21
+ empezar cada sesión en la nube y se copia en `docs/agent-cloud.md`: que sea corto.
22
+ - **La CLI:** `agent-cloud-kit <comando>`. Si no está en el `PATH`, usa `npx -y agent-cloud-kit <comando>`.
23
+
24
+ ## Archivos de la preparación
25
+
26
+ Son los únicos que `verify` puede cambiar:
27
+
28
+ - `.claude/cloud.json`
29
+ - el archivo de `notes`
30
+ - `docs/agent-cloud.md` (lo regenera `agent-cloud-kit manual`; a mano solo la sección "Hallazgos")
31
+ - `.agents/skills/test-audit/project.md` (solo su sección "Nube", y solo si el archivo existe)
32
+
33
+ ## `configure` (local, con el usuario)
34
+
35
+ Si `CLAUDE_CODE_REMOTE=true`, detente: este modo necesita al usuario en una terminal local.
36
+ No le pidas al usuario una lista de variables secretas ni de dominios: anota en `requiredEnv` y
37
+ `domains` solo lo que veas que el repo necesita en la nube; lo que falte lo detecta `verify` (ver
38
+ "Dominios y variables nuevos").
39
+
40
+ 1. **Lee qué usa el repo:** `package.json` (scripts, `packageManager`, `engines`), el lockfile,
41
+ `.tool-versions`, `mise.toml`, `.nvmrc`, archivos de Docker compose, `Dockerfile`,
42
+ `.env.example`, la CI (`.github/workflows/`) y el README. Lee `.claude/cloud.json` si existe.
43
+ 2. **Escribe la preparación en `.claude/cloud.json`** (no toques `version` ni `$schema`):
44
+ - `setup`: la instalación de dependencias con el gestor del repo y el lockfile congelado
45
+ (`npm ci`, `pnpm install --frozen-lockfile`, `bun install --frozen-lockfile`…), más lo que
46
+ haga falta una sola vez (p. ej. navegadores de Playwright). Node y los gestores fijados en
47
+ `.tool-versions`, `mise.toml`, `.nvmrc` o `packageManager` los instala el paquete solo: no
48
+ los agregues. Cada paso tiene 180 s y el setup entero debe quedar bajo ~5 min.
49
+ - `apt` y `aptRepos`: solo lo que el repo necesite y la imagen no traiga.
50
+ - `session`: comandos rápidos de cada sesión (2 min como máximo cada uno, sin depender de la red).
51
+ - `domains`: los hosts que el setup o los tests necesitan y que no están en la lista por
52
+ defecto de gestores de paquetes.
53
+ - `requiredEnv`: solo los nombres de las variables secretas que de verdad hacen falta en la nube.
54
+ - `notes`: la ruta del archivo de `notes`.
55
+ 3. **Busca los tests del repo:** scripts `test*` y `e2e*` de `package.json`, configuración de
56
+ runners (`vitest.config.*`, `jest.config.*`, `playwright.config.*`, `.mocharc*`),
57
+ Testcontainers en las dependencias, compose de tests y los jobs de la CI que corren tests.
58
+ 4. **Sin tests:** díselo al usuario y no hagas nada más con tests. Anota en `notes` que el repo
59
+ no tiene tests y que en la nube solo se prepara la máquina. Sigue en el paso 7.
60
+ 5. **Con tests:** agrúpalos (p. ej. unitarios, con base de datos, de navegador) y, para cada
61
+ grupo, di si correría en la nube tal cual. Si no, propón, en este orden
62
+ (`reference/compat.md`):
63
+ 1. Adaptar la preparación: `setup`, `session`, `domains`, `requiredEnv`.
64
+ 2. Solo si eso no basta, una variante para la nube: un script aparte (p. ej. `cloud:services`)
65
+ o una rama por `CLAUDE_CODE_REMOTE` en un script existente.
66
+ 3. Si nada de eso alcanza, déjalo fuera de la nube y explica por qué.
67
+
68
+ Muestra cada propuesta con el cambio exacto (archivo y contenido).
69
+ 6. **Pregunta y aplica solo lo aprobado.** Un cambio fuera de la config (scripts, compose,
70
+ configuración de tests) necesita un sí explícito del usuario. Nunca cambies lo que un test
71
+ comprueba ni crees scripts de tests nuevos sin aprobación.
72
+ 7. **Escribe el archivo de `notes`:** qué correr en la nube (el comando tal como lo corre el
73
+ repo, lo que hay que levantar antes, cuánto tarda y si va en segundo plano), qué no y por qué.
74
+ Por ejemplo:
75
+
76
+ ```markdown
77
+ En la nube: `npm test` (~1 min). Antes de `npm run test:db`, `docker compose up -d db`
78
+ (~3 min, en segundo plano). No corre `npm run test:e2e:prod`: pega a una API externa que el
79
+ entorno no permite.
80
+ ```
81
+
82
+ 8. **Si existe `.agents/skills/test-audit/project.md`:** escribe o actualiza su sección "Nube"
83
+ con el mismo resumen; no toques el resto del archivo.
84
+ 9. **Corre `agent-cloud-kit manual`** y muéstrale al usuario la sección "Configurar el entorno en
85
+ claude.ai" de `docs/agent-cloud.md` (setup script, dominios, variables). Dile que ese
86
+ documento se irá actualizando: si la skill, en local o en la nube, detecta que hace falta un
87
+ dominio o una variable nueva, lo agrega ahí y se lo dice.
88
+ 10. **No hagas commit:** `agent-cloud-kit prepare` lo hace al salir. Si te invocaron fuera de
89
+ `prepare`, pregunta al usuario.
90
+ 11. **Termina diciéndole al usuario que salga:** dile al usuario, con estas palabras o parecidas,
91
+ "configure terminó: sal de claude con `/exit` y `prepare` sigue (commit, push y PR)". Si te
92
+ invocaron fuera de `prepare`, omite este paso.
93
+
94
+ ## `verify` (en la nube)
95
+
96
+ Solo con `CLAUDE_CODE_REMOTE=true`; si no, detente y dilo. El prompt trae la rama de `prepare`
97
+ (`chore/cloud-ready-…`) y puede traer el número del PR. La sesión trabaja en su propia rama
98
+ `claude/…`: no la cambies; al final empujas a la rama de `prepare`.
99
+
100
+ 1. **Contexto:** lee `.claude/cloud.json`, el archivo de `notes` y `docs/agent-cloud.md`. Anota
101
+ las líneas `agent-cloud-kit:` de falla que mostró el inicio de la sesión ("did not run",
102
+ pasos fallidos, "another configuration", variables faltantes).
103
+ 2. **Corre `agent-cloud-kit doctor`** y guarda la salida. Si `agent-cloud-kit` no está en el
104
+ `PATH`, el setup no corrió: usa `npx -y agent-cloud-kit doctor` y repórtalo.
105
+ 3. **Comprueba la preparación:** dependencias instaladas (p. ej. `node_modules` existe), las
106
+ versiones fijadas (`node -v` y los demás gestores contra `.tool-versions`, `mise.toml`,
107
+ `.nvmrc` o `packageManager`), las variables de `requiredEnv` presentes (solo nombres; nunca
108
+ imprimas valores) y Docker, si el repo lo usa.
109
+ 4. **Tests, solo si las `notes` dicen qué correr en la nube.** Córrelos tal como el repo los
110
+ corre, con lo que haya que levantar antes. Las corridas largas van en segundo plano, con la
111
+ salida a un log que sigues hasta que terminen. Si las `notes` no dicen qué correr, no corras
112
+ tests y dilo en el reporte.
113
+ 5. **Si algo falla,** diagnostica con `reference/knowledge.md` y `reference/compat.md`, y
114
+ clasifica:
115
+ - **Se arregla en la preparación:** cambia solo los archivos de la preparación y vuelve a
116
+ comprobar. Un cambio de `setup` no se re-ejecuta en esta sesión (el setup ya corrió): corre
117
+ el comando a mano para probarlo y anota que la máquina guardada se rehará en una sesión nueva.
118
+ - **Falta un dominio o una variable:** sigue "Dominios y variables nuevos" y deja de intentar
119
+ arreglos que dependan de eso.
120
+ - **Solo se cambia en claude.ai** (entorno por defecto con `/remote-env`, rehacer la máquina
121
+ editando el setup script, el Claude GitHub App): no lo intentes; deja la instrucción exacta
122
+ para el reporte y deja de intentar arreglos.
123
+ - **Requiere cambiar código, tests o scripts:** no lo cambies; deja el cambio propuesto
124
+ (archivo y diff) para el comentario del PR.
125
+
126
+ Hasta **3 intentos** de arreglo en total. Tras el tercero, detente y reporta lo que queda.
127
+ 6. **Hallazgos:** ver "Hallazgos", abajo.
128
+ 7. **Corre `agent-cloud-kit manual`.**
129
+ 8. **Antes del commit,** revisa `git diff --name-only HEAD` y `git status --porcelain`. Todo lo
130
+ que no sea un archivo de la preparación se revierte con `git checkout -- <archivo>` (si está
131
+ versionado) o se borra (si es nuevo y lo creaste tú). Un cambio necesario al código, tests o
132
+ scripts queda como propuesta en el comentario del PR.
133
+ 9. **Commit y push:** agrega solo los archivos de la preparación que cambiaron, por nombre
134
+ (`git add -- .claude/cloud.json <archivo de notes> docs/agent-cloud.md`, más
135
+ `.agents/skills/test-audit/project.md` si lo tocaste); si hay algo en stage,
136
+ `git commit -m "chore(cloud): verify cloud-ready"`. Luego
137
+ `git push origin HEAD:<rama de prepare>`. Si el push se rechaza, `git fetch origin <rama de
138
+ prepare>`, `git rebase FETCH_HEAD` (el clon es parcial: `origin/<rama>` puede no existir) y
139
+ reintenta una vez.
140
+ 10. **Comentario en el PR,** por REST con `gh api` (`gh pr …` usa GraphQL y está bloqueado;
141
+ `gh auth status` dice que el token es inválido, pero `gh api` funciona):
142
+ - `owner` y `repo`: de `git remote get-url origin` (las dos últimas partes de la ruta, sin `.git`).
143
+ - Número del PR: el del prompt; si no viene,
144
+ `gh api "repos/{owner}/{repo}/pulls?head={owner}:{rama de prepare}&state=open" --jq '.[0].number'`.
145
+ - Comentario: escribe el cuerpo en un archivo temporal y
146
+ `gh api repos/{owner}/{repo}/issues/{n}/comments -f body="$(cat <archivo>)"`.
147
+ - Si no hay PR, el resultado queda solo en `docs/agent-cloud.md` y en tu resumen final.
148
+ - No saques el PR de draft: lo decide el usuario.
149
+ 11. **Resumen final** en la sesión, con lo mismo que el comentario.
150
+
151
+ ### El comentario del PR
152
+
153
+ ```markdown
154
+ ## cloud-ready verify
155
+
156
+ **Comprobado:** `agent-cloud-kit doctor` (salida 0 / qué falló), dependencias, versiones, variables.
157
+ **Corrió:** cada comando, su resultado y cuánto tardó.
158
+ **No corrió:** qué y por qué.
159
+ **Cambiar en claude.ai:** la instrucción exacta (dónde y qué), o "nada".
160
+ **Cambios propuestos al código o los tests:** archivo y diff, o "ninguno".
161
+ **Arreglado en la preparación:** archivos y por qué. Intentos: n de 3.
162
+ **Hallazgos:** los nuevos, o "ninguno".
163
+ ```
164
+
165
+ ## Dominios y variables nuevos
166
+
167
+ En ambos modos, cuando algo falla porque falta un dominio en la red del entorno (el proxy
168
+ rechaza la conexión, `403` o `CONNECT` fallido hacia un host) o una variable de entorno (el
169
+ inicio de la sesión la marca como faltante, o el error nombra una variable vacía):
170
+
171
+ 1. Agrega el host a `domains` o el nombre a `requiredEnv` en `.claude/cloud.json` (solo el
172
+ nombre; nunca un valor).
173
+ 2. Corre `agent-cloud-kit manual`: `docs/agent-cloud.md` queda con lo nuevo.
174
+ 3. **Díselo al usuario en la sesión, en ese momento**, sin esperar al final: qué falta, por qué
175
+ (el comando y el error) y qué hacer, con estas palabras o parecidas: "Falta `<X>` en el entorno
176
+ de claude.ai. Agrégalo en <Network access / Variables de entorno> (está en
177
+ `docs/agent-cloud.md`), abre una sesión nueva y corre `/cloud-ready verify`".
178
+ 4. En `verify`, ponlo también en "Cambiar en claude.ai" del comentario del PR.
179
+
180
+ Un dominio que ya está en `domains`, o una variable que ya está en `requiredEnv`, no se agrega
181
+ de nuevo: si sigue faltando, el usuario aún no actualizó el entorno; recuérdaselo.
182
+
183
+ ## Hallazgos
184
+
185
+ En ambos modos: todo hecho nuevo sobre la nube (algo que no está en `reference/knowledge.md`,
186
+ o que lo contradice) va a la sección `## Hallazgos` de `docs/agent-cloud.md`, fuera de las
187
+ marcas de `agent-cloud-kit` (`manual` la conserva). Una línea por hallazgo, marcada
188
+ "proponer al paquete":
189
+
190
+ ```markdown
191
+ - (proponer al paquete) `docker compose` necesita `--wait` para que la base acepte conexiones.
192
+ Visto en `verify`, 2026-11-02, con `docker compose up -d db`.
193
+ ```
194
+
195
+ No edites `reference/knowledge.md` del repo: se reemplaza al actualizar el paquete.
196
+
197
+ ## Límites
198
+
199
+ - `verify` nunca cambia código, tests ni scripts; `configure`, solo con aprobación del usuario.
200
+ - Nunca imprimas ni escribas el valor de una variable secreta; solo su nombre.
201
+ - Nada de `gh pr` ni `gh issue` en la nube: usa `gh api` (REST).
202
+ - No configures claude.ai: da la instrucción exacta y que la haga el usuario.
@@ -0,0 +1,110 @@
1
+ # Hacer compatible con la nube lo que el repo ya tiene
2
+
3
+ De menos a más invasivo. Prueba cada nivel antes de pasar al siguiente, y propón al usuario el
4
+ cambio exacto: en `configure` se aplica solo lo aprobado; en `verify` solo se tocan los archivos
5
+ de la preparación y el resto queda propuesto en el PR.
6
+
7
+ ## 1. Adaptar la preparación
8
+
9
+ Todo va en `.claude/cloud.json`; no toca código ni tests.
10
+
11
+ | Lo que usa el repo | Qué hacer |
12
+ | --- | --- |
13
+ | Dependencias de npm, pnpm, Yarn o Bun | En `setup`, con el lockfile congelado: `npm ci`, `pnpm install --frozen-lockfile`, `yarn install --immutable`, `bun install --frozen-lockfile`. Node y el gestor fijados los instala el paquete. |
14
+ | Servicios de Docker compose (base de datos, cola) | `docker.pull: "auto"` baja las imágenes en el setup. Levántalos en el comando que corre los tests (`agent-cloud-kit docker && docker compose up -d --wait db`) o, si son rápidos, en `session` (2 min como máximo) con ese mismo comando. `agent-cloud-kit session` arranca `dockerd` en segundo plano sin esperar a que responda; `agent-cloud-kit docker` lo arranca si hace falta y espera hasta 60 s a que `docker info` responda (sale con error si no). Para comprobarlo a mano: `docker info`. |
15
+ | Testcontainers | Necesita Docker respondiendo: corre `agent-cloud-kit docker` antes de los tests (`agent-cloud-kit session` lo arranca pero no espera) y que la imagen esté en el setup: lístala en `docker.pull` si no está en un compose. Si construye imágenes, el CA del proxy entra con `CLOUD_PROXY_CA` como secreto. |
16
+ | Playwright | Los navegadores de la versión del repo en `setup` (`npx playwright install --with-deps chromium`); suma tiempo al setup (límite ~5 min) y puede pedir `domains` (`cdn.playwright.dev`, `playwright.download.prss.microsoft.com`). La imagen ya trae navegadores en `/opt/pw-browsers`: si la versión coincide, alcanza con eso. Corre contra el servidor local de la sesión. |
17
+ | `localhost` en URLs de tests o servicios | No hay IPv6: si algo resuelve `localhost` a `::1` y falla, usa `127.0.0.1` (en la config del test, por variable de entorno si el repo lo permite). |
18
+ | Variables secretas (API keys de prueba) | Nombres en `requiredEnv`; el usuario pone los valores en el entorno de claude.ai. Recuerda que las lee cualquiera que use el entorno. |
19
+ | Hosts externos que el setup o los tests necesitan | En `domains`; el manual los lista para la red del entorno. |
20
+ | Corridas largas (más de 2 min) | En segundo plano con la salida a un log, o `BASH_DEFAULT_TIMEOUT_MS` y `BASH_MAX_TIMEOUT_MS` en las variables del entorno. Anota en `notes` cuánto tardan. |
21
+
22
+ ## 2. Bifurcar una variante para la nube
23
+
24
+ Solo si adaptar la preparación no basta, y con aprobación del usuario.
25
+
26
+ - **Script aparte:** p. ej. `cloud:services` en `package.json`, que prende Docker
27
+ (`agent-cloud-kit docker`) y levanta lo que los tests necesitan. El script de tests del repo
28
+ no cambia.
29
+ - **Rama por `CLAUDE_CODE_REMOTE`:** dentro de un script existente,
30
+ `if [ "$CLAUDE_CODE_REMOTE" = true ]; then …; fi`, para lo que en la nube se hace distinto
31
+ (p. ej. no abrir un navegador, usar `127.0.0.1`). Que en local se comporte igual que antes.
32
+
33
+ ## 3. Lo que no corre en la nube
34
+
35
+ Déjalo fuera y anótalo en `notes` con el porqué:
36
+
37
+ - APIs externas que el entorno no permite o que no deben recibir tráfico de pruebas.
38
+ - Secretos que el entorno no tiene (o que no deben estar en un entorno compartido).
39
+ - Lo que necesita abrir puertos hacia fuera o una vista previa (no existen).
40
+ - Recursos de dominios externos cargados por Playwright (bug abierto, ver `knowledge.md`).
41
+
42
+ ## Ejemplo: vitest, Postgres en compose y Playwright
43
+
44
+ El repo usa npm, `vitest` para unitarios (`npm test`), `vitest` con Postgres de
45
+ `compose.yml` para integración (`npm run test:db`, espera `DATABASE_URL`) y Playwright para e2e
46
+ (`npm run test:e2e`, levanta el servidor con `webServer`). Una prueba e2e de pagos llama a la
47
+ sandbox de un proveedor externo.
48
+
49
+ Propuesta: unitarios tal cual; integración adaptando la preparación (la imagen se baja en el
50
+ setup y la base se levanta antes de los tests); e2e con los navegadores en el setup; la prueba
51
+ de pagos fuera (necesita una API externa y su secreto).
52
+
53
+ `.claude/cloud.json`:
54
+
55
+ ```json
56
+ {
57
+ "$schema": "https://unpkg.com/agent-cloud-kit@1.0.0/schema.json",
58
+ "version": "1.0.0",
59
+ "aptRepos": [],
60
+ "apt": [],
61
+ "docker": { "pull": "auto" },
62
+ "setup": ["npm ci", "npx playwright install --with-deps chromium"],
63
+ "session": [],
64
+ "requiredEnv": [],
65
+ "domains": ["cdn.playwright.dev", "playwright.download.prss.microsoft.com"],
66
+ "notes": ".claude/cloud-notes.md"
67
+ }
68
+ ```
69
+
70
+ `.claude/cloud-notes.md`:
71
+
72
+ ```markdown
73
+ En la nube corren:
74
+ - `npm test` (unitarios, ~1 min).
75
+ - `npm run test:db`, antes `docker compose up -d --wait db` y
76
+ `DATABASE_URL=postgres://app:app@127.0.0.1:5432/app` (~3 min, en segundo plano).
77
+ - `npm run test:e2e -- --grep-invert @payments` (Playwright contra el servidor local, ~6 min,
78
+ en segundo plano).
79
+
80
+ No corre `@payments`: llama a la sandbox del proveedor de pagos, que el entorno no permite y
81
+ cuyo secreto no está en el entorno.
82
+ ```
83
+
84
+ ## Ejemplo: repo sin tests
85
+
86
+ Solo se prepara la máquina; nada de tests.
87
+
88
+ `.claude/cloud.json`:
89
+
90
+ ```json
91
+ {
92
+ "$schema": "https://unpkg.com/agent-cloud-kit@1.0.0/schema.json",
93
+ "version": "1.0.0",
94
+ "aptRepos": [],
95
+ "apt": [],
96
+ "docker": { "pull": false },
97
+ "setup": ["pnpm install --frozen-lockfile"],
98
+ "session": [],
99
+ "requiredEnv": [],
100
+ "domains": [],
101
+ "notes": ".claude/cloud-notes.md"
102
+ }
103
+ ```
104
+
105
+ `.claude/cloud-notes.md`:
106
+
107
+ ```markdown
108
+ El repo no tiene tests: en la nube solo se prepara la máquina (pnpm y dependencias). `verify`
109
+ comprueba `agent-cloud-kit doctor`, `node_modules` y las versiones fijadas.
110
+ ```