agent-kitchen 0.8.0 → 0.10.0

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/README.md CHANGED
@@ -26,7 +26,7 @@ Al arrancar revisa si npm tiene una versión más nueva de sí mismo. Si la hay,
26
26
  |---|---|---|---|---|---|
27
27
  | `skills-manager` | La versión de [`@ramon-sg/skills-manager`](https://github.com/ramon-sg/skills-manager) que corre `agent-kitchen`: como mínimo 1.2.0. Si falta la agrega y si es menor la sube; nunca la baja. Un rango o `workspace:` no se toca, y avisa si la versión que corre queda bajo el mínimo. Solo en repos que usan skills y tienen `package.json`, o donde issue-flow se instala en la misma corrida: entonces crea el `package.json` | `package.json` | — | La agrega `agent-kitchen` | La sube `agent-kitchen` |
28
28
  | `issue-flow` | Flujo de issues ([`@ramon-sg/issue-flow`](https://github.com/ramon-sg/issue-flow)): rama, worktree y PR en draft por issue, y sus 5 skills | `package.json` | npm | `issue-flow apply` (`init` antes de 0.3.0), y sus skills a la misma tag | `issue-flow apply` en la versión nueva (`upgrade` antes de 0.3.0), y sus skills a la misma tag. Desde 0.3.0, si su doctor ve hooks o el `$schema` atrás, `apply` en la versión del repo. Desde 0.3.0, si `github-rest` es una versión exacta bajo el mínimo que pide esa versión del flujo, la sube. Desde 0.2.6, si a GitHub le faltan labels del flujo (`spike`, `P1`–`P4`…), `issue-flow labels sync` los crea |
29
- | `claude-cloud` | Sesiones en la nube ([`@ramon-sg/claude-cloud`](https://github.com/ramon-sg/claude-cloud)): herramientas, Docker y el clone completo | `.claude/cloud.json` | npm | `claude-cloud init` | Cambia la versión que fija `.claude/cloud.json` |
29
+ | `agent-cloud-kit` | Sesiones en la nube ([`agent-cloud-kit`](https://github.com/ramon-sg/agent-cloud-kit)): herramientas, Docker y el clone completo | `.claude/cloud.json`, `.agents/skills/cloud-ready/` | npm | `agent-cloud-kit init` | Cambia la versión que fija `.claude/cloud.json` y corre el `init` de la versión nueva, para que la skill `cloud-ready` quede igual. Al terminar `apply` sugiere `npx agent-cloud-kit prepare` |
30
30
  | `claude-repo-guard` | Barandas ([`claude-repo-guard`](https://github.com/ramon-sg/claude-repo-guard)): modo auto con sandbox, escribe solo en su worktree | `.claude/repo-guard.manifest.json` | Tags `vX.Y.Z` de GitHub | `apply` en esa tag (`install` antes de 0.5.0) | `apply` en la tag nueva |
31
31
  | `skills` | Skills con [`skills-manager`](https://github.com/ramon-sg/skills-manager): las del flujo y las de [`agent-skills`](https://github.com/ramon-sg/agent-skills) (planes, auditoría de código y de tests) | `.agents/skills.lock.json` | Tags del catálogo | `skills-manager init --select` con las elegidas | `skills-manager update` |
32
32
  | `agents-md` | Una línea en `AGENTS.md` que le pide al agente mirar la versión instalada de una biblioteca antes de usarla. Si el repo tiene `CLAUDE.md`, le agrega `@AGENTS.md`, porque si no Claude Code no lee `AGENTS.md`. Solo en repos con `package.json` | `AGENTS.md` | — | La escribe `agent-kitchen` | — |
@@ -48,9 +48,10 @@ Si npm o GitHub no responden, la pieza se muestra pero no se ofrece actualizarla
48
48
  npx agent-kitchen apply # pregunta, en una terminal (Enter: todo lo sugerido)
49
49
  npx agent-kitchen apply --dry-run # solo muestra qué haría
50
50
  npx agent-kitchen apply --yes # hace lo sugerido sin preguntar
51
- npx agent-kitchen apply --commit # al final, pregunta si commitear, en una rama o con PR
51
+ npx agent-kitchen apply --pr # en una copia de origin, y abre un PR en draft
52
+ npx agent-kitchen apply --here # en tu copia, sin commit
52
53
  npx agent-kitchen apply --verbose # muestra la salida de cada herramienta
53
- npx agent-kitchen apply --skip claude-cloud # deja fuera una pieza (o varias, con comas)
54
+ npx agent-kitchen apply --skip agent-cloud-kit # deja fuera una pieza (o varias, con comas)
54
55
  npx agent-kitchen apply --pieces # cambia qué piezas usa el repo (solo en una terminal)
55
56
  npx agent-kitchen doctor # revisa cada pieza con su propio doctor
56
57
  ```
@@ -59,14 +60,16 @@ npx agent-kitchen doctor # revisa cada pieza con su propio
59
60
 
60
61
  - **Sin terminal y sin `--yes`** (por ejemplo, desde un agente), solo muestra lo que ofrecería.
61
62
  - **Corre en el checkout principal,** no en un worktree, y desde tu terminal. Dentro de una sesión de Claude, el sandbox de `claude-repo-guard` no deja escribir `.claude/settings.json`.
62
- - **Al terminar,** si los pasos cambiaron `package.json`, corre el `install` de tu gestor de paquetes. Te queda revisar el diff y hacer commit. O usa `--commit`.
63
+ **Dónde trabaja.** En una terminal, sin `--pr` ni `--here`, lo primero que pregunta es dónde aplicar los cambios. Con `--yes`, sin terminal o sin remoto `origin`, trabaja en tu copia.
63
64
 
64
- **`--commit`** deja los cambios commiteados sin que lo haga un agente:
65
+ **`--pr`** deja los cambios en un PR sin tocar tu copia:
65
66
 
66
- - **Antes de cualquier paso,** exige el repo sin cambios (también sin seguimiento; los worktrees de `.claude/worktrees/` no cuentan) y en la rama base: `baseBranch` de `.agents/flow.json`, si no la rama por defecto de `origin`, si no `main`. Con `--dry-run` hace solo esta revisión y lista los pasos.
67
- - **Al terminar,** si cambió `package.json`, corre el `install` de tu gestor para que el lockfile entre en el commit. Muestra los archivos cambiados y pregunta: dejarlos sin commit (por defecto), commit en la base, commit en una rama nueva `chore/agent-kitchen-apply-<fecha>`, o rama nueva con push y PR (con `@ramon-sg/github-rest`).
68
- - **Con rama,** la copia principal pasa por la rama solo mientras corre el comando: commitea ahí (los hooks de git corren, y uno que prohíbe commitear en la base no la frena) y vuelve a la base sin los cambios. Si el push o el PR fallan, la rama queda y la salida dice qué correr.
69
- - **Con `--yes`,** elige la rama con PR. Si algún paso falló, no commitea.
67
+ - **Parte de origin, no de tu copia.** Hace `git fetch` y clona el repo en una carpeta temporal en `origin/<base>`: `baseBranch` de `.agents/flow.json`, si no la rama por defecto de `origin`, si no `main`. Da igual si tu copia está atrás, en otra rama o con cambios: ni sus archivos ni su `node_modules` cambian. Es un clon y no un worktree porque `issue-flow apply` no corre en un worktree; el clon comparte los objetos del repo, así que no copia el historial.
68
+ - **Al terminar,** instala las dependencias en el clon (los hooks del repo pueden necesitarlas) y hace el commit con los hooks, el nombre y la firma de tu repo. El lockfile entra solo si cambió `package.json`.
69
+ - **Sube la rama directo a origin.** Tu repo empuja el commit a `chore/agent-kitchen-apply-<fecha>` sin crear una rama local, y abre el PR en draft con `@ramon-sg/github-rest`. Tras el merge, `git fetch --prune` limpia la referencia `origin/chore/…`.
70
+ - **Si algo falla antes del push** (un paso, el install o el commit), no sube nada y deja el clon para revisarlo; la salida dice dónde está. Si el push funciona y el PR falla, la salida dice qué correr.
71
+
72
+ **`--here`** trabaja en tu copia, como `ng update` o `copier update`. Avisa si tu copia tiene cambios sin commit o si tu rama está atrás de origin según el último fetch. Si los pasos cambiaron `package.json`, corre el `install` de tu gestor de paquetes al final. Te queda revisar el diff y hacer commit.
70
73
 
71
74
  **`doctor`** corre el doctor de cada herramienta en la versión del repo: `issue-flow doctor --json`, `skills-manager check` y `claude-repo-guard doctor --json`. Además revisa que `skills-manager` esté al menos en 1.2.0, que las skills del flujo estén en la tag de su CLI, qué labels del flujo le faltan a GitHub (con `·`), que `.claude/cloud.json` fije una versión exacta y, en macOS, `weakerNetworkIsolation`. En un repo con `package.json`, también la línea de `AGENTS.md` y, si hay `CLAUDE.md`, su `@AGENTS.md`. Lo que no está instalado o tiene una versión nueva solo se anota con `·`. Sale con 1 si algo falla (`✗`).
72
75
 
package/dist/cli.js CHANGED
@@ -14,25 +14,27 @@ import { KitError, processOutput, UsageError } from "./output.js";
14
14
  import { FLOW_PACKAGE, GENERAL_CATALOGS } from "./piece.js";
15
15
  import { PACKAGE as CLOUD_PACKAGE } from "./pieces/cloud.js";
16
16
  import { REPO as GUARD_REPO } from "./pieces/guard.js";
17
- import { compareVersions, isRelease, latestTag, npmLatest } from "./tools.js";
17
+ import { compareVersions, isRelease, latestTag, npmLatest, npmLatestExact } from "./tools.js";
18
18
  /** This package on npm. */
19
19
  const PACKAGE = "agent-kitchen";
20
20
  /** Set in the run that a newer agent-kitchen was handed to, so it never hands over again. */
21
21
  const HANDED_OVER = "AGENT_KITCHEN_HANDED_OVER";
22
22
  /** Usage text. */
23
23
  const USAGE = `Uso:
24
- agent-kitchen apply [--yes | --dry-run] [--commit] [--verbose] [--skip <pieza>,...]
25
- agent-kitchen apply --pieces [--commit]
24
+ agent-kitchen apply [--yes | --dry-run] [--pr | --here] [--verbose] [--skip <pieza>,...]
25
+ agent-kitchen apply --pieces [--pr | --here]
26
26
  agent-kitchen doctor
27
27
 
28
28
  agent-kitchen revisa cada pieza de las herramientas de agentes del repo (${pieceNames().join(", ")}),
29
29
  muestra qué tiene el repo y la última versión de cada una, y apply ofrece instalar lo que falta
30
30
  y actualizar lo que está atrasado. En una terminal pregunta una vez: hacer todo lo sugerido,
31
31
  o elegir los pasos y las skills. --yes hace lo sugerido sin preguntar; sin terminal solo lo
32
- muestra. --verbose muestra la salida de cada herramienta. --commit exige el repo sin cambios
33
- y en la rama base; al final instala si cambió package.json y pregunta si dejar los cambios,
34
- commitearlos en la base, en una rama nueva, o en una rama nueva con push y PR (con --yes,
35
- esto último). apply --pieces elige qué piezas usa el repo y lo guarda en .agents/kitchen.json
32
+ muestra. --verbose muestra la salida de cada herramienta.
33
+ --pr trabaja en una copia temporal de origin/<base> recién bajada, sin tocar tu copia: instala
34
+ las dependencias, hace el commit, sube una rama nueva a origin y abre un PR en draft. --here
35
+ trabaja en tu copia y deja los cambios sin commit; si los pasos cambiaron package.json, corre
36
+ el install. En una terminal, sin ninguna de las dos, pregunta; con --yes o sin terminal, usa
37
+ --here. apply --pieces elige qué piezas usa el repo y lo guarda en .agents/kitchen.json
36
38
  (en una terminal, sin --yes ni --dry-run). Córrelo en la copia principal del
37
39
  repo, desde tu terminal. Sin subcomando hace lo mismo que apply, por ahora.
38
40
 
@@ -50,7 +52,7 @@ function ownVersion() {
50
52
  */
51
53
  function startLookups() {
52
54
  void npmLatest(FLOW_PACKAGE);
53
- void npmLatest(CLOUD_PACKAGE);
55
+ void npmLatestExact(CLOUD_PACKAGE);
54
56
  void latestTag(GUARD_REPO);
55
57
  for (const catalog of GENERAL_CATALOGS)
56
58
  void latestTag(catalog);
@@ -82,7 +84,7 @@ async function main(argv) {
82
84
  const { values, positionals } = parseArgs({
83
85
  args: argv,
84
86
  allowPositionals: true,
85
- options: { yes: { type: "boolean" }, "dry-run": { type: "boolean" }, commit: { type: "boolean" }, verbose: { type: "boolean", short: "v" }, skip: { type: "string" }, pieces: { type: "boolean" }, help: { type: "boolean", short: "h" }, version: { type: "boolean" } },
87
+ options: { yes: { type: "boolean" }, "dry-run": { type: "boolean" }, commit: { type: "boolean" }, pr: { type: "boolean" }, here: { type: "boolean" }, verbose: { type: "boolean", short: "v" }, skip: { type: "string" }, pieces: { type: "boolean" }, help: { type: "boolean", short: "h" }, version: { type: "boolean" } },
86
88
  });
87
89
  if (values.help === true) {
88
90
  out.stdout(`${USAGE}\n`);
@@ -100,14 +102,16 @@ async function main(argv) {
100
102
  if (rest.length > 0 || (command !== undefined && command !== "doctor" && command !== "apply"))
101
103
  throw new UsageError(`no existe el comando ${positionals.join(" ")}.`);
102
104
  if (command === "doctor") {
103
- if (values.yes !== undefined || values["dry-run"] !== undefined || values.skip !== undefined || values.verbose !== undefined || values.commit !== undefined || values.pieces !== undefined)
105
+ if (values.yes !== undefined || values["dry-run"] !== undefined || values.skip !== undefined || values.verbose !== undefined || values.commit !== undefined || values.pr !== undefined || values.here !== undefined || values.pieces !== undefined)
104
106
  throw new UsageError("doctor no lleva opciones.");
105
107
  return doctor(process.cwd(), out);
106
108
  }
109
+ if (values.commit !== undefined)
110
+ throw new UsageError("--commit ya no existe: usa --pr para abrir un PR desde una copia de origin, o --here para dejar los cambios sin commit en tu copia.");
107
111
  if (command === undefined)
108
112
  out.stderr("agent-kitchen: usa 'agent-kitchen apply'; sin subcomando dejará de instalar en una versión mayor.\n");
109
113
  const skip = (values.skip ?? "").split(",").map((name) => name.trim()).filter((name) => name !== "");
110
- return sync({ cwd: process.cwd(), yes: values.yes ?? false, dryRun: values["dry-run"] ?? false, verbose: values.verbose ?? false, skip, commit: values.commit ?? false, pieces: values.pieces ?? false }, out);
114
+ return sync({ cwd: process.cwd(), yes: values.yes ?? false, dryRun: values["dry-run"] ?? false, verbose: values.verbose ?? false, skip, pr: values.pr ?? false, here: values.here ?? false, pieces: values.pieces ?? false }, out);
111
115
  }
112
116
  try {
113
117
  process.exitCode = await main(process.argv.slice(2));
@@ -7,14 +7,17 @@ export interface SyncOptions {
7
7
  dryRun: boolean;
8
8
  verbose: boolean;
9
9
  skip: string[];
10
- commit: boolean;
10
+ /** Work in a clone of origin and open a PR. */
11
+ pr: boolean;
12
+ /** Work in the checkout, leaving the changes uncommitted. */
13
+ here: boolean;
11
14
  /** Ask which pieces the repo uses, save the answer, then go on as usual. */
12
15
  pieces: boolean;
13
16
  }
14
17
  /**
15
18
  * The names `--skip` takes.
16
19
  * @returns The pieces' names.
17
- * @example pieceNames() // ["skills-manager", "issue-flow", "claude-cloud", "claude-repo-guard", "skills", "agents-md"]
20
+ * @example pieceNames() // ["skills-manager", "issue-flow", "agent-cloud-kit", "claude-repo-guard", "skills", "agents-md"]
18
21
  */
19
22
  export declare function pieceNames(): string[];
20
23
  /**
@@ -26,7 +29,7 @@ export declare function pieceNames(): string[];
26
29
  */
27
30
  export declare function noStepsMessage(why: "leave" | "none", wrote: boolean): string;
28
31
  /**
29
- * What the commit of `--commit` lists: the steps that worked, and the decisions file if this
32
+ * What the commit of `--pr` lists: the steps that worked, and the decisions file if this
30
33
  * run wrote it, in the commit's texts (not the interface's).
31
34
  * @param done - The steps that worked.
32
35
  * @param wrote - Whether the decisions file was written in this run.
@@ -40,6 +43,6 @@ export declare function commitLabels(done: Pick<Step, "text">[], wrote: boolean)
40
43
  * @param out - Where to write and ask.
41
44
  * @returns 0, or 1 if a step failed or the person cancelled.
42
45
  * @throws KitError outside the main checkout or with a broken file; UsageError for an unknown piece.
43
- * @example await sync({ cwd, yes: false, dryRun: true, verbose: false, skip: [], commit: false, pieces: false }, out)
46
+ * @example await sync({ cwd, yes: false, dryRun: true, verbose: false, skip: [], pr: false, here: false, pieces: false }, out)
44
47
  */
45
48
  export declare function sync(options: SyncOptions, out: Output): Promise<number>;
@@ -1,7 +1,11 @@
1
1
  /**
2
- * @file `agent-kitchen apply [--yes] [--dry-run] [--commit] [--verbose] [--skip <piece>,...]`: the one command
3
- * that changes the repo (and, for now, also the bare `agent-kitchen`).
4
- * It looks at each piece (the version of skills-manager, the issue flow, claude-cloud,
2
+ * @file `agent-kitchen apply [--yes] [--dry-run] [--pr | --here] [--verbose] [--skip <piece>,...]`:
3
+ * the one command that changes the repo (and, for now, also the bare `agent-kitchen`).
4
+ * First it settles where to work: `--pr` in a clone of `origin/<base>` that ends in a PR in
5
+ * draft (see pr-clone.ts), `--here` in the person's checkout, leaving the changes uncommitted.
6
+ * In a terminal, without either, it asks; with `--yes` or without a terminal, or without an
7
+ * origin, it works here. Here it warns if the checkout has changes or is behind its upstream.
8
+ * Then it looks at each piece (the version of skills-manager, the issue flow, agent-cloud-kit,
5
9
  * claude-repo-guard, the skills, the rule in AGENTS.md) with
6
10
  * every lookup at once, shows what the repo has, and offers the steps that install what is
7
11
  * missing or update what is behind. In a terminal it asks first, all in one go: everything
@@ -11,11 +15,10 @@
11
15
  * flow's version), one line each, with the
12
16
  * tools' output only when one fails; one that fails does not stop the rest. With `--yes`
13
17
  * it does what is suggested without asking (and keeps the repo's own skills); without a
14
- * terminal it only shows the steps. Without `--commit`, it installs at the end if the steps
15
- * changed package.json. With `--commit` it first checks the repo is clean and on
16
- * its base branch, and at the end installs if package.json changed and asks what to do with
17
- * the changes (see git-finish.ts), also when the only change is the decisions file below;
18
- * the steps then show as plain lines, since a spinner before a question leaves it deaf.
18
+ * terminal it only shows the steps. Here, it installs at the end if the steps changed
19
+ * package.json. In the clone it always installs (the repo's hooks may need node_modules),
20
+ * commits, pushes and opens the PR, also when the only change is the decisions file below;
21
+ * if a step, the install or the commit fails, nothing is pushed and the clone is kept.
19
22
  * What the repo decided of each piece, in `.agents/kitchen.json`, is respected: a piece set to
20
23
  * false is left out as with `--skip`, and one with no decision is marked as new. Without
21
24
  * asking (`--yes` or no terminal), a new piece that is not installed is left out for the run.
@@ -25,8 +28,8 @@
25
28
  * uses, writes all of them to the file, and then goes on as usual.
26
29
  */
27
30
  import { DECIDABLE, decide, DECISIONS, off, readDecisions, undecided, writeDecisions } from "../decisions.js";
28
- import { baseBranch, branchName, changedPaths, checkReady, finish } from "../git-finish.js";
29
- import { askFinish, askPath, askPieces, askReplace, askSkills, askSteps } from "../menu.js";
31
+ import { behindUpstream, baseBranch, changedPaths, cloneFromOrigin, originUrl, publish, removeClone } from "../pr-clone.js";
32
+ import { askPath, askPieces, askPlace, askReplace, askSkills, askSteps } from "../menu.js";
30
33
  import { KitError, UsageError, warn } from "../output.js";
31
34
  import { cloudStatus, NAME as CLOUD } from "../pieces/cloud.js";
32
35
  import { guardStatus, NAME as GUARD } from "../pieces/guard.js";
@@ -34,7 +37,7 @@ import { issueFlowStatus, NAME as FLOW } from "../pieces/issue-flow.js";
34
37
  import { keepOwn, NAME as SKILLS, ownInPick, skillsStatus } from "../pieces/skills.js";
35
38
  import { agentsMdStatus, NAME as AGENTS_MD } from "../pieces/agents-md.js";
36
39
  import { NAME as SKILLS_MANAGER, skillsManagerStatus } from "../pieces/skills-manager.js";
37
- import { mainCheckout, packageManager, packageText } from "../repo.js";
40
+ import { mainCheckout, packageManager, packageText, readJson } from "../repo.js";
38
41
  import { download, failedOutput, runInstall, setVerbose } from "../tools.js";
39
42
  import { commitLine, DECISIONS_LINE } from "../commit-texts.js";
40
43
  import { progressFor } from "../progress.js";
@@ -44,7 +47,7 @@ const PIECES = [SKILLS_MANAGER, FLOW, CLOUD, GUARD, SKILLS, AGENTS_MD];
44
47
  /**
45
48
  * The names `--skip` takes.
46
49
  * @returns The pieces' names.
47
- * @example pieceNames() // ["skills-manager", "issue-flow", "claude-cloud", "claude-repo-guard", "skills", "agents-md"]
50
+ * @example pieceNames() // ["skills-manager", "issue-flow", "agent-cloud-kit", "claude-repo-guard", "skills", "agents-md"]
48
51
  */
49
52
  export function pieceNames() {
50
53
  return [...PIECES];
@@ -96,7 +99,7 @@ function printStatus(statuses, decidedOff, out) {
96
99
  * @param saved - What was read, or null if there is no file.
97
100
  * @param statuses - What each piece found.
98
101
  * @param chosen - The steps the person picked.
99
- * @param commits - Whether `--commit` asks about the changes at the end.
102
+ * @param commits - Whether the run ends in a PR, so the file need not be committed by hand.
100
103
  * @param out - Where to write.
101
104
  * @returns True if the file was written.
102
105
  */
@@ -122,14 +125,14 @@ export function noStepsMessage(why, wrote) {
122
125
  }
123
126
  /**
124
127
  * The line that says the decisions were saved.
125
- * @param commits - Whether `--commit` asks about the changes at the end, so the file need not be committed by hand.
128
+ * @param commits - Whether the run ends in a PR, so the file need not be committed by hand.
126
129
  * @returns The line.
127
130
  */
128
131
  function savedMessage(commits) {
129
132
  return `${ui.saved(DECISIONS, commits)}\n`;
130
133
  }
131
134
  /**
132
- * What the commit of `--commit` lists: the steps that worked, and the decisions file if this
135
+ * What the commit of `--pr` lists: the steps that worked, and the decisions file if this
133
136
  * run wrote it, in the commit's texts (not the interface's).
134
137
  * @param done - The steps that worked.
135
138
  * @param wrote - Whether the decisions file was written in this run.
@@ -144,7 +147,7 @@ export function commitLabels(done, wrote) {
144
147
  * Asks which pieces the repo uses and writes the answer for every decidable piece.
145
148
  * @param root - The repo.
146
149
  * @param saved - What was read, or null if there is no file.
147
- * @param commits - Whether `--commit` asks about the changes at the end.
150
+ * @param commits - Whether the run ends in a PR.
148
151
  * @param out - Where to write.
149
152
  * @returns False if the person cancelled.
150
153
  */
@@ -279,13 +282,45 @@ async function runStep(root, step, progress, out) {
279
282
  out.stderr(failure);
280
283
  return ok;
281
284
  }
285
+ /**
286
+ * Settles where to work: the flag, or the person's answer in a terminal, or the checkout.
287
+ * @param root - The repo.
288
+ * @param options - The options.
289
+ * @param out - Where to write and ask.
290
+ * @returns The place, or null if the person cancelled.
291
+ * @throws KitError with `--pr` and no origin.
292
+ */
293
+ async function choosePlace(root, options, out) {
294
+ if (options.pr) {
295
+ if (originUrl(root) === null)
296
+ throw new KitError("--pr necesita un remoto origin, y este repo no tiene. Usa --here.");
297
+ return "pr";
298
+ }
299
+ if (options.here || options.yes || options.dryRun || !out.interactive || originUrl(root) === null)
300
+ return "here";
301
+ return askPlace(baseBranch(root));
302
+ }
303
+ /**
304
+ * Warns about what the checkout already has before working on it: changes that will mix with
305
+ * the run's, or a branch behind its upstream.
306
+ * @param root - The repo.
307
+ * @param out - Where to write.
308
+ */
309
+ function warnHere(root, out) {
310
+ const changed = changedPaths(root);
311
+ if (changed.length > 0)
312
+ warn(out, `tu copia tiene cambios sin commit (${changed.length === 1 ? changed[0] : `${changed.length} archivos`}); los de apply se mezclarán con ellos.`);
313
+ const behind = behindUpstream(root);
314
+ if (behind > 0)
315
+ warn(out, `tu rama está ${behind} ${behind === 1 ? "commit" : "commits"} atrás de origin, según el último fetch; corre git pull antes, o usa --pr.`);
316
+ }
282
317
  /**
283
318
  * Runs the command; see the file's header.
284
319
  * @param options - The options.
285
320
  * @param out - Where to write and ask.
286
321
  * @returns 0, or 1 if a step failed or the person cancelled.
287
322
  * @throws KitError outside the main checkout or with a broken file; UsageError for an unknown piece.
288
- * @example await sync({ cwd, yes: false, dryRun: true, verbose: false, skip: [], commit: false, pieces: false }, out)
323
+ * @example await sync({ cwd, yes: false, dryRun: true, verbose: false, skip: [], pr: false, here: false, pieces: false }, out)
289
324
  */
290
325
  export async function sync(options, out) {
291
326
  const unknown = options.skip.find((name) => !pieceNames().includes(name));
@@ -293,21 +328,51 @@ export async function sync(options, out) {
293
328
  throw new UsageError(`no existe la pieza ${unknown}; son ${pieceNames().join(", ")}.`);
294
329
  if (options.yes && options.dryRun)
295
330
  throw new UsageError("usa --yes o --dry-run, no los dos.");
331
+ if (options.pr && options.here)
332
+ throw new UsageError("usa --pr o --here, no los dos.");
296
333
  if (options.pieces && (options.yes || options.dryRun))
297
334
  throw new UsageError("usa --pieces solo, sin --yes ni --dry-run.");
298
335
  if (options.pieces && !out.interactive)
299
336
  throw new UsageError("--pieces pregunta qué piezas usa el repo; córrelo en una terminal.");
300
337
  const root = mainCheckout(options.cwd, "agent-kitchen apply");
301
- const base = options.commit ? baseBranch(root) : null;
302
- if (base !== null)
303
- checkReady(root, base);
304
338
  setVerbose(options.verbose);
339
+ const place = await choosePlace(root, options, out);
340
+ if (place === null) {
341
+ out.stdout("No se cambió nada.\n");
342
+ return 1;
343
+ }
344
+ if (place === "here") {
345
+ if (!options.dryRun)
346
+ warnHere(root, out);
347
+ return applyIn({ kind: "here", root }, options, out);
348
+ }
349
+ const base = baseBranch(root);
350
+ out.stdout(`${ui.cloning(base)}\n`);
351
+ const work = { kind: "pr", root: cloneFromOrigin(root, base), repo: root, base, keep: false };
352
+ try {
353
+ return await applyIn(work, options, out);
354
+ }
355
+ finally {
356
+ if (!work.keep)
357
+ removeClone(work.root);
358
+ }
359
+ }
360
+ /**
361
+ * Looks at the pieces, asks, runs the steps and ends as the place says.
362
+ * @param work - Where to work.
363
+ * @param options - The options.
364
+ * @param out - Where to write and ask.
365
+ * @returns 0, or 1 if a step failed or the person cancelled.
366
+ */
367
+ async function applyIn(work, options, out) {
368
+ const { root } = work;
369
+ const pr = work.kind === "pr";
305
370
  let saved = readDecisions(root);
306
371
  let wrote = false;
307
372
  for (const warning of saved?.warnings ?? [])
308
373
  warn(out, warning);
309
374
  if (options.pieces) {
310
- if (!(await choosePieces(root, saved, base !== null, out))) {
375
+ if (!(await choosePieces(root, saved, pr, out))) {
311
376
  out.stdout("No se cambió nada.\n");
312
377
  return 0;
313
378
  }
@@ -338,18 +403,24 @@ export async function sync(options, out) {
338
403
  }
339
404
  printStatus(statuses, off(saved).filter((name) => !options.skip.includes(name)), out);
340
405
  const steps = statuses.flatMap((status) => status.steps);
341
- // With --commit, a run that only wrote the decisions file still ends with the question.
342
- const progress = progressFor(out, options.verbose, base !== null);
343
- const endWithoutSteps = async () => (base !== null && wrote ? commitChanges(root, base, commitLabels([], true), [], progress, options, out) : 0);
406
+ const progress = progressFor(out, options.verbose);
407
+ // In a clone, a run that only wrote the decisions file still ends in a PR.
408
+ const endWithoutSteps = async () => {
409
+ if (work.kind === "pr")
410
+ return wrote ? openPr(work, commitLabels([], true), [], progress, out) : 0;
411
+ if (!options.dryRun)
412
+ printCloudNext(root, skip, out);
413
+ return 0;
414
+ };
344
415
  if (steps.length === 0) {
345
416
  if (saves)
346
- wrote = saveDecisions(root, saved, statuses, [], base !== null, out) || wrote;
417
+ wrote = saveDecisions(root, saved, statuses, [], pr, out) || wrote;
347
418
  out.stdout(`${ui.upToDate}\n`);
348
419
  return endWithoutSteps();
349
420
  }
350
421
  const chosen = await chooseSteps(root, steps, options, out);
351
422
  if (saves && Array.isArray(chosen))
352
- wrote = saveDecisions(root, saved, statuses, chosen, base !== null, out) || wrote;
423
+ wrote = saveDecisions(root, saved, statuses, chosen, pr, out) || wrote;
353
424
  if (chosen === "leave") {
354
425
  out.stdout(noStepsMessage("leave", wrote));
355
426
  return endWithoutSteps();
@@ -375,14 +446,26 @@ export async function sync(options, out) {
375
446
  failedSteps.push(step);
376
447
  }
377
448
  const failed = failedSteps.map(stepLabel);
378
- if (base !== null)
379
- return commitChanges(root, base, commitLabels(chosen.filter((step) => !failedSteps.includes(step)), wrote), failed, progress, options, out);
449
+ if (work.kind === "pr")
450
+ return openPr(work, commitLabels(chosen.filter((step) => !failedSteps.includes(step)), wrote), failed, progress, out);
380
451
  const installFailed = packageText(root) !== pkgBefore ? !(await install(root, progress, out)) : false;
381
452
  if (installFailed)
382
453
  failed.push(phrases({ kind: "install-deps", manager: packageManager(root) }).menu);
383
454
  out.stdout(`\n${ui.summary(failed)} ${ui.next(installFailed ? packageManager(root) : null)}\n`);
455
+ if (!options.dryRun)
456
+ printCloudNext(root, skip, out);
384
457
  return failed.length === 0 ? 0 : 1;
385
458
  }
459
+ /**
460
+ * Says what to run next for the cloud, if agent-cloud-kit is installed and was not left out.
461
+ * @param root - The repo.
462
+ * @param skip - The pieces left out.
463
+ * @param out - Where to write.
464
+ */
465
+ function printCloudNext(root, skip, out) {
466
+ if (!skip.includes(CLOUD) && readJson(root, ".claude/cloud.json") !== null)
467
+ out.stdout(`${ui.cloudNext}\n`);
468
+ }
386
469
  /**
387
470
  * Runs the repo's install with the same progress as a step, and shows its output if it fails.
388
471
  * @param root - The repo.
@@ -400,40 +483,42 @@ async function install(root, progress, out) {
400
483
  return installed.ok;
401
484
  }
402
485
  /**
403
- * The end of `--commit`: the install if package.json changed, the changes, the question (or
404
- * the branch with a PR under --yes) and what git did.
405
- * @param root - The repo.
406
- * @param base - The base branch.
486
+ * The end of a run in a clone: the install, the changes, the commit, the push and the PR.
487
+ * Nothing is pushed if a step, the install or the commit failed; the clone is kept then.
488
+ * @param work - The clone.
407
489
  * @param done - What the commit lists: the steps that worked, and the decisions file if written.
408
490
  * @param failed - The steps that failed, as the interface names them.
409
491
  * @param progress - How progress is shown, for the install.
410
- * @param options - The options.
411
- * @param out - Where to write and ask.
412
- * @returns 0, or 1 if a step, the install or git failed.
492
+ * @param out - Where to write.
493
+ * @returns 0, or 1 if a step, the install, git or the PR failed.
413
494
  */
414
- async function commitChanges(root, base, done, failed, progress, options, out) {
495
+ async function openPr(work, done, failed, progress, out) {
415
496
  const summary = ui.summary(failed);
416
- const changed = changedPaths(root);
417
- if (changed.length === 0) {
418
- out.stdout(`\n${summary} No cambió ningún archivo: no hay nada que commitear.\n`);
497
+ const before = changedPaths(work.root);
498
+ if (before.length === 0) {
499
+ out.stdout(`\n${summary} ${ui.noChanges}\n`);
419
500
  return failed.length === 0 ? 0 : 1;
420
501
  }
421
- const manager = packageManager(root);
422
- const packageChanged = changed.includes("package.json");
423
- if (packageChanged && !(await install(root, progress, out))) {
424
- out.stdout(`\n${summary} No commiteé: el install falló y los cambios quedan sin commit.\n`);
502
+ if (failed.length > 0) {
503
+ work.keep = true;
504
+ out.stdout(`\n${summary} ${ui.prNotOpened("step", work.root)}\n`);
425
505
  return 1;
426
506
  }
427
- const paths = changedPaths(root);
428
- out.stdout(`\n${summary} Cambios:\n${paths.map((path) => ` ${path}`).join("\n")}\n`);
429
- if (failed.length > 0 && options.yes) {
430
- out.stdout("No commiteé porque algún paso falló; los cambios quedan sin commit.\n");
507
+ // A clone starts without node_modules, and the repo's hooks may need them.
508
+ if (packageText(work.root) !== null && !(await install(work.root, progress, out))) {
509
+ work.keep = true;
510
+ out.stdout(`\n${summary} ${ui.prNotOpened("install", work.root)}\n`);
431
511
  return 1;
432
512
  }
433
- const branch = branchName(root, new Date());
434
- const choice = options.yes ? "pr" : await askFinish(base, branch, failed.length > 0);
435
- const result = await finish(root, choice, { base, branch, steps: done, paths, manager, packageChanged });
513
+ // Only a changed package.json brings the lockfile along; an install that only rewrites it stays out.
514
+ const paths = before.includes("package.json") ? changedPaths(work.root) : before;
515
+ out.stdout(`\n${summary} ${ui.changes}\n${paths.map((path) => ` ${path}`).join("\n")}\n`);
516
+ const result = await publish(work.repo, work.root, { base: work.base, steps: done, paths });
436
517
  for (const line of result.lines)
437
518
  out.stdout(`${line}\n`);
438
- return result.ok && failed.length === 0 ? 0 : 1;
519
+ if (!result.pushed) {
520
+ work.keep = true;
521
+ out.stdout(`${ui.prNotOpened("git", work.root)}\n`);
522
+ }
523
+ return result.ok ? 0 : 1;
439
524
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @file The texts of the commit and the PR of `apply --commit`. They are always in Spanish,
2
+ * @file The texts of the commit and the PR of `apply --pr`. They are always in Spanish,
3
3
  * whatever language the interface shows: a repo's history should not change language with the
4
4
  * terminal of whoever ran the command. A step reads as its Spanish menu order.
5
5
  */
@@ -24,7 +24,7 @@ export declare function readDecisions(root: string): Saved | null;
24
24
  * The decidable pieces with no decision yet.
25
25
  * @param saved - What was read, or null if there is no file.
26
26
  * @returns Their names; all of them without a file.
27
- * @example undecided(null) // ["issue-flow", "claude-cloud", ...]
27
+ * @example undecided(null) // ["issue-flow", "agent-cloud-kit", ...]
28
28
  */
29
29
  export declare function undecided(saved: Saved | null): string[];
30
30
  /**
package/dist/decisions.js CHANGED
@@ -43,7 +43,7 @@ export function readDecisions(root) {
43
43
  * The decidable pieces with no decision yet.
44
44
  * @param saved - What was read, or null if there is no file.
45
45
  * @returns Their names; all of them without a file.
46
- * @example undecided(null) // ["issue-flow", "claude-cloud", ...]
46
+ * @example undecided(null) // ["issue-flow", "agent-cloud-kit", ...]
47
47
  */
48
48
  export function undecided(saved) {
49
49
  return DECIDABLE.filter((name) => saved?.pieces[name] === undefined);
package/dist/menu.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { FinishChoice } from "./git-finish.js";
2
1
  import type { Candidate, Step } from "./piece.js";
3
2
  /** What the person wants after seeing the table. */
4
3
  export type Path = "all" | "pick" | "leave";
@@ -77,13 +76,12 @@ export declare function askSkills(candidates: Candidate[], ticked: string[]): Pr
77
76
  * @example await askReplace(["planning"])
78
77
  */
79
78
  export declare function askReplace(own: string[]): Promise<boolean | null>;
79
+ /** Where apply works: a PR from a clone of origin, or the person's checkout. */
80
+ export type Place = "pr" | "here";
80
81
  /**
81
- * Asks, after the steps, what to do with the changes. Leaving them is the default, and
82
- * cancelling leaves them too.
82
+ * Asks, before anything else, where to apply the changes.
83
83
  * @param base - The base branch.
84
- * @param branch - The name of the new branch.
85
- * @param failed - Whether a step failed, to warn above the question.
86
- * @returns The answer.
87
- * @example await askFinish("main", "chore/agent-kitchen-apply-2026-11-01", false) // "pr"
84
+ * @returns The answer, or null if the person cancelled.
85
+ * @example await askPlace("main") // "pr"
88
86
  */
89
- export declare function askFinish(base: string, branch: string, failed: boolean): Promise<FinishChoice>;
87
+ export declare function askPlace(base: string): Promise<Place | null>;
package/dist/menu.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
- * @file The questions, in Spanish (`@clack/prompts`), all asked before any step runs: the
3
- * quick path (do everything suggested, pick, or leave), the checkbox menu of the steps, the
4
- * skills of a step grouped by catalog with their description, and whether to replace the
5
- * repo's own skills. They only run with a terminal.
2
+ * @file The questions, in Spanish (`@clack/prompts`), all asked before any step runs: where to
3
+ * apply (a PR or the checkout), the quick path (do everything suggested, pick, or leave), the
4
+ * checkbox menu of the steps, the skills of a step grouped by catalog with their description,
5
+ * and whether to replace the repo's own skills. They only run with a terminal.
6
6
  */
7
7
  import { styleText } from "node:util";
8
8
  import { confirm, groupMultiselect, isCancel, MULTISELECT_INSTRUCTIONS, multiselect, select, SELECT_INSTRUCTIONS } from "@clack/prompts";
@@ -123,25 +123,20 @@ export async function askReplace(own) {
123
123
  return isCancel(answer) ? null : answer;
124
124
  }
125
125
  /**
126
- * Asks, after the steps, what to do with the changes. Leaving them is the default, and
127
- * cancelling leaves them too.
126
+ * Asks, before anything else, where to apply the changes.
128
127
  * @param base - The base branch.
129
- * @param branch - The name of the new branch.
130
- * @param failed - Whether a step failed, to warn above the question.
131
- * @returns The answer.
132
- * @example await askFinish("main", "chore/agent-kitchen-apply-2026-11-01", false) // "pr"
128
+ * @returns The answer, or null if the person cancelled.
129
+ * @example await askPlace("main") // "pr"
133
130
  */
134
- export async function askFinish(base, branch, failed) {
131
+ export async function askPlace(base) {
135
132
  spanishKeys();
136
133
  const answer = await select({
137
- message: failed ? "Algún paso falló. ¿Qué hago con los cambios?" : "¿Qué hago con los cambios?",
138
- initialValue: "leave",
134
+ message: "¿Dónde aplico los cambios?",
135
+ initialValue: "pr",
139
136
  options: [
140
- { value: "leave", label: "Dejarlos sin commit" },
141
- { value: "base", label: `Commit en ${base}` },
142
- { value: "branch", label: `Commit en una rama nueva, ${branch}`, hint: `${base} queda como está` },
143
- { value: "pr", label: "Commit en una rama nueva, push y PR", hint: `contra ${base}` },
137
+ { value: "pr", label: "En un PR nuevo", hint: `recomendado · parte de origin/${base}, no toca tu copia` },
138
+ { value: "here", label: "En tu copia, sin commit", hint: "los revisas con git diff y haces commit tú" },
144
139
  ],
145
140
  });
146
- return isCancel(answer) ? "leave" : answer;
141
+ return isCancel(answer) ? null : answer;
147
142
  }
package/dist/output.d.ts CHANGED
@@ -24,6 +24,6 @@ export declare function processOutput(): Output;
24
24
  * Prints a warning on stderr with the program's name.
25
25
  * @param out - Where to write.
26
26
  * @param text - The warning.
27
- * @example warn(out, "claude-cloud init failed")
27
+ * @example warn(out, "agent-cloud-kit init failed")
28
28
  */
29
29
  export declare function warn(out: Output, text: string): void;
package/dist/output.js CHANGED
@@ -24,7 +24,7 @@ export function processOutput() {
24
24
  * Prints a warning on stderr with the program's name.
25
25
  * @param out - Where to write.
26
26
  * @param text - The warning.
27
- * @example warn(out, "claude-cloud init failed")
27
+ * @example warn(out, "agent-cloud-kit init failed")
28
28
  */
29
29
  export function warn(out, text) {
30
30
  out.stderr(`agent-kitchen: ${text}\n`);
package/dist/piece.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file What agent-kitchen knows of each piece it manages, and the names they share. A piece
3
- * (the version of skills-manager, the issue flow, claude-cloud, claude-repo-guard, the
3
+ * (the version of skills-manager, the issue flow, agent-cloud-kit, claude-repo-guard, the
4
4
  * skills, the rule in AGENTS.md) tells what the repo has and the steps that would install or
5
5
  * update it; agent-kitchen shows them, lets the person pick, and runs the picked ones in
6
6
  * order. Every step runs the piece's own tool, which owns its files: agent-kitchen only