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.
@@ -123,7 +123,7 @@ async function nodeExpectedHash(run, env, dir, version, file) {
123
123
  await run("curl", ["-fsSL", "-o", path, "--", url], { env, timeoutMs: TOOL_INSTALL_TIMEOUT_MS });
124
124
  const hash = parseShasums(readFileSync(path, "utf8"), file);
125
125
  if (!hash)
126
- throw new Error(`${file} is not listed in ${NODE_SHASUMS_FILE}`);
126
+ throw new Error(`${file} no aparece en ${NODE_SHASUMS_FILE}`);
127
127
  return hash;
128
128
  }
129
129
  /**
@@ -138,7 +138,7 @@ async function nodeExpectedHash(run, env, dir, version, file) {
138
138
  export function nodeArch(arch) {
139
139
  const mapped = NODE_ARCHS[arch];
140
140
  if (!mapped)
141
- throw new Error(`unsupported architecture for node: ${arch}`);
141
+ throw new Error(`arquitectura no soportada para node: ${arch}`);
142
142
  return mapped;
143
143
  }
144
144
  /**
@@ -153,7 +153,7 @@ export function nodeArch(arch) {
153
153
  */
154
154
  export function assertVersion(tool, version) {
155
155
  if (!VERSION_PATTERN.test(version))
156
- throw new Error(`invalid version for ${tool}: ${version}`);
156
+ throw new Error(`versión inválida para ${tool}: ${version}`);
157
157
  }
158
158
  /**
159
159
  * Tells whether a version is safe to use.
@@ -254,7 +254,7 @@ export function curlNodeIndex(run, env) {
254
254
  await run("curl", ["-fsSL", "--max-time", NODE_INDEX_TIMEOUT_SECONDS, "-o", file, "--", NODE_INDEX_URL], { env });
255
255
  const parsed = JSON.parse(readFileSync(file, "utf8"));
256
256
  if (!Array.isArray(parsed))
257
- throw new Error("the node release index is not a list");
257
+ throw new Error("el índice de versiones de node no es una lista");
258
258
  return parsed.filter((r) => typeof r === "object" && r !== null && typeof r.version === "string" && (r.lts === false || typeof r.lts === "string"));
259
259
  }
260
260
  finally {
@@ -283,7 +283,7 @@ export async function resolveVersions(versions, fetchIndex) {
283
283
  const result = await step(`node ${requested}`, async () => {
284
284
  exact = resolveNodeVersion(requested, await fetchIndex());
285
285
  if (!exact)
286
- throw new Error(`no node release matches ${requested}`);
286
+ throw new Error(`ninguna versión de node coincide con ${requested}`);
287
287
  });
288
288
  if (!exact)
289
289
  return { resolved, results: [result] };
@@ -403,7 +403,7 @@ export async function installTools(versions, toolsDir, run, env) {
403
403
  }
404
404
  if (tool === "node" && !NODE_EXACT.test(version)) {
405
405
  results.push(await step(`${tool} ${version}`, async () => {
406
- throw new Error(`node ${version} must be resolved to an exact version first`);
406
+ throw new Error(`node ${version} debe resolverse primero a una versión exacta`);
407
407
  }));
408
408
  continue;
409
409
  }
package/dist/launcher.js CHANGED
@@ -58,7 +58,7 @@ export function planSetup(repos, current, read) {
58
58
  return repos.map((repo) => {
59
59
  const { config, errors } = read(repo);
60
60
  if (!config)
61
- return { repo, action: "skip", reason: errors.length ? errors.join("; ") : `no ${CONFIG_FILE}` };
61
+ return { repo, action: "skip", reason: errors.length ? errors.join("; ") : `no hay ${CONFIG_FILE}` };
62
62
  if (config.version === current)
63
63
  return { repo, action: "run", version: current };
64
64
  return { repo, action: "relaunch", version: config.version };
package/dist/manual.js CHANGED
@@ -5,7 +5,7 @@
5
5
  import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
6
6
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
7
  import { readConfig } from "./config.js";
8
- import { CONFIG_FILE, OUTPUT_PREFIX, PACKAGE } from "./constants.js";
8
+ import { CONFIG_FILE, OUTPUT_PREFIX, PACKAGE, packageSpec } from "./constants.js";
9
9
  /** Path of the manual, relative to the repo. */
10
10
  export const MANUAL_FILE = "docs/agent-cloud.md";
11
11
  /** Marker that opens the generated block. */
@@ -16,17 +16,28 @@ export const END = "<!-- agent-cloud-kit:end -->";
16
16
  const FINDINGS = "## Hallazgos\n\n_Lo que el agente descubra de la nube va aquí._\n";
17
17
  /** Prefix of an https URL. */
18
18
  const HTTPS_PREFIX = "https://";
19
- /** Setup script the environment must run, in a `bash` block. */
20
- const SETUP_SCRIPT = `#!/bin/bash\nnpx -y ${PACKAGE}@1 setup`;
19
+ /**
20
+ * Setup script the environment must run, for a `bash` block.
21
+ *
22
+ * @param packageVersion - Version of the running package.
23
+ * @returns The script.
24
+ * @example
25
+ * setupScript("1.2.0"); // "#!/bin/bash\nnpx -y agent-cloud-kit@1 setup"
26
+ */
27
+ function setupScript(packageVersion) {
28
+ return `#!/bin/bash\nnpx -y ${packageSpec(packageVersion)} setup`;
29
+ }
21
30
  /** The "how it works" section, five lines. */
22
31
  const HOW_IT_WORKS = [
23
32
  "## Cómo funciona",
24
33
  "",
25
34
  "1. agent-kitchen instala las herramientas y los ganchos en el repo.",
26
- `2. \`${PACKAGE} prepare\` configura el repo con la skill y deja lista la lista de dominios y variables.`,
35
+ `2. \`${PACKAGE} prepare\` configura el repo con la skill y genera este documento.`,
27
36
  "3. Tú configuras el entorno en claude.ai con lo que indica la siguiente sección.",
28
37
  "4. Una sesión en la nube verifica que el entorno quedó bien.",
29
38
  "5. El PR guarda el resultado.",
39
+ "",
40
+ "Este documento se va actualizando. Cuando la skill `cloud-ready`, en local o en la nube, detecta que hace falta un dominio o una variable de entorno nueva, la agrega a `.claude/cloud.json`, regenera este archivo y te lo dice en la sesión. Cuando cambie la sección \"Configurar el entorno en claude.ai\", aplica lo nuevo en claude.ai y vuelve a verificar con `npx agent-cloud-kit verify` desde la rama.",
30
41
  ].join("\n");
31
42
  /**
32
43
  * Extracts the host of an apt repo URL.
@@ -62,11 +73,12 @@ function allowedHosts(config) {
62
73
  * Renders the "Configurar el entorno en claude.ai" section.
63
74
  *
64
75
  * @param config - Repo config.
76
+ * @param packageVersion - Version of the running package, for the setup script.
65
77
  * @returns The section, without a trailing newline.
66
78
  * @example
67
- * renderEnvironmentSection(config).startsWith("## Configurar el entorno en claude.ai"); // true
79
+ * renderEnvironmentSection(config, "1.0.0").startsWith("## Configurar el entorno en claude.ai"); // true
68
80
  */
69
- export function renderEnvironmentSection(config) {
81
+ export function renderEnvironmentSection(config, packageVersion) {
70
82
  const hosts = allowedHosts(config);
71
83
  const lines = [
72
84
  "## Configurar el entorno en claude.ai",
@@ -82,7 +94,7 @@ export function renderEnvironmentSection(config) {
82
94
  "### Setup script",
83
95
  "",
84
96
  "```bash",
85
- SETUP_SCRIPT,
97
+ setupScript(packageVersion),
86
98
  "```",
87
99
  "",
88
100
  "### Variables de entorno",
@@ -154,7 +166,7 @@ export function renderManual(input) {
154
166
  `<!-- Generado por ${PACKAGE} ${input.packageVersion}. No edites entre las marcas. -->`,
155
167
  "# Nube de Claude Code",
156
168
  HOW_IT_WORKS,
157
- renderEnvironmentSection(input.config),
169
+ renderEnvironmentSection(input.config, input.packageVersion),
158
170
  VERIFY_SECTION,
159
171
  `## Qué corre en la nube\n\n${notes?.trim() ? notes.trim() : "Nada anotado."}`,
160
172
  END,
@@ -193,7 +205,7 @@ export function mergeManual(existing, block) {
193
205
  export function writeManual(repo, packageVersion) {
194
206
  const { config, errors } = readConfig(repo);
195
207
  if (!config)
196
- throw new Error(errors[0] ?? `${CONFIG_FILE} not found; run init first`);
208
+ throw new Error(errors[0] ?? `no existe ${CONFIG_FILE}; corre init primero`);
197
209
  const file = join(repo, MANUAL_FILE);
198
210
  const existing = existsSync(file) ? readFileSync(file, "utf8") : undefined;
199
211
  const block = renderManual({ config, packageVersion, notes: resolveNotes(repo, config.notes) });
@@ -1,57 +1,28 @@
1
1
  /**
2
- * @file Pure parts of `prepare`: secret-name suggestions and preflight checks.
2
+ * @file Pure parts of `prepare`: preflight checks.
3
3
  */
4
- import { existsSync, readFileSync } from "node:fs";
5
- import { join } from "node:path";
6
- /** File, in the repo root, whose keys are suggested as secret names. */
7
- const ENV_EXAMPLE_FILE = ".env.example";
8
- /** Matches a `KEY=value` line (optionally prefixed by `export`) and captures the key. */
9
- const ENV_LINE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/;
10
4
  /** Flag `claude` must advertise in its help to launch cloud sessions. */
11
5
  const CLOUD_FLAG = "--cloud";
12
6
  /**
13
- * Suggests secret names from the keys of `.env.example` in the repo root.
14
- *
15
- * @param repo Repo root.
16
- * @returns Keys in file order, without duplicates; empty when the file does not exist.
17
- * @example
18
- * suggestSecrets("/repo"); // ["API_TOKEN", "DB_URL"]
19
- */
20
- export function suggestSecrets(repo) {
21
- const file = join(repo, ENV_EXAMPLE_FILE);
22
- if (!existsSync(file))
23
- return [];
24
- const keys = [];
25
- for (const line of readFileSync(file, "utf8").split(/\r?\n/)) {
26
- if (line.trimStart().startsWith("#"))
27
- continue;
28
- const key = ENV_LINE.exec(line)?.[1];
29
- if (key !== undefined && !keys.includes(key))
30
- keys.push(key);
31
- }
32
- return keys;
33
- }
34
- /**
35
- * Checks that `prepare` can run.
7
+ * Checks that `prepare` (or `verify`) can run.
36
8
  *
37
9
  * @param probe What was observed about the environment.
38
- * @returns Problems in English, in a fixed order; empty when everything is fine.
10
+ * @param command Command named in the messages.
11
+ * @returns Problems in Spanish, in a fixed order; empty when everything is fine.
39
12
  * @example
40
- * preflight({ isTty: false, hasOrigin: true, clean: true }); // ["prepare needs an interactive terminal", ...]
13
+ * preflight({ isTty: false, hasOrigin: true }); // ["prepare necesita una terminal interactiva", ...]
41
14
  */
42
- export function preflight(probe) {
15
+ export function preflight(probe, command = "prepare") {
43
16
  const problems = [];
44
17
  if (!probe.isTty)
45
- problems.push("prepare needs an interactive terminal");
18
+ problems.push(`${command} necesita una terminal interactiva (corre el comando directamente en tu terminal)`);
46
19
  if (probe.gitRoot === undefined)
47
- problems.push("not inside a git repository");
20
+ problems.push(`esto no es un repositorio git; corre ${command} dentro del repo`);
48
21
  if (!probe.hasOrigin)
49
- problems.push("the repository has no 'origin' remote");
50
- if (!probe.clean)
51
- problems.push("the working tree has uncommitted changes; commit or stash them first");
22
+ problems.push(`el repositorio no tiene el remoto 'origin'; ${command} necesita subir una rama a GitHub`);
52
23
  if (probe.claudeHelp === undefined)
53
- problems.push("claude is not installed or not on the PATH");
24
+ problems.push(`claude no está instalado o no está en el PATH; instala Claude Code y vuelve a correr ${command}`);
54
25
  else if (!probe.claudeHelp.includes(CLOUD_FLAG))
55
- problems.push("claude does not support --cloud; update Claude Code");
26
+ problems.push(`tu versión de claude no soporta --cloud; actualiza Claude Code y vuelve a correr ${command}`);
56
27
  return problems;
57
28
  }
@@ -9,10 +9,9 @@ import { readConfig } from "../config.js";
9
9
  import { CONFIG_FILE, OUTPUT_PREFIX, PACKAGE } from "../constants.js";
10
10
  import { defaultBranch } from "../detect.js";
11
11
  import { init } from "../init.js";
12
- import { renderEnvironmentSection, writeManual } from "../manual.js";
13
- import { preflight, suggestSecrets } from "./detect.js";
12
+ import { MANUAL_FILE, renderEnvironmentSection, writeManual } from "../manual.js";
13
+ import { preflight } from "./detect.js";
14
14
  import { configurePrompt, verifyPrompt } from "./prompts.js";
15
- import { askAll } from "./questions.js";
16
15
  /** Prefix of the branch `prepare` creates; the day follows it. */
17
16
  const BRANCH_PREFIX = "chore/cloud-ready-";
18
17
  /** Suffix added to the repo folder name to name the worktree folder, next to the repo. */
@@ -30,13 +29,13 @@ const KNOWN_PATHS = [
30
29
  /** Project file of the test-audit skill, committed too when it exists. */
31
30
  const TEST_AUDIT_PROJECT_FILE = ".agents/skills/test-audit/project.md";
32
31
  /** Message of the commit `prepare` makes. */
33
- const COMMIT_MESSAGE = "chore: prepare the repo for the Claude Code cloud";
32
+ const COMMIT_MESSAGE = "chore: prepara el repo para la nube de Claude Code";
34
33
  /** Question asked before committing changes outside the known files. */
35
- const INCLUDE_QUESTION = "Include these changes in the commit? [y/N]";
34
+ const INCLUDE_QUESTION = "¿Incluir estos cambios en el commit? [s/N]";
36
35
  /** Answers to `INCLUDE_QUESTION` that mean yes. */
37
- const YES_ANSWER = /^y(es)?$/i;
36
+ const YES_ANSWER = /^(s|si|sí|y|yes)$/i;
38
37
  /** Text shown while the user configures the environment in claude.ai. */
39
- const WAIT_TEXT = "Configure the environment in claude.ai, then press Enter";
38
+ const WAIT_TEXT = "Configura el entorno en claude.ai y luego presiona Enter";
40
39
  /** Matches a GitHub remote URL (https or ssh) and captures `owner/repo`. */
41
40
  const GITHUB_REMOTE = /github\.com[:/]([^/\s]+\/[^/\s]+?)(?:\.git)?\/?$/;
42
41
  /** Matches a pull request URL and captures its number. */
@@ -44,7 +43,13 @@ const PR_URL = /\/pull\/(\d+)/;
44
43
  /** Command that continues a prepare run; `npx` because a first-time user does not have the binary on PATH. */
45
44
  const RESUME_COMMAND = `npx ${PACKAGE} prepare --resume`;
46
45
  /** Printed just before the local agent starts: `prepare` continues only when `claude` exits. */
47
- const EXIT_CLAUDE_HINT = `${OUTPUT_PREFIX}when the skill says configure is done, exit claude (/exit) and prepare will commit, push and open the PR`;
46
+ const EXIT_CLAUDE_HINT = `${OUTPUT_PREFIX}cuando la skill diga que configure terminó, sal de claude (/exit) y prepare hará el commit, el push y abrirá el PR`;
47
+ /** Printed before the local agent starts in the current branch: nothing is committed. */
48
+ const EXIT_CLAUDE_HINT_HERE = `${OUTPUT_PREFIX}cuando la skill diga que configure terminó, sal de claude (/exit)`;
49
+ /** Question of the place menu. */
50
+ const PLACE_QUESTION = "¿Dónde preparo el repo?";
51
+ /** Command that launches the cloud verification of the current branch. */
52
+ const VERIFY_COMMAND = `npx ${PACKAGE} verify`;
48
53
  /** Exit code for success. */
49
54
  const EXIT_OK = 0;
50
55
  /** Exit code for failure. */
@@ -57,7 +62,7 @@ const PR_NUMBER = /^\d+$/;
57
62
  export class InterruptedError extends Error {
58
63
  /** Builds the error. */
59
64
  constructor() {
60
- super("interrupted");
65
+ super("interrumpido");
61
66
  this.name = "InterruptedError";
62
67
  }
63
68
  }
@@ -80,10 +85,10 @@ export function branchDay(date) {
80
85
  * @param worktree - Worktree folder.
81
86
  * @returns The line to print.
82
87
  * @example
83
- * resumeLine("interrupted", "/home/u/app-cloud-ready"); // "interrupted; the worktree is at ...; run ... --resume there"
88
+ * resumeLine("interrumpido", "/home/u/app-cloud-ready"); // "interrumpido; el worktree está en ...; corre ... --resume ahí para continuar"
84
89
  */
85
90
  function resumeLine(reason, worktree) {
86
- return `${reason}; the worktree is at ${worktree}; run ${RESUME_COMMAND} there`;
91
+ return `${reason}; el worktree está en ${worktree}; corre ${RESUME_COMMAND} ahí para continuar`;
87
92
  }
88
93
  /**
89
94
  * Runs a capture and trims its output; never throws.
@@ -154,7 +159,7 @@ function worktreePath(repo) {
154
159
  function configureProblem(worktree) {
155
160
  const { config, errors } = readConfig(worktree);
156
161
  if (!config || errors.length)
157
- return errors[0] ?? `${CONFIG_FILE} not found`;
162
+ return errors[0] ?? `no existe ${CONFIG_FILE}`;
158
163
  const notes = config.notes ?? DEFAULT_NOTES_FILE;
159
164
  const rel = relative(worktree, join(worktree, notes));
160
165
  const inside = !isAbsolute(notes) && rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`);
@@ -165,7 +170,7 @@ function configureProblem(worktree) {
165
170
  catch {
166
171
  // NOTE: a missing file is reported below.
167
172
  }
168
- return `the notes file ${notes} does not exist`;
173
+ return `no existe el archivo de notas ${notes}`;
169
174
  }
170
175
  /**
171
176
  * Lists the paths of `git status --porcelain=v1 -z` output.
@@ -235,7 +240,7 @@ async function findResume(repo, deps) {
235
240
  return undefined;
236
241
  }
237
242
  /**
238
- * Steps 2–5: asks the questions, creates the worktree, installs the kit and runs the local agent.
243
+ * Steps 2–5: creates the worktree, installs the kit and runs the local agent.
239
244
  *
240
245
  * @param repo - Git root of the repo.
241
246
  * @param branch - Branch to create.
@@ -243,20 +248,18 @@ async function findResume(repo, deps) {
243
248
  * @param deps - Dependencies.
244
249
  * @param progress - Gets the worktree once it exists.
245
250
  * @returns True when the worktree is ready to commit.
246
- * @throws InterruptedError when the user presses Ctrl+C during the questions.
247
251
  * @example
248
252
  * await createAndConfigure("/home/u/app", "chore/cloud-ready-20261102", "/home/u/app-cloud-ready", deps, {});
249
253
  */
250
254
  async function createAndConfigure(repo, branch, worktree, deps, progress) {
251
255
  if (existsSync(worktree)) {
252
- deps.print(`${OUTPUT_PREFIX}${worktree} already exists; remove it (git worktree remove ${worktree}) or continue there with ${RESUME_COMMAND}`);
256
+ deps.print(`${OUTPUT_PREFIX}${worktree} ya existe; bórralo (git worktree remove ${worktree}) o continúa ahí con ${RESUME_COMMAND}`);
253
257
  return false;
254
258
  }
255
259
  if ((await tryCapture(deps, "git", ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`], repo)) !== undefined) {
256
- deps.print(`${OUTPUT_PREFIX}branch ${branch} already exists; delete it (git branch -D ${branch}) or run prepare another day`);
260
+ deps.print(`${OUTPUT_PREFIX}la rama ${branch} ya existe; bórrala (git branch -D ${branch}) o corre prepare otro día`);
257
261
  return false;
258
262
  }
259
- const answers = await askAll(deps.ask, { secrets: suggestSecrets(repo) }, deps.print);
260
263
  const base = `origin/${defaultBranch(repo)}`;
261
264
  try {
262
265
  await deps.run("git", ["fetch", "-q", "origin"], { cwd: repo });
@@ -264,10 +267,10 @@ async function createAndConfigure(repo, branch, worktree, deps, progress) {
264
267
  progress.worktree = worktree;
265
268
  }
266
269
  catch (err) {
267
- deps.print(`${OUTPUT_PREFIX}could not create the worktree: ${message(err)}`);
270
+ deps.print(`${OUTPUT_PREFIX}no se pudo crear el worktree: ${message(err)}`);
268
271
  return false;
269
272
  }
270
- const stopped = resumeLine("the local agent stopped", worktree);
273
+ const stopped = resumeLine("el agente local se detuvo", worktree);
271
274
  try {
272
275
  const result = init(worktree, deps.packageVersion, deps.packageRoot);
273
276
  for (const line of result.lines)
@@ -284,7 +287,7 @@ async function createAndConfigure(repo, branch, worktree, deps, progress) {
284
287
  }
285
288
  deps.print(EXIT_CLAUDE_HINT);
286
289
  try {
287
- await runInteractive(deps, progress, [configurePrompt(answers)], worktree);
290
+ await runInteractive(deps, progress, [configurePrompt()], worktree);
288
291
  }
289
292
  catch {
290
293
  deps.print(stopped);
@@ -292,8 +295,8 @@ async function createAndConfigure(repo, branch, worktree, deps, progress) {
292
295
  }
293
296
  const problem = configureProblem(worktree);
294
297
  if (problem !== undefined) {
295
- deps.print(`${OUTPUT_PREFIX}configure did not finish: ${problem}`);
296
- deps.print(resumeLine("prepare stopped", worktree));
298
+ deps.print(`${OUTPUT_PREFIX}configure no terminó: ${problem}`);
299
+ deps.print(resumeLine("prepare se detuvo", worktree));
297
300
  return false;
298
301
  }
299
302
  return true;
@@ -320,7 +323,7 @@ async function commitAndPush(worktree, branch, deps) {
320
323
  const status = await deps.capture("git", ["status", "--porcelain=v1", "-z", "--untracked-files=all"], { cwd: worktree });
321
324
  const others = statusPaths(status).filter((path) => !covered(path, known));
322
325
  if (others.length) {
323
- deps.print(`${OUTPUT_PREFIX}the local agent also changed:`);
326
+ deps.print(`${OUTPUT_PREFIX}el agente local también cambió:`);
324
327
  for (const path of others)
325
328
  deps.print(` ${path}`);
326
329
  if (YES_ANSWER.test((await deps.ask(INCLUDE_QUESTION, "")).trim())) {
@@ -328,48 +331,49 @@ async function commitAndPush(worktree, branch, deps) {
328
331
  }
329
332
  else {
330
333
  await deps.run("git", ["reset", "-q", "--", ...others], { cwd: worktree });
331
- deps.print(`${OUTPUT_PREFIX}left uncommitted in ${worktree}`);
334
+ deps.print(`${OUTPUT_PREFIX}quedó sin commitear en ${worktree}`);
332
335
  }
333
336
  }
334
337
  const staged = await deps.capture("git", ["diff", "--cached", "--name-only"], { cwd: worktree });
335
338
  if (staged.trim())
336
339
  await deps.run("git", ["commit", "-m", COMMIT_MESSAGE], { cwd: worktree });
337
340
  else
338
- deps.print(`${OUTPUT_PREFIX}nothing new to commit`);
341
+ deps.print(`${OUTPUT_PREFIX}no hay nada nuevo que commitear`);
339
342
  await deps.run("git", ["push", "-u", "origin", branch], { cwd: worktree });
340
343
  }
341
344
  catch (err) {
342
345
  if (err instanceof InterruptedError)
343
346
  throw err;
344
- deps.print(`${OUTPUT_PREFIX}could not commit and push: ${message(err)}; fix it in ${worktree} and run ${RESUME_COMMAND} there`);
347
+ deps.print(`${OUTPUT_PREFIX}no se pudo hacer commit y push: ${message(err)}; corrígelo en ${worktree} y corre ${RESUME_COMMAND} ahí`);
345
348
  return false;
346
349
  }
347
350
  const existing = await tryCapture(deps, "gh", ["pr", "view", branch, "--json", "number", "--jq", ".number"], worktree);
348
351
  if (existing !== undefined && PR_NUMBER.test(existing)) {
349
- deps.print(`${OUTPUT_PREFIX}reusing the open PR #${existing}`);
352
+ deps.print(`${OUTPUT_PREFIX}se reutiliza el PR abierto #${existing}`);
350
353
  return { pr: existing };
351
354
  }
352
355
  try {
353
356
  const url = await deps.capture("gh", ["pr", "create", "--draft", "--fill"], { cwd: worktree });
354
- deps.print(`${OUTPUT_PREFIX}draft PR: ${url.trim()}`);
357
+ deps.print(`${OUTPUT_PREFIX}PR en borrador: ${url.trim()}`);
355
358
  return { pr: PR_URL.exec(url)?.[1] };
356
359
  }
357
360
  catch {
358
361
  const origin = await tryCapture(deps, "git", ["remote", "get-url", "origin"], worktree);
359
362
  const link = compareLink(origin, branch);
360
- deps.print(`${OUTPUT_PREFIX}could not open the PR with gh; open it by hand${link ? `: ${link}` : ` from branch ${branch}`}`);
363
+ deps.print(`${OUTPUT_PREFIX}no se pudo abrir el PR con gh; ábrelo a mano${link ? `: ${link}` : ` desde la rama ${branch}`}`);
361
364
  return {};
362
365
  }
363
366
  }
364
367
  /**
365
- * Prepares a repo for Claude Code cloud sessions: checks, asks, creates a worktree on a new
366
- * branch, runs the local agent with the `cloud-ready` skill, commits, pushes, opens a draft PR,
368
+ * Prepares a repo for Claude Code cloud sessions: checks, asks where to work (a new PR or the
369
+ * current branch, see `prepareHere`), creates a worktree on a new branch, runs the local agent with the `cloud-ready` skill, commits, pushes, opens a draft PR,
367
370
  * waits while the user configures the environment and starts a cloud session that verifies it.
368
371
  *
369
372
  * @param repo - Folder where the user runs `prepare` (inside the repo, or the worktree with resume).
370
373
  * @param deps - Dependencies.
371
374
  * @param options - `resume` continues in an existing prepare worktree.
372
- * @returns 0 when the cloud session was started, 130 after Ctrl+C at a question, 1 otherwise.
375
+ * @returns 0 when the cloud session was started (or, in the current branch, when it is prepared),
376
+ * 130 after Ctrl+C at a question, 1 otherwise.
373
377
  * @example
374
378
  * process.exitCode = await prepare(process.cwd(), deps);
375
379
  */
@@ -377,9 +381,8 @@ export async function prepare(repo, deps, options = {}) {
377
381
  const gitRoot = await tryCapture(deps, "git", ["rev-parse", "--show-toplevel"], repo);
378
382
  const cwd = gitRoot ?? repo;
379
383
  const hasOrigin = (await tryCapture(deps, "git", ["remote", "get-url", "origin"], cwd)) !== undefined;
380
- const clean = options.resume ? true : (await tryCapture(deps, "git", ["status", "--porcelain"], cwd)) === "";
381
384
  const claudeHelp = await tryCapture(deps, "claude", ["--help"], cwd);
382
- const problems = preflight({ isTty: deps.isTty, gitRoot, hasOrigin, clean, claudeHelp });
385
+ const problems = preflight({ isTty: deps.isTty, gitRoot, hasOrigin, claudeHelp });
383
386
  if (problems.length || gitRoot === undefined) {
384
387
  for (const problem of problems)
385
388
  deps.print(`${OUTPUT_PREFIX}${problem}`);
@@ -433,10 +436,12 @@ async function runInteractive(deps, progress, args, cwd) {
433
436
  * @param progress - What exists so far.
434
437
  * @returns The line.
435
438
  * @example
436
- * interruptedLine({}); // "agent-cloud-kit: interrupted; nothing was created"
439
+ * interruptedLine({}); // "agent-cloud-kit: interrumpido; no se creó nada"
437
440
  */
438
441
  function interruptedLine(progress) {
439
- return progress.worktree === undefined ? `${OUTPUT_PREFIX}interrupted; nothing was created` : resumeLine("interrupted", progress.worktree);
442
+ if (progress.here)
443
+ return `${OUTPUT_PREFIX}interrumpido; lo que alcanzó a cambiar quedó sin commit en tu rama`;
444
+ return progress.worktree === undefined ? `${OUTPUT_PREFIX}interrumpido; no se creó nada` : resumeLine("interrumpido", progress.worktree);
440
445
  }
441
446
  /**
442
447
  * Everything after the preflight: resume or create the worktree, commit, wait, launch.
@@ -456,18 +461,25 @@ async function prepareSteps(gitRoot, deps, options, progress) {
456
461
  if (options.resume) {
457
462
  const found = await findResume(gitRoot, deps);
458
463
  if (!found) {
459
- deps.print(`${OUTPUT_PREFIX}nothing to resume: run it in the ${WORKTREE_SUFFIX} worktree on a ${BRANCH_PREFIX}* branch`);
464
+ deps.print(`${OUTPUT_PREFIX}no hay nada que reanudar: corre prepare --resume en el worktree *${WORKTREE_SUFFIX}, sobre una rama ${BRANCH_PREFIX}*`);
460
465
  return EXIT_FAIL;
461
466
  }
462
467
  ({ worktree, branch } = found);
463
468
  progress.worktree = worktree;
464
469
  const problem = configureProblem(worktree);
465
470
  if (problem) {
466
- deps.print(`${OUTPUT_PREFIX}configure is not finished in ${worktree}: ${problem}; run claude "/cloud-ready configure" there first, then ${RESUME_COMMAND}`);
471
+ deps.print(`${OUTPUT_PREFIX}configure no terminó en ${worktree}: ${problem}; corre claude "/cloud-ready configure" ahí primero y luego ${RESUME_COMMAND}`);
467
472
  return EXIT_FAIL;
468
473
  }
469
474
  }
470
475
  else {
476
+ const place = options.place ?? (await askPlace(gitRoot, deps));
477
+ if (place === null) {
478
+ deps.print(`${OUTPUT_PREFIX}no se cambió nada`);
479
+ return EXIT_FAIL;
480
+ }
481
+ if (place === "here")
482
+ return prepareHere(gitRoot, deps, progress);
471
483
  branch = `${BRANCH_PREFIX}${deps.today}`;
472
484
  worktree = worktreePath(gitRoot);
473
485
  if (!(await createAndConfigure(gitRoot, branch, worktree, deps, progress)))
@@ -478,15 +490,145 @@ async function prepareSteps(gitRoot, deps, options, progress) {
478
490
  return EXIT_FAIL;
479
491
  const config = readConfig(worktree).config;
480
492
  if (config)
481
- deps.print(renderEnvironmentSection(config));
493
+ deps.print(renderEnvironmentSection(config, deps.packageVersion));
482
494
  await deps.waitEnter(WAIT_TEXT);
483
495
  try {
484
496
  await runInteractive(deps, progress, ["--cloud", verifyPrompt({ branch, pr: pushed.pr })], worktree);
485
497
  }
486
498
  catch (err) {
487
- deps.print(`${OUTPUT_PREFIX}claude --cloud failed: ${message(err)}; run ${RESUME_COMMAND} in ${worktree} to try again`);
499
+ deps.print(`${OUTPUT_PREFIX}falló claude --cloud: ${message(err)}; corre ${RESUME_COMMAND} en ${worktree} para reintentar`);
488
500
  return EXIT_FAIL;
489
501
  }
490
- deps.print(`${OUTPUT_PREFIX}the cloud session will report back as a push to ${branch} and a comment on the PR`);
502
+ deps.print(`${OUTPUT_PREFIX}la sesión cloud avisará con un push a ${branch} y un comentario en el PR`);
503
+ return EXIT_OK;
504
+ }
505
+ /**
506
+ * Asks where to work, with the same options as agent-kitchen.
507
+ *
508
+ * @param gitRoot - Git root.
509
+ * @param deps - Dependencies.
510
+ * @returns The place, or null when cancelled.
511
+ * @example
512
+ * await askPlace("/home/u/app", deps); // "pr"
513
+ */
514
+ async function askPlace(gitRoot, deps) {
515
+ return deps.choose(PLACE_QUESTION, [
516
+ { value: "pr", label: "En un PR nuevo", hint: `recomendado · parte de origin/${defaultBranch(gitRoot)}, no toca tu rama ni tus archivos` },
517
+ { value: "here", label: "En la rama actual, sin commit", hint: `los revisas con git diff, haces commit y push, y luego corres ${VERIFY_COMMAND}` },
518
+ ], "pr");
519
+ }
520
+ /**
521
+ * The "current branch" place: installs the kit, runs the local agent and writes the manual in the
522
+ * checkout, without committing. The cloud verification comes later, with `verify`, once pushed.
523
+ *
524
+ * @param gitRoot - Git root (the current checkout).
525
+ * @param deps - Dependencies.
526
+ * @param progress - Marks the checkout as changed.
527
+ * @returns The exit code.
528
+ * @example
529
+ * await prepareHere("/home/u/app", deps, {}); // 0
530
+ */
531
+ async function prepareHere(gitRoot, deps, progress) {
532
+ progress.here = true;
533
+ const stopped = `${OUTPUT_PREFIX}prepare se detuvo; lo que alcanzó a cambiar quedó sin commit en tu rama`;
534
+ try {
535
+ const result = init(gitRoot, deps.packageVersion, deps.packageRoot);
536
+ for (const line of result.lines)
537
+ deps.print(line);
538
+ if (!result.ok) {
539
+ deps.print(stopped);
540
+ return EXIT_FAIL;
541
+ }
542
+ }
543
+ catch (err) {
544
+ deps.print(`${OUTPUT_PREFIX}${message(err)}`);
545
+ deps.print(stopped);
546
+ return EXIT_FAIL;
547
+ }
548
+ deps.print(EXIT_CLAUDE_HINT_HERE);
549
+ try {
550
+ await runInteractive(deps, progress, [configurePrompt()], gitRoot);
551
+ }
552
+ catch {
553
+ deps.print(stopped);
554
+ return EXIT_FAIL;
555
+ }
556
+ const problem = configureProblem(gitRoot);
557
+ if (problem !== undefined) {
558
+ deps.print(`${OUTPUT_PREFIX}configure no terminó: ${problem}; corre claude "/cloud-ready configure" y luego prepare otra vez`);
559
+ return EXIT_FAIL;
560
+ }
561
+ try {
562
+ writeManual(gitRoot, deps.packageVersion);
563
+ }
564
+ catch (err) {
565
+ deps.print(`${OUTPUT_PREFIX}no se pudo escribir ${MANUAL_FILE}: ${message(err)}`);
566
+ return EXIT_FAIL;
567
+ }
568
+ const config = readConfig(gitRoot).config;
569
+ if (config)
570
+ deps.print(renderEnvironmentSection(config, deps.packageVersion));
571
+ deps.print(`${OUTPUT_PREFIX}listo, sin commit. Revisa los cambios con git diff, haz commit y push, configura el entorno en claude.ai (${MANUAL_FILE}) y corre ${VERIFY_COMMAND}`);
572
+ return EXIT_OK;
573
+ }
574
+ /**
575
+ * Launches the cloud verification of the current branch: checks that the preparation is there
576
+ * and pushed (the cloud only sees origin), then starts `claude --cloud` with `cloud-ready verify`.
577
+ *
578
+ * @param repo - Folder where the user runs `verify`.
579
+ * @param deps - Dependencies (`ask`, `choose`, `waitEnter` and `today` are not used).
580
+ * @returns 0 when the cloud session was started, 1 otherwise.
581
+ * @example
582
+ * process.exitCode = await verifyBranch(process.cwd(), deps);
583
+ */
584
+ export async function verifyBranch(repo, deps) {
585
+ const gitRoot = await tryCapture(deps, "git", ["rev-parse", "--show-toplevel"], repo);
586
+ const cwd = gitRoot ?? repo;
587
+ const hasOrigin = (await tryCapture(deps, "git", ["remote", "get-url", "origin"], cwd)) !== undefined;
588
+ const claudeHelp = await tryCapture(deps, "claude", ["--help"], cwd);
589
+ const problems = preflight({ isTty: deps.isTty, gitRoot, hasOrigin, claudeHelp }, "verify");
590
+ if (problems.length || gitRoot === undefined) {
591
+ for (const problem of problems)
592
+ deps.print(`${OUTPUT_PREFIX}${problem}`);
593
+ return EXIT_FAIL;
594
+ }
595
+ const branch = await tryCapture(deps, "git", ["rev-parse", "--abbrev-ref", "HEAD"], gitRoot);
596
+ if (branch === undefined || branch === "HEAD") {
597
+ deps.print(`${OUTPUT_PREFIX}no estás en una rama; cámbiate a la rama que quieres verificar`);
598
+ return EXIT_FAIL;
599
+ }
600
+ const problem = configureProblem(gitRoot);
601
+ if (problem !== undefined) {
602
+ deps.print(`${OUTPUT_PREFIX}el repo no está preparado (${problem}); corre npx ${PACKAGE} prepare primero`);
603
+ return EXIT_FAIL;
604
+ }
605
+ const notes = readConfig(gitRoot).config?.notes ?? DEFAULT_NOTES_FILE;
606
+ const files = [CONFIG_FILE, MANUAL_FILE, notes].filter((path) => entryExists(join(gitRoot, path)));
607
+ const dirty = await tryCapture(deps, "git", ["status", "--porcelain", "--", ...files], gitRoot);
608
+ if (dirty) {
609
+ deps.print(`${OUTPUT_PREFIX}hay cambios sin commit en la preparación; haz commit y push: la nube solo ve lo que está en origin`);
610
+ return EXIT_FAIL;
611
+ }
612
+ const head = await tryCapture(deps, "git", ["rev-parse", "HEAD"], gitRoot);
613
+ const remote = (await tryCapture(deps, "git", ["ls-remote", "origin", `refs/heads/${branch}`], gitRoot))?.split(/\s/)[0];
614
+ if (!head || remote !== head) {
615
+ deps.print(`${OUTPUT_PREFIX}la rama ${branch} no está subida o tiene commits sin subir; haz git push y vuelve a correr verify`);
616
+ return EXIT_FAIL;
617
+ }
618
+ const found = await tryCapture(deps, "gh", ["pr", "view", branch, "--json", "number", "--jq", ".number"], gitRoot);
619
+ const pr = found !== undefined && PR_NUMBER.test(found) ? found : undefined;
620
+ const progress = {};
621
+ const stop = deps.onInterrupt?.(() => !progress.childOwnsTerminal);
622
+ try {
623
+ await runInteractive(deps, progress, ["--cloud", verifyPrompt({ branch, pr })], gitRoot);
624
+ }
625
+ catch (err) {
626
+ deps.print(`${OUTPUT_PREFIX}falló claude --cloud: ${message(err)}`);
627
+ return EXIT_FAIL;
628
+ }
629
+ finally {
630
+ stop?.();
631
+ }
632
+ deps.print(`${OUTPUT_PREFIX}la sesión cloud avisará con un push a ${branch}${pr ? " y un comentario en el PR" : ""}`);
491
633
  return EXIT_OK;
492
634
  }