agent-cloud-kit 1.0.0-rc.0 → 1.0.0-rc.2

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.
@@ -1,31 +1,16 @@
1
1
  /**
2
- * Formats a list for a prompt line.
3
- *
4
- * @param items Items.
5
- * @param none Text when there are no items.
6
- * @returns The items joined by commas, or `none`.
7
- * @example
8
- * list(["A", "B"], "ninguna"); // "A, B"
2
+ * @file Prompts `prepare` hands to Claude, in Spanish: they invoke the `cloud-ready` skill.
9
3
  */
10
- function list(items, none) {
11
- return items.length > 0 ? items.join(", ") : none;
12
- }
13
4
  /**
14
- * Prompt for the local session: `configure` mode with the user's answers.
5
+ * Prompt for the local session: `configure` mode. `prepare` asks nothing first; the skill detects
6
+ * secrets and domains itself.
15
7
  *
16
- * @param answers What the user answered.
17
8
  * @returns The prompt.
18
9
  * @example
19
- * configurePrompt({ secrets: ["A"], domains: [] }).startsWith("/cloud-ready configure"); // true
10
+ * configurePrompt(); // "/cloud-ready configure"
20
11
  */
21
- export function configurePrompt(answers) {
22
- return [
23
- "/cloud-ready configure",
24
- "",
25
- "Respuestas del usuario (no las vuelvas a preguntar):",
26
- `- Variables secretas: ${list(answers.secrets, "ninguna")}`,
27
- `- Dominios extra: ${list(answers.domains, "ninguno")}`,
28
- ].join("\n");
12
+ export function configurePrompt() {
13
+ return "/cloud-ready configure";
29
14
  }
30
15
  /**
31
16
  * Prompt for the cloud session: `verify` mode with the prepare branch and, when known, the PR.
@@ -1,70 +1,5 @@
1
1
  /**
2
- * @file The questions `prepare` asks: only secret names and extra domains (tests are handled by
3
- * the skill with the user).
2
+ * @file How `prepare` asks the user a question. It asks nothing up front: secrets and domains are
3
+ * detected by the `cloud-ready` skill, locally or in the cloud, when they are needed.
4
4
  */
5
- import { isHostName } from "../config.js";
6
- import { OUTPUT_PREFIX } from "../constants.js";
7
- /** Answer that leaves a list empty. */
8
- const EMPTY_LIST_ANSWER = "-";
9
- /**
10
- * Turns a raw answer into a list.
11
- *
12
- * @param answer Raw answer: comma-separated, empty to accept, `-` for none.
13
- * @param suggestion Suggested items, used when the answer is empty.
14
- * @returns The items.
15
- * @example
16
- * parseList("a, b", []); // ["a", "b"]
17
- */
18
- function parseList(answer, suggestion) {
19
- const text = answer.trim();
20
- if (text === "")
21
- return suggestion;
22
- if (text === EMPTY_LIST_ANSWER)
23
- return [];
24
- return text.split(",").map((item) => item.trim()).filter((item) => item !== "");
25
- }
26
- /** Example host shown when a typed domain is not a host name. */
27
- const HOST_EXAMPLE = "api.example.com";
28
- /**
29
- * Keeps the typed domains that are host names (the rule of `domains` in the config). Each invalid
30
- * entry is asked again once; if it is still not a host name it is dropped with a printed note.
31
- *
32
- * @param ask Function that asks one question.
33
- * @param typed Domains as typed.
34
- * @param print Prints one line.
35
- * @returns The valid domains.
36
- * @example
37
- * await validDomains(ask, ["api.example.com"], console.log); // ["api.example.com"]
38
- */
39
- async function validDomains(ask, typed, print) {
40
- const domains = [];
41
- for (const entry of typed) {
42
- if (isHostName(entry)) {
43
- domains.push(entry);
44
- continue;
45
- }
46
- const retry = (await ask(`${JSON.stringify(entry)} is not a host name (like ${HOST_EXAMPLE}); type it again, or ${EMPTY_LIST_ANSWER} to drop it`, "")).trim();
47
- if (isHostName(retry)) {
48
- domains.push(retry);
49
- continue;
50
- }
51
- const dropped = retry === "" || retry === EMPTY_LIST_ANSWER ? entry : retry;
52
- print(`${OUTPUT_PREFIX}dropped ${JSON.stringify(dropped)}: not a host name like ${HOST_EXAMPLE}`);
53
- }
54
- return domains;
55
- }
56
- /**
57
- * Asks every question.
58
- *
59
- * @param ask Function that asks one question.
60
- * @param suggestions Suggested items.
61
- * @param print Prints one line (notes about dropped domains).
62
- * @returns The answers.
63
- * @example
64
- * await askAll(ask, { secrets: ["API_TOKEN"] }, console.log); // { secrets: ["API_TOKEN"], domains: [] }
65
- */
66
- export async function askAll(ask, suggestions, print) {
67
- const secrets = parseList(await ask("Secret environment variables needed in the cloud (comma-separated, - for none)", suggestions.secrets.join(", ")), suggestions.secrets);
68
- const typed = parseList(await ask("Extra domains the setup or tests need (comma-separated, - for none)", ""), []);
69
- return { secrets, domains: await validDomains(ask, typed, print) };
70
- }
5
+ export {};
package/dist/run.js CHANGED
@@ -16,7 +16,7 @@ export class RunTimeoutError extends Error {
16
16
  * @param ms - Time limit, in milliseconds.
17
17
  */
18
18
  constructor(cmd, ms) {
19
- super(`${cmd} timed out after ${ms}ms`);
19
+ super(`${cmd} superó el límite de ${ms}ms`);
20
20
  this.name = "RunTimeoutError";
21
21
  }
22
22
  }
@@ -82,7 +82,7 @@ function spawnAndWait(cmd, args, opts, stdio, onStdout) {
82
82
  clearTimeout(timer);
83
83
  if (code === 0)
84
84
  return resolve();
85
- reject(new Error(`${cmd} exited with ${code === null ? `signal ${signal}` : `code ${code}`}`));
85
+ reject(new Error(`${cmd} terminó con ${code === null ? `la señal ${signal}` : `el código ${code}`}`));
86
86
  });
87
87
  });
88
88
  }
package/dist/select.js ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * @file A small arrow-key menu for the terminal, with the look of agent-kitchen's (`@clack/prompts`)
3
+ * and no dependencies: ↑/↓ move, Enter picks, Esc or Ctrl+C cancels.
4
+ */
5
+ import { emitKeypressEvents } from "node:readline";
6
+ /** ANSI codes used by the menu. */
7
+ const ANSI = {
8
+ dim: (text) => `\x1b[2m${text}\x1b[22m`,
9
+ cyan: (text) => `\x1b[36m${text}\x1b[39m`,
10
+ green: (text) => `\x1b[32m${text}\x1b[39m`,
11
+ hideCursor: "\x1b[?25l",
12
+ showCursor: "\x1b[?25h",
13
+ up: (lines) => (lines > 0 ? `\x1b[${lines}A` : ""),
14
+ clearDown: "\r\x1b[J",
15
+ };
16
+ /**
17
+ * Applies one key to the menu.
18
+ *
19
+ * @param state - Current state.
20
+ * @param key - Key pressed.
21
+ * @param count - Number of options.
22
+ * @returns The new state.
23
+ * @example
24
+ * menuKey({ active: 0 }, { name: "down" }, 2); // { active: 1 }
25
+ */
26
+ export function menuKey(state, key, count) {
27
+ if (key.ctrl && key.name === "c")
28
+ return { ...state, done: "cancelled" };
29
+ if (key.name === "escape")
30
+ return { ...state, done: "cancelled" };
31
+ if (key.name === "return" || key.name === "enter")
32
+ return { ...state, done: "picked" };
33
+ if (key.name === "up" || key.name === "k")
34
+ return { active: (state.active - 1 + count) % count };
35
+ if (key.name === "down" || key.name === "j")
36
+ return { active: (state.active + 1) % count };
37
+ return state;
38
+ }
39
+ /**
40
+ * Draws the menu: the question, the options (hint only on the active one) and, once done, the
41
+ * picked option or "Cancelado".
42
+ *
43
+ * @param message - Question.
44
+ * @param choices - Options.
45
+ * @param state - Current state.
46
+ * @returns The lines to print.
47
+ * @example
48
+ * renderMenu("¿Dónde?", [{ value: "a", label: "A" }], { active: 0 })[0]; // "◆ ¿Dónde?" (with color)
49
+ */
50
+ export function renderMenu(message, choices, state) {
51
+ const bar = ANSI.dim("│");
52
+ if (state.done === "picked")
53
+ return [`${ANSI.green("◇")} ${message}`, `${bar} ${ANSI.dim(choices[state.active]?.label ?? "")}`, bar];
54
+ if (state.done === "cancelled")
55
+ return [`${ANSI.dim("■")} ${message}`, `${bar} ${ANSI.dim("Cancelado")}`, bar];
56
+ const lines = [`${ANSI.cyan("◆")} ${message}`];
57
+ choices.forEach((choice, index) => {
58
+ if (index === state.active) {
59
+ const hint = choice.hint ? ` ${ANSI.dim(`(${choice.hint})`)}` : "";
60
+ lines.push(`${ANSI.cyan("│")} ${ANSI.green("●")} ${choice.label}${hint}`);
61
+ }
62
+ else {
63
+ lines.push(`${ANSI.cyan("│")} ${ANSI.dim(`○ ${choice.label}`)}`);
64
+ }
65
+ });
66
+ lines.push(ANSI.cyan("└"));
67
+ return lines;
68
+ }
69
+ /**
70
+ * Shows the menu on the process terminal. Raw mode is on only while it is open, so a later
71
+ * `readline` question or a child process gets a normal terminal.
72
+ *
73
+ * @param message - Question.
74
+ * @param choices - Options.
75
+ * @param initial - Value active at the start.
76
+ * @returns The picked value, or null on Esc or Ctrl+C.
77
+ * @example
78
+ * await chooseTerminal("¿Dónde preparo el repo?", [{ value: "pr", label: "En un PR nuevo" }], "pr"); // "pr"
79
+ */
80
+ export const chooseTerminal = async (message, choices, initial) => {
81
+ const { stdin, stdout } = process;
82
+ let state = { active: Math.max(0, choices.findIndex((c) => c.value === initial)) };
83
+ let drawn = 0;
84
+ const draw = () => {
85
+ const lines = renderMenu(message, choices, state);
86
+ stdout.write(`${ANSI.up(drawn)}${ANSI.clearDown}${lines.join("\n")}\n`);
87
+ drawn = lines.length;
88
+ };
89
+ emitKeypressEvents(stdin);
90
+ stdin.setRawMode(true);
91
+ stdin.resume();
92
+ stdout.write(ANSI.hideCursor);
93
+ draw();
94
+ try {
95
+ return await new Promise((resolvePick) => {
96
+ const onKey = (_text, key = {}) => {
97
+ state = menuKey(state, key, choices.length);
98
+ draw();
99
+ if (state.done === undefined)
100
+ return;
101
+ stdin.off("keypress", onKey);
102
+ resolvePick(state.done === "picked" ? (choices[state.active]?.value ?? null) : null);
103
+ };
104
+ stdin.on("keypress", onKey);
105
+ });
106
+ }
107
+ finally {
108
+ stdin.setRawMode(false);
109
+ stdin.pause();
110
+ stdout.write(ANSI.showCursor);
111
+ }
112
+ };
package/dist/session.js CHANGED
@@ -6,7 +6,7 @@ import { appendFileSync, existsSync } from "node:fs";
6
6
  import { basename, join } from "node:path";
7
7
  import { findProxyCa, shellQuote } from "./ca.js";
8
8
  import { readConfig } from "./config.js";
9
- import { CONFIG_FILE, GIT_FETCH_TIMEOUT_MS, OUTPUT_PREFIX, PACKAGE, SETUP_SLOW_MS } from "./constants.js";
9
+ import { CONFIG_FILE, GIT_FETCH_TIMEOUT_MS, OUTPUT_PREFIX, packageSpec, SETUP_SLOW_MS } from "./constants.js";
10
10
  import { configImages, defaultBranch, readToolVersions } from "./detect.js";
11
11
  import { realDocker } from "./docker.js";
12
12
  import { pathLine, toolBinDirs } from "./installers.js";
@@ -15,7 +15,7 @@ import { resolveNotes } from "./manual.js";
15
15
  import { RunTimeoutError } from "./run.js";
16
16
  import { fingerprint, readState } from "./state.js";
17
17
  /** Generic note shown in every cloud session. */
18
- const GENERIC_NOTE = "you are in a Claude Code cloud session. There are no open ports or previews: test against localhost. gh pr and gh issue fail (the GitHub proxy blocks GraphQL): use gh api (REST).";
18
+ const GENERIC_NOTE = "estás en una sesión cloud de Claude Code. No hay puertos abiertos ni previews: prueba contra localhost. gh pr y gh issue fallan (el proxy de GitHub bloquea GraphQL): usa gh api (REST).";
19
19
  /** Milliseconds in a second. */
20
20
  const MS_PER_SECOND = 1000;
21
21
  /** Seconds in a minute. */
@@ -91,24 +91,25 @@ export function expectedFingerprint(repo, config, state) {
91
91
  * @param repo - Repo root.
92
92
  * @param config - Repo config.
93
93
  * @param state - Recorded state, if any.
94
+ * @param packageVersion - Version of the running package, for the setup command.
94
95
  * @returns Warning messages, without prefix.
95
96
  */
96
- function machineWarnings(repo, config, state) {
97
+ function machineWarnings(repo, config, state, packageVersion) {
97
98
  if (!state) {
98
99
  return [
99
- `the environment setup did not run for ${basename(repo)}. Set the setup script of the environment in claude.ai to: npx -y ${PACKAGE}@1 setup`,
100
+ `el setup del entorno no corrió para ${basename(repo)}. Pon como script de setup del entorno en claude.ai: npx -y ${packageSpec(packageVersion)} setup`,
100
101
  ];
101
102
  }
102
103
  const out = [];
103
104
  const failed = state.steps.filter((s) => !s.ok).map((s) => s.name);
104
105
  if (failed.length)
105
- out.push(`setup failed: ${failed.join(", ")}. Run agent-cloud-kit doctor.`);
106
+ out.push(`falló el setup: ${failed.join(", ")}. Corre agent-cloud-kit doctor.`);
106
107
  if (expectedFingerprint(repo, config, state) !== state.fingerprint) {
107
- out.push("the machine was prepared with another configuration. Edit the setup script field in claude.ai (any change) so it is rebuilt.");
108
+ out.push("la máquina se preparó con otra configuración. Edita el campo del script de setup en claude.ai (cualquier cambio) para que se reconstruya.");
108
109
  }
109
110
  const total = state.steps.reduce((sum, s) => sum + s.ms, 0);
110
111
  if (total > SETUP_SLOW_MS)
111
- out.push(`setup took ${minutesSeconds(total)}, close to the ~5 minute limit.`);
112
+ out.push(`el setup tardó ${minutesSeconds(total)}, cerca del límite de ~5 minutos.`);
112
113
  return out;
113
114
  }
114
115
  /**
@@ -128,7 +129,7 @@ function writeEnvFile(env, lines) {
128
129
  return undefined;
129
130
  }
130
131
  catch (err) {
131
- return `could not write ${ENV_FILE_VAR} (${file}): ${err instanceof Error ? err.message : String(err)}`;
132
+ return `no se pudo escribir ${ENV_FILE_VAR} (${file}): ${err instanceof Error ? err.message : String(err)}`;
132
133
  }
133
134
  }
134
135
  /**
@@ -169,7 +170,7 @@ async function runSessionSteps(repo, commands, run, env) {
169
170
  await run("bash", ["-c", command], { cwd: repo, env, stdio: "stderr", timeoutMs: SESSION_STEP_TIMEOUT_MS });
170
171
  }
171
172
  catch (err) {
172
- problems.push(`${err instanceof RunTimeoutError ? "session step timed out" : "session step failed"}: ${command}`);
173
+ problems.push(`${err instanceof RunTimeoutError ? "el paso de sesión superó el tiempo límite" : "falló el paso de sesión"}: ${command}`);
173
174
  }
174
175
  }
175
176
  return problems;
@@ -209,16 +210,16 @@ async function sessionBody(repo, ctx, out) {
209
210
  if (envFileProblem)
210
211
  out.push(envFileProblem);
211
212
  if (config)
212
- out.push(...machineWarnings(repo, config, state));
213
+ out.push(...machineWarnings(repo, config, state, ctx.packageVersion));
213
214
  const missing = (config?.requiredEnv ?? []).filter((name) => !ctx.env[name]);
214
215
  if (missing.length)
215
- out.push(`missing environment variables: ${missing.join(", ")}`);
216
+ out.push(`faltan variables de entorno: ${missing.join(", ")}`);
216
217
  const notes = resolveNotes(repo, config?.notes)?.trim();
217
218
  if (notes)
218
219
  out.push(notes);
219
220
  const dockerProblem = startDocker(ctx.docker ?? realDocker);
220
221
  if (dockerProblem)
221
- out.push(`could not start dockerd: ${dockerProblem}`);
222
+ out.push(`no se pudo iniciar dockerd: ${dockerProblem}`);
222
223
  const env = toolsEnv(dirs, ctx.env, ca);
223
224
  await syncGit(repo, ctx.run, env);
224
225
  out.push(...(await runSessionSteps(repo, config?.session ?? [], ctx.run, env)));
@@ -239,7 +240,7 @@ export async function session(repo, ctx) {
239
240
  await sessionBody(repo, ctx, out);
240
241
  }
241
242
  catch (err) {
242
- out.push(`session error: ${err instanceof Error ? err.message : String(err)}`);
243
+ out.push(`error en session: ${err instanceof Error ? err.message : String(err)}`);
243
244
  }
244
245
  return out.map((line) => OUTPUT_PREFIX + line);
245
246
  }
package/dist/setup.js CHANGED
@@ -66,14 +66,14 @@ async function aptLane(config, deps) {
66
66
  for (const url of config.aptRepos) {
67
67
  const name = `apt repo ${url}`;
68
68
  if (!url.startsWith(HTTPS_PREFIX)) {
69
- results.push(await refused(name, `apt repo must be an ${HTTPS_PREFIX} URL`));
69
+ results.push(await refused(name, `el repo de apt debe ser una URL ${HTTPS_PREFIX}`));
70
70
  continue;
71
71
  }
72
72
  results.push(await step(name, () => deps.run("bash", ["-c", APT_REPO_SCRIPT, SCRIPT_NAME, url], { env, timeoutMs: APT_STEP_TIMEOUT_MS })));
73
73
  }
74
74
  const valid = config.apt.filter((pkg) => DEBIAN_PACKAGE_NAME.test(pkg));
75
75
  for (const pkg of config.apt.filter((p) => !valid.includes(p))) {
76
- results.push(await refused(`apt package ${pkg}`, "invalid Debian package name"));
76
+ results.push(await refused(`apt package ${pkg}`, "nombre de paquete Debian inválido"));
77
77
  }
78
78
  if (!valid.length && !config.aptRepos.length)
79
79
  return results;
@@ -119,11 +119,11 @@ async function dockerLane(config, images, docker, deps) {
119
119
  const results = [];
120
120
  const valid = images.filter((image) => !WHITESPACE.test(image) && !image.startsWith("-") && image !== "");
121
121
  for (const image of images.filter((i) => !valid.includes(i))) {
122
- results.push(await refused(`docker pull ${image}`, "invalid image reference"));
122
+ results.push(await refused(`docker pull ${image}`, "referencia de imagen inválida"));
123
123
  }
124
124
  const up = await docker.ensure().catch(() => false);
125
125
  if (!up)
126
- return [...results, await refused("docker", `dockerd did not start; see ${DOCKERD_LOG}`)];
126
+ return [...results, await refused("docker", `dockerd no arrancó; revisa ${DOCKERD_LOG}`)];
127
127
  return [...results, ...(await pullAll(valid, deps))];
128
128
  }
129
129
  /**
@@ -181,7 +181,7 @@ function manifestVersion(root) {
181
181
  function copyPackage(source, target, version) {
182
182
  const found = manifestVersion(source);
183
183
  if (found !== version)
184
- throw new Error(`the package at ${source} is ${found ?? "unknown"}, not ${version}`);
184
+ throw new Error(`el paquete en ${source} es ${found ?? "desconocido"}, no ${version}`);
185
185
  const temp = `${target}.${process.pid}.tmp`;
186
186
  rmSync(temp, { recursive: true, force: true });
187
187
  mkdirSync(temp, { recursive: true });
@@ -267,7 +267,7 @@ export async function setupRepo(repo, ctx) {
267
267
  });
268
268
  const { config, errors } = readConfig(repo);
269
269
  if (!config)
270
- return failed("config", errors.join("; ") || "no config");
270
+ return failed("config", errors.join("; ") || "sin configuración");
271
271
  try {
272
272
  return await runPhases(repo, config, ctx);
273
273
  }
package/dist/skill.js CHANGED
@@ -78,15 +78,15 @@ function withVersion(text, version) {
78
78
  * @throws When the package has no skill or the copy fails (the installed skill is kept).
79
79
  * @example
80
80
  * installSkill("/home/user/app", "/usr/lib/node_modules/agent-cloud-kit", "1.0.0");
81
- * // "installed .agents/skills/cloud-ready 1.0.0"
81
+ * // "se instaló .agents/skills/cloud-ready 1.0.0"
82
82
  */
83
83
  export function installSkill(repo, packageRoot, packageVersion) {
84
84
  const current = skillVersion(repo);
85
85
  if (current === packageVersion)
86
- return `${SKILL_DIR} already at ${packageVersion}`;
86
+ return `${SKILL_DIR} ya está en ${packageVersion}`;
87
87
  const source = join(packageRoot, PACKAGE_SKILL_DIR);
88
88
  if (!existsSync(join(source, "SKILL.md")))
89
- throw new Error(`${PACKAGE_SKILL_DIR}/SKILL.md not found in ${packageRoot}`);
89
+ throw new Error(`no se encontró ${PACKAGE_SKILL_DIR}/SKILL.md en ${packageRoot}`);
90
90
  const target = join(repo, SKILL_DIR);
91
91
  const temp = `${target}.${process.pid}.tmp`;
92
92
  const old = `${target}.${process.pid}.old`;
@@ -107,7 +107,7 @@ export function installSkill(repo, packageRoot, packageVersion) {
107
107
  throw err;
108
108
  }
109
109
  rmSync(old, { recursive: true, force: true });
110
- return current === undefined ? `installed ${SKILL_DIR} ${packageVersion}` : `updated ${SKILL_DIR} from ${current} to ${packageVersion}`;
110
+ return current === undefined ? `se instaló ${SKILL_DIR} ${packageVersion}` : `se actualizó ${SKILL_DIR} de ${current} a ${packageVersion}`;
111
111
  }
112
112
  /**
113
113
  * Tells whether a path exists, counting a broken symlink as existing.
@@ -132,7 +132,7 @@ function present(path) {
132
132
  * @param repo - Repo root.
133
133
  * @returns A line describing what was done, or undefined when nothing was needed.
134
134
  * @example
135
- * linkSkill("/home/user/app"); // "linked .claude/skills/cloud-ready to .agents/skills/cloud-ready"
135
+ * linkSkill("/home/user/app"); // "se enlazó .claude/skills/cloud-ready a .agents/skills/cloud-ready"
136
136
  */
137
137
  export function linkSkill(repo) {
138
138
  const claudeSkills = join(repo, CLAUDE_SKILLS_DIR);
@@ -144,5 +144,5 @@ export function linkSkill(repo) {
144
144
  return undefined;
145
145
  mkdirSync(claudeSkills, { recursive: true });
146
146
  symlinkSync(SKILL_LINK_TARGET, link);
147
- return `linked ${SKILL_LINK} to ${SKILL_DIR}`;
147
+ return `se enlazó ${SKILL_LINK} a ${SKILL_DIR}`;
148
148
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-cloud-kit",
3
- "version": "1.0.0-rc.0",
3
+ "version": "1.0.0-rc.2",
4
4
  "description": "Zero-dependency CLI that prepares Claude Code cloud sessions.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: cloud-ready
3
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 [respuestas] | verify"
4
+ argument-hint: "configure | verify"
5
5
  metadata:
6
6
  version: "1.0.0"
7
7
  ---
@@ -33,8 +33,9 @@ Son los únicos que `verify` puede cambiar:
33
33
  ## `configure` (local, con el usuario)
34
34
 
35
35
  Si `CLAUDE_CODE_REMOTE=true`, detente: este modo necesita al usuario en una terminal local.
36
- Las respuestas que traiga el argumento (variables secretas, dominios extra) son el punto de
37
- partida: no las vuelvas a preguntar.
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").
38
39
 
39
40
  1. **Lee qué usa el repo:** `package.json` (scripts, `packageManager`, `engines`), el lockfile,
40
41
  `.tool-versions`, `mise.toml`, `.nvmrc`, archivos de Docker compose, `Dockerfile`,
@@ -81,11 +82,13 @@ partida: no las vuelvas a preguntar.
81
82
  8. **Si existe `.agents/skills/test-audit/project.md`:** escribe o actualiza su sección "Nube"
82
83
  con el mismo resumen; no toques el resto del archivo.
83
84
  9. **Corre `agent-cloud-kit manual`** y muéstrale al usuario la sección "Configurar el entorno en
84
- claude.ai" de `docs/agent-cloud.md` (setup script, dominios, variables).
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.
85
88
  10. **No hagas commit:** `agent-cloud-kit prepare` lo hace al salir. Si te invocaron fuera de
86
89
  `prepare`, pregunta al usuario.
87
90
  11. **Termina diciéndole al usuario que salga:** dile al usuario, con estas palabras o parecidas,
88
- "configure terminó: sal de claude con `/exit` y `prepare` sigue (commit, push y PR)". Si te
91
+ "configure terminó: sal de claude con `/exit` y `prepare` sigue". Si te
89
92
  invocaron fuera de `prepare`, omite este paso.
90
93
 
91
94
  ## `verify` (en la nube)
@@ -95,8 +98,8 @@ Solo con `CLAUDE_CODE_REMOTE=true`; si no, detente y dilo. El prompt trae la ram
95
98
  `claude/…`: no la cambies; al final empujas a la rama de `prepare`.
96
99
 
97
100
  1. **Contexto:** lee `.claude/cloud.json`, el archivo de `notes` y `docs/agent-cloud.md`. Anota
98
- las líneas `agent-cloud-kit:` de falla que mostró el inicio de la sesión ("did not run",
99
- pasos fallidos, "another configuration", variables faltantes).
101
+ las líneas `agent-cloud-kit:` de falla que mostró el inicio de la sesión ("el setup del entorno no corrió",
102
+ "falló el setup: …", "la máquina se preparó con otra configuración", "faltan variables de entorno").
100
103
  2. **Corre `agent-cloud-kit doctor`** y guarda la salida. Si `agent-cloud-kit` no está en el
101
104
  `PATH`, el setup no corrió: usa `npx -y agent-cloud-kit doctor` y repórtalo.
102
105
  3. **Comprueba la preparación:** dependencias instaladas (p. ej. `node_modules` existe), las
@@ -112,9 +115,11 @@ Solo con `CLAUDE_CODE_REMOTE=true`; si no, detente y dilo. El prompt trae la ram
112
115
  - **Se arregla en la preparación:** cambia solo los archivos de la preparación y vuelve a
113
116
  comprobar. Un cambio de `setup` no se re-ejecuta en esta sesión (el setup ya corrió): corre
114
117
  el comando a mano para probarlo y anota que la máquina guardada se rehará en una sesión nueva.
115
- - **Solo se cambia en claude.ai** (dominios de la red, variables, entorno por defecto con
116
- `/remote-env`, rehacer la máquina editando el setup script, el Claude GitHub App): no lo
117
- intentes; deja la instrucción exacta para el reporte y deja de intentar arreglos.
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.
118
123
  - **Requiere cambiar código, tests o scripts:** no lo cambies; deja el cambio propuesto
119
124
  (archivo y diff) para el comentario del PR.
120
125
 
@@ -157,6 +162,25 @@ Solo con `CLAUDE_CODE_REMOTE=true`; si no, detente y dilo. El prompt trae la ram
157
162
  **Hallazgos:** los nuevos, o "ninguno".
158
163
  ```
159
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`) y vuelve a verificar: en tu terminal, en la rama `<rama>` actualizada
178
+ (`git pull`), corre `npx agent-cloud-kit verify`".
179
+ 4. En `verify`, ponlo también en "Cambiar en claude.ai" del comentario del PR.
180
+
181
+ Un dominio que ya está en `domains`, o una variable que ya está en `requiredEnv`, no se agrega
182
+ de nuevo: si sigue faltando, el usuario aún no actualizó el entorno; recuérdaselo.
183
+
160
184
  ## Hallazgos
161
185
 
162
186
  En ambos modos: todo hecho nuevo sobre la nube (algo que no está en `reference/knowledge.md`,