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/dist/piece.js 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
@@ -1,17 +1,18 @@
1
1
  /**
2
- * @file Claude cloud sessions (`@ramon-sg/claude-cloud`). The repo's version is the one
2
+ * @file Claude cloud sessions (`agent-cloud-kit`). The repo's version is the one
3
3
  * `.claude/cloud.json` pins: the cloud machine runs exactly that one. Installing runs its
4
- * `init`; updating only moves that version (and a `$schema` pinned to a version), which is
5
- * how `claude-cloud` updates. Outside
6
- * the cloud its hook does nothing, so a repo nobody opens in the cloud loses nothing.
4
+ * `init`; updating moves that version (and a `$schema` pinned to a version) and then runs the
5
+ * new version's `init`, so the `cloud-ready` skill copied into the repo matches it (`init` is
6
+ * idempotent: it adds no second hook and keeps `.claude/cloud.json`). Outside the cloud its
7
+ * hook does nothing, so a repo nobody opens in the cloud loses nothing.
7
8
  */
8
9
  import type { Check, PieceStatus } from "../piece.js";
9
10
  /** The piece's name, as `--skip` takes it. */
10
- export declare const NAME = "claude-cloud";
11
+ export declare const NAME = "agent-cloud-kit";
11
12
  /** The package on npm. */
12
- export declare const PACKAGE = "@ramon-sg/claude-cloud";
13
+ export declare const PACKAGE = "agent-cloud-kit";
13
14
  /**
14
- * What the repo has of claude-cloud, and the step it needs.
15
+ * What the repo has of agent-cloud-kit, and the step it needs.
15
16
  * @param root - The repo.
16
17
  * @returns The piece's status.
17
18
  * @example (await cloudStatus(root)).state // "1.0.0, al día"
@@ -1,18 +1,18 @@
1
1
  import { readJson, writeJson } from "../repo.js";
2
- import { compareVersions, isRelease, npmLatest, runNpx } from "../tools.js";
2
+ import { compareSemver, isExactVersion, npmLatestExact, runNpx } from "../tools.js";
3
3
  /** The piece's name, as `--skip` takes it. */
4
- export const NAME = "claude-cloud";
4
+ export const NAME = "agent-cloud-kit";
5
5
  /** The package on npm. */
6
- export const PACKAGE = "@ramon-sg/claude-cloud";
6
+ export const PACKAGE = "agent-cloud-kit";
7
7
  /** Its config in the repo, which pins its version. */
8
8
  const CONFIG = ".claude/cloud.json";
9
9
  /**
10
- * The version `.claude/cloud.json` pins.
10
+ * The version `.claude/cloud.json` pins: exact semver, so a release candidate counts.
11
11
  * @param config - The config, or null.
12
12
  * @returns The version, or null if it has none.
13
13
  */
14
14
  function pinned(config) {
15
- return typeof config?.version === "string" && isRelease(config.version) ? config.version : null;
15
+ return typeof config?.version === "string" && isExactVersion(config.version) ? config.version : null;
16
16
  }
17
17
  /**
18
18
  * The step the repo needs, if any.
@@ -33,35 +33,40 @@ function stepFor(config, latest) {
33
33
  const run = await runNpx(context.root, [spec, "init"]);
34
34
  if (run.ok)
35
35
  return true;
36
- context.fail(`claude-cloud init falló; corre 'npx ${spec} init'.`, run.output);
36
+ context.fail(`agent-cloud-kit init falló; corre 'npx ${spec} init'.`, run.output);
37
37
  return false;
38
38
  },
39
39
  };
40
40
  }
41
- if (latest === null || (version !== null && compareVersions(latest, version) <= 0))
41
+ if (latest === null || (version !== null && compareSemver(latest, version) <= 0))
42
42
  return null;
43
43
  return {
44
44
  piece: NAME,
45
45
  checked: true,
46
- downloads: [],
46
+ downloads: [`${PACKAGE}@${latest}`],
47
47
  text: { kind: "cloud-update", from: version, to: latest, config: CONFIG },
48
48
  run: async (context) => {
49
49
  const current = readJson(context.root, CONFIG) ?? {};
50
- const schema = typeof current.$schema === "string" ? current.$schema.replace(/@ramon-sg\/claude-cloud@[^/]+\//, `${PACKAGE}@${latest}/`) : undefined;
50
+ const schema = typeof current.$schema === "string" ? current.$schema.replace(/\/agent-cloud-kit@[^/]+\//, `/${PACKAGE}@${latest}/`) : undefined;
51
51
  writeJson(context.root, CONFIG, { ...current, ...(schema === undefined ? {} : { $schema: schema }), version: latest });
52
- return true;
52
+ const spec = `${PACKAGE}@${latest}`;
53
+ const run = await runNpx(context.root, [spec, "init"]);
54
+ if (run.ok)
55
+ return true;
56
+ context.fail(`agent-cloud-kit init falló; corre 'npx ${spec} init'.`, run.output);
57
+ return false;
53
58
  },
54
59
  };
55
60
  }
56
61
  /**
57
- * What the repo has of claude-cloud, and the step it needs.
62
+ * What the repo has of agent-cloud-kit, and the step it needs.
58
63
  * @param root - The repo.
59
64
  * @returns The piece's status.
60
65
  * @example (await cloudStatus(root)).state // "1.0.0, al día"
61
66
  */
62
67
  export async function cloudStatus(root) {
63
68
  const config = readJson(root, CONFIG);
64
- const latest = await npmLatest(PACKAGE);
69
+ const latest = await npmLatestExact(PACKAGE);
65
70
  const version = pinned(config);
66
71
  const step = stepFor(config, latest);
67
72
  const known = latest === null ? " (npm no responde)" : "";
@@ -79,10 +84,10 @@ export async function cloudChecks(root) {
79
84
  if (config === null)
80
85
  return [{ name: NAME, status: "info", detail: "no instalado" }];
81
86
  const version = pinned(config);
82
- const latest = await npmLatest(PACKAGE);
87
+ const latest = await npmLatestExact(PACKAGE);
83
88
  if (version === null)
84
89
  return [{ name: NAME, status: "fail", detail: `${CONFIG} no fija una versión exacta: pon en "version" la que se usa, como "${latest ?? "1.0.0"}"` }];
85
- if (latest !== null && compareVersions(latest, version) > 0)
90
+ if (latest !== null && compareSemver(latest, version) > 0)
86
91
  return [{ name: NAME, status: "info", detail: `${version}, hay ${latest}: corre npx agent-kitchen apply` }];
87
92
  return [{ name: NAME, status: "ok", detail: version }];
88
93
  }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The base branch, as issue-flow takes it: `baseBranch` of `.agents/flow.json`, or origin's
3
+ * default as git knows it, or `main`.
4
+ * @param root - The repo.
5
+ * @returns The branch.
6
+ * @example baseBranch(root) // "main"
7
+ */
8
+ export declare function baseBranch(root: string): string;
9
+ /**
10
+ * Origin's URL.
11
+ * @param root - The repo.
12
+ * @returns The URL, or null if the repo has no origin.
13
+ */
14
+ export declare function originUrl(root: string): string | null;
15
+ /**
16
+ * The files that differ from HEAD, tracked or not, one by one, without the repo's worktrees.
17
+ * @param root - The repo.
18
+ * @returns Their paths.
19
+ * @example changedPaths(root) // ["package.json", "package-lock.json"]
20
+ */
21
+ export declare function changedPaths(root: string): string[];
22
+ /**
23
+ * How many commits the current branch is behind its upstream, as the last fetch saw it.
24
+ * @param root - The repo.
25
+ * @returns The count, or 0 without an upstream.
26
+ */
27
+ export declare function behindUpstream(root: string): number;
28
+ /**
29
+ * The branch for today's changes: a new name, also against origin's branches.
30
+ * @param root - The repo, just fetched.
31
+ * @param date - Today.
32
+ * @returns `chore/agent-kitchen-apply-AAAA-MM-DD`, or with `-2`, `-3`… if taken.
33
+ * @example branchName(root, new Date()) // "chore/agent-kitchen-apply-2026-11-01"
34
+ */
35
+ export declare function branchName(root: string, date: Date): string;
36
+ /**
37
+ * Fetches origin and clones the repo at `origin/<base>` into a temporary folder.
38
+ * @param root - The repo.
39
+ * @param base - The base branch.
40
+ * @returns The clone's folder.
41
+ * @throws KitError if the repo has no origin, the fetch fails or origin has no such branch.
42
+ */
43
+ export declare function cloneFromOrigin(root: string, base: string): string;
44
+ /**
45
+ * Deletes the clone.
46
+ * @param clone - Its folder.
47
+ */
48
+ export declare function removeClone(clone: string): void;
49
+ /** What `publish` needs to know. */
50
+ export interface PublishOptions {
51
+ base: string;
52
+ steps: string[];
53
+ paths: string[];
54
+ }
55
+ /**
56
+ * Commits the clone's changes, pushes them from the repo to a new branch of origin and opens
57
+ * the PR. Until the push works, the commit lives only in the clone, which the caller keeps.
58
+ * @param root - The repo.
59
+ * @param clone - The clone, with the changes.
60
+ * @param options - The base, the steps that ran (for the commit and the PR) and the files to commit (the ones shown).
61
+ * @returns The lines to show, whether it all worked and whether the changes reached origin.
62
+ * @example await publish(root, clone, { base: "main", steps, paths })
63
+ */
64
+ export declare function publish(root: string, clone: string, options: PublishOptions): Promise<{
65
+ ok: boolean;
66
+ pushed: boolean;
67
+ lines: string[];
68
+ }>;
@@ -0,0 +1,254 @@
1
+ /**
2
+ * @file What `apply --pr` does with git. It fetches origin and clones the repo into a temporary
3
+ * folder at `origin/<base>`, so the steps see what the base has today and the person's checkout
4
+ * is never touched: not its branch, its files, nor its node_modules. A clone and not a worktree,
5
+ * because `issue-flow apply` refuses to run in a worktree. The clone shares the repo's objects
6
+ * (`--shared`), points its origin where the repo's does, and commits with the repo's hooks,
7
+ * identity and signing. The commit goes from the clone into the repo's objects and the repo
8
+ * pushes it straight to a new branch of origin, with its own credentials, so no local branch
9
+ * is made. Then github-rest opens the PR in draft. git runs its hooks; nothing here skips them.
10
+ */
11
+ import { spawnSync } from "node:child_process";
12
+ import { existsSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
13
+ import { tmpdir } from "node:os";
14
+ import { isAbsolute, join, relative } from "node:path";
15
+ import { COMMIT_TITLE as TITLE, prBody } from "./commit-texts.js";
16
+ import { KitError } from "./output.js";
17
+ import { FLOW_CONFIG } from "./piece.js";
18
+ import { dependencyVersion, readJson } from "./repo.js";
19
+ import { captureNpxWithout, isRelease, npmLatest } from "./tools.js";
20
+ /** github-rest on npm, which opens the PR (REST, never gh's GraphQL). */
21
+ const GITHUB_REST = "@ramon-sg/github-rest";
22
+ /** The settings of the repo a commit made in the clone needs: who signs it and how. */
23
+ const IDENTITY = ["user.name", "user.email", "user.signingkey", "commit.gpgsign", "gpg.format", "gpg.program", "gpg.openpgp.program", "gpg.x509.program", "gpg.ssh.program", "gpg.ssh.allowedsignersfile"];
24
+ /**
25
+ * Runs git in a folder.
26
+ * @param cwd - The folder.
27
+ * @param args - The arguments.
28
+ * @returns How it went.
29
+ */
30
+ function git(cwd, args) {
31
+ const result = spawnSync("git", args, { cwd, encoding: "utf8" });
32
+ return { ok: result.status === 0, out: (result.stdout ?? "").trimEnd(), err: (result.stderr ?? "").trim() };
33
+ }
34
+ /**
35
+ * The base branch, as issue-flow takes it: `baseBranch` of `.agents/flow.json`, or origin's
36
+ * default as git knows it, or `main`.
37
+ * @param root - The repo.
38
+ * @returns The branch.
39
+ * @example baseBranch(root) // "main"
40
+ */
41
+ export function baseBranch(root) {
42
+ const flow = readJson(root, FLOW_CONFIG);
43
+ if (typeof flow?.baseBranch === "string" && flow.baseBranch !== "")
44
+ return flow.baseBranch;
45
+ const head = git(root, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
46
+ return head.ok && head.out.startsWith("origin/") ? head.out.slice("origin/".length) : "main";
47
+ }
48
+ /**
49
+ * Origin's URL.
50
+ * @param root - The repo.
51
+ * @returns The URL, or null if the repo has no origin.
52
+ */
53
+ export function originUrl(root) {
54
+ const url = git(root, ["remote", "get-url", "origin"]);
55
+ return url.ok && url.out !== "" ? url.out : null;
56
+ }
57
+ /**
58
+ * The repo's other worktrees that live inside it, relative to it.
59
+ * @param root - The repo.
60
+ * @returns Their folders, like `.claude/worktrees/feat+1-x`.
61
+ */
62
+ function innerWorktrees(root) {
63
+ const list = git(root, ["worktree", "list", "--porcelain"]);
64
+ if (!list.ok)
65
+ return [];
66
+ return list.out
67
+ .split("\n")
68
+ .filter((line) => line.startsWith("worktree "))
69
+ .map((line) => relative(root, line.slice("worktree ".length)))
70
+ .filter((path) => path !== "" && !path.startsWith(".."));
71
+ }
72
+ /**
73
+ * The files that differ from HEAD, tracked or not, one by one, without the repo's worktrees.
74
+ * @param root - The repo.
75
+ * @returns Their paths.
76
+ * @example changedPaths(root) // ["package.json", "package-lock.json"]
77
+ */
78
+ export function changedPaths(root) {
79
+ const status = git(root, ["status", "--porcelain", "-uall", "-z"]);
80
+ if (!status.ok)
81
+ throw new KitError(`no pude leer git status: ${status.err}`);
82
+ const worktrees = innerWorktrees(root);
83
+ const entries = status.out.split("\0").filter((entry) => entry !== "");
84
+ const paths = [];
85
+ for (let index = 0; index < entries.length; index += 1) {
86
+ const entry = entries[index] ?? "";
87
+ if (/[RC]/.test(entry.slice(0, 2)))
88
+ index += 1;
89
+ const path = entry.slice(3).replace(/\/$/, "");
90
+ if (!worktrees.some((tree) => path === tree || path.startsWith(`${tree}/`)))
91
+ paths.push(path);
92
+ }
93
+ return paths;
94
+ }
95
+ /**
96
+ * How many commits the current branch is behind its upstream, as the last fetch saw it.
97
+ * @param root - The repo.
98
+ * @returns The count, or 0 without an upstream.
99
+ */
100
+ export function behindUpstream(root) {
101
+ const count = git(root, ["rev-list", "--count", "HEAD..@{upstream}"]);
102
+ return count.ok ? Number(count.out) || 0 : 0;
103
+ }
104
+ /**
105
+ * The branch for today's changes: a new name, also against origin's branches.
106
+ * @param root - The repo, just fetched.
107
+ * @param date - Today.
108
+ * @returns `chore/agent-kitchen-apply-AAAA-MM-DD`, or with `-2`, `-3`… if taken.
109
+ * @example branchName(root, new Date()) // "chore/agent-kitchen-apply-2026-11-01"
110
+ */
111
+ export function branchName(root, date) {
112
+ const pad = (n) => String(n).padStart(2, "0");
113
+ const name = `chore/agent-kitchen-apply-${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
114
+ const taken = (candidate) => git(root, ["rev-parse", "-q", "--verify", `refs/heads/${candidate}`]).ok || git(root, ["rev-parse", "-q", "--verify", `refs/remotes/origin/${candidate}`]).ok;
115
+ if (!taken(name))
116
+ return name;
117
+ let n = 2;
118
+ while (taken(`${name}-${n}`))
119
+ n += 1;
120
+ return `${name}-${n}`;
121
+ }
122
+ /**
123
+ * The hooks the clone runs: the repo's `core.hooksPath` as is when it is relative and the
124
+ * folder is in the clone (committed, like `.githooks`), or else the folder the repo uses,
125
+ * absolute (`.git/hooks`, or a generated one like husky's).
126
+ * @param root - The repo.
127
+ * @param clone - The clone.
128
+ * @returns The folder.
129
+ */
130
+ function hooksPath(root, clone) {
131
+ const own = git(root, ["config", "--get", "core.hooksPath"]);
132
+ if (own.ok && own.out !== "" && !isAbsolute(own.out) && existsSync(join(clone, own.out)))
133
+ return own.out;
134
+ return git(root, ["rev-parse", "--path-format=absolute", "--git-path", "hooks"]).out;
135
+ }
136
+ /**
137
+ * Fetches origin and clones the repo at `origin/<base>` into a temporary folder.
138
+ * @param root - The repo.
139
+ * @param base - The base branch.
140
+ * @returns The clone's folder.
141
+ * @throws KitError if the repo has no origin, the fetch fails or origin has no such branch.
142
+ */
143
+ export function cloneFromOrigin(root, base) {
144
+ const url = originUrl(root);
145
+ if (url === null)
146
+ throw new KitError("--pr necesita un remoto origin, y este repo no tiene.");
147
+ const fetched = git(root, ["fetch", "-q", "--prune", "origin"]);
148
+ if (!fetched.ok)
149
+ throw new KitError(`no pude bajar origin con git fetch; revisa la conexión o las credenciales y corre otra vez.${fetched.err === "" ? "" : `\n${fetched.err}`}`);
150
+ const head = git(root, ["rev-parse", "-q", "--verify", `refs/remotes/origin/${base}^{commit}`]);
151
+ if (!head.ok)
152
+ throw new KitError(`origin no tiene la rama ${base}.`);
153
+ const clone = realpathSync(mkdtempSync(join(tmpdir(), "agent-kitchen-pr-")));
154
+ const steps = [
155
+ ["clone", "-q", "--shared", "--no-checkout", root, clone],
156
+ ["-C", clone, "remote", "set-url", "origin", url],
157
+ ["-C", clone, "update-ref", `refs/remotes/origin/${base}`, head.out],
158
+ ["-C", clone, "symbolic-ref", "refs/remotes/origin/HEAD", `refs/remotes/origin/${base}`],
159
+ ["-C", clone, "checkout", "-q", "--detach", head.out],
160
+ ];
161
+ for (const args of steps) {
162
+ const done = git(root, args);
163
+ if (!done.ok) {
164
+ rmSync(clone, { recursive: true, force: true });
165
+ throw new KitError(`no pude preparar la copia temporal: ${done.err}`);
166
+ }
167
+ }
168
+ for (const key of IDENTITY) {
169
+ const value = git(root, ["config", "--get", key]);
170
+ if (value.ok && value.out !== "")
171
+ git(clone, ["config", key, value.out]);
172
+ }
173
+ git(clone, ["config", "core.hooksPath", hooksPath(root, clone)]);
174
+ return clone;
175
+ }
176
+ /**
177
+ * Deletes the clone.
178
+ * @param clone - Its folder.
179
+ */
180
+ export function removeClone(clone) {
181
+ rmSync(clone, { recursive: true, force: true });
182
+ }
183
+ /**
184
+ * The version of github-rest to open the PR with: the repo's exact one, or npm's latest.
185
+ * @param root - The checkout to read package.json from.
186
+ * @returns The version, or null if neither is known.
187
+ */
188
+ async function githubRestVersion(root) {
189
+ const own = dependencyVersion(readJson(root, "package.json"), GITHUB_REST);
190
+ if (own !== null && isRelease(own))
191
+ return own;
192
+ return npmLatest(GITHUB_REST);
193
+ }
194
+ /**
195
+ * Opens the PR in draft with github-rest, without GH_REPO, so the repo is origin's.
196
+ * @param root - The repo.
197
+ * @param clone - The clone, whose package.json says which github-rest the base uses.
198
+ * @param branch - The branch.
199
+ * @param base - The base.
200
+ * @param steps - The steps, for the body.
201
+ * @returns The PR's URL, or why it failed.
202
+ */
203
+ async function openPr(root, clone, branch, base, steps) {
204
+ const version = await githubRestVersion(clone);
205
+ if (version === null)
206
+ return { error: `no sé qué versión de ${GITHUB_REST} usar.` };
207
+ const dir = mkdtempSync(join(tmpdir(), "agent-kitchen-body-"));
208
+ const bodyFile = join(dir, "body.md");
209
+ writeFileSync(bodyFile, prBody(steps));
210
+ try {
211
+ const result = await captureNpxWithout(root, [`${GITHUB_REST}@${version}`, "pr", "create", "--head", branch, "--base", base, "--title", TITLE, "--body-file", bodyFile, "--draft"], ["GH_REPO"]);
212
+ const url = result.out.trim().split("\n").at(-1) ?? "";
213
+ return result.status === 0 && url !== "" ? { url } : { error: `${result.err}${result.out}`.trim() || "github-rest falló sin decir por qué." };
214
+ }
215
+ finally {
216
+ rmSync(dir, { recursive: true, force: true });
217
+ }
218
+ }
219
+ /**
220
+ * What the person can run to open the PR by hand.
221
+ * @param branch - The branch.
222
+ * @param base - The base.
223
+ * @returns The command.
224
+ */
225
+ function prCommand(branch, base) {
226
+ return `npx @ramon-sg/github-rest pr create --head ${branch} --base ${base} --title "${TITLE}" --body "..." --draft`;
227
+ }
228
+ /**
229
+ * Commits the clone's changes, pushes them from the repo to a new branch of origin and opens
230
+ * the PR. Until the push works, the commit lives only in the clone, which the caller keeps.
231
+ * @param root - The repo.
232
+ * @param clone - The clone, with the changes.
233
+ * @param options - The base, the steps that ran (for the commit and the PR) and the files to commit (the ones shown).
234
+ * @returns The lines to show, whether it all worked and whether the changes reached origin.
235
+ * @example await publish(root, clone, { base: "main", steps, paths })
236
+ */
237
+ export async function publish(root, clone, options) {
238
+ const { base, steps, paths } = options;
239
+ const add = git(clone, ["--literal-pathspecs", "add", "-A", "--", ...paths]);
240
+ const done = add.ok ? git(clone, ["commit", "-q", "-m", TITLE, "-m", steps.map((step) => `- ${step}`).join("\n")]) : add;
241
+ if (!done.ok)
242
+ return { ok: false, pushed: false, lines: ["No pude hacer el commit.", done.err || done.out].filter((line) => line !== "") };
243
+ const sha = git(clone, ["rev-parse", "HEAD"]).out;
244
+ const branch = branchName(root, new Date());
245
+ // git asks for credentials on the terminal itself, so its output can stay captured.
246
+ const fetched = git(root, ["fetch", "-q", clone, "HEAD"]);
247
+ const pushed = fetched.ok ? git(root, ["push", "-q", "origin", `${sha}:refs/heads/${branch}`]) : fetched;
248
+ if (!pushed.ok)
249
+ return { ok: false, pushed: false, lines: [`No pude subir la rama ${branch} a origin.`, pushed.err].filter((line) => line !== "") };
250
+ const pr = await openPr(root, clone, branch, base, steps);
251
+ if ("url" in pr)
252
+ return { ok: true, pushed: true, lines: [`PR en draft: ${pr.url}`] };
253
+ return { ok: false, pushed: true, lines: [`La rama ${branch} está en origin, pero no pude abrir el PR: ${pr.error}`, `Falta: ${prCommand(branch, base)}`] };
254
+ }
@@ -19,12 +19,11 @@ export declare function plainProgress(out: Output, options: {
19
19
  now?: () => number;
20
20
  }): Progress;
21
21
  /**
22
- * How progress is shown: a spinner in a terminal, plain lines otherwise or when `plain` asks
23
- * for them. Only for the steps, after every question: a spinner shown before a question
24
- * leaves the second question deaf to the keyboard (@clack/prompts 1.8.1).
22
+ * How progress is shown: a spinner in a terminal, plain lines otherwise. Only for the steps,
23
+ * after every question: a spinner shown before a question leaves the second question deaf to
24
+ * the keyboard (@clack/prompts 1.8.1).
25
25
  * @param out - Where to write.
26
26
  * @param verbose - Whether the tools print on the terminal.
27
- * @param plain - Whether a question may come after the steps (`--commit`).
28
27
  * @returns The progress.
29
28
  */
30
- export declare function progressFor(out: Output, verbose: boolean, plain: boolean): Progress;
29
+ export declare function progressFor(out: Output, verbose: boolean): Progress;
package/dist/progress.js CHANGED
@@ -46,16 +46,15 @@ export function plainProgress(out, options) {
46
46
  };
47
47
  }
48
48
  /**
49
- * How progress is shown: a spinner in a terminal, plain lines otherwise or when `plain` asks
50
- * for them. Only for the steps, after every question: a spinner shown before a question
51
- * leaves the second question deaf to the keyboard (@clack/prompts 1.8.1).
49
+ * How progress is shown: a spinner in a terminal, plain lines otherwise. Only for the steps,
50
+ * after every question: a spinner shown before a question leaves the second question deaf to
51
+ * the keyboard (@clack/prompts 1.8.1).
52
52
  * @param out - Where to write.
53
53
  * @param verbose - Whether the tools print on the terminal.
54
- * @param plain - Whether a question may come after the steps (`--commit`).
55
54
  * @returns The progress.
56
55
  */
57
- export function progressFor(out, verbose, plain) {
58
- if (out.interactive && !verbose && !plain) {
56
+ export function progressFor(out, verbose) {
57
+ if (out.interactive && !verbose) {
59
58
  const spin = spinner();
60
59
  let started = 0;
61
60
  return {
@@ -37,8 +37,10 @@ export interface Catalog {
37
37
  suggested: (dryRun: boolean) => string;
38
38
  /** The last line when every step worked or some failed. */
39
39
  summary: (failed: string[]) => string;
40
- /** What is left to do by hand after the steps, without --commit; with the manager if its install failed. */
40
+ /** What is left to do by hand after the steps, in the checkout; with the manager if its install failed. */
41
41
  next: (failedInstall: string | null) => string;
42
+ /** The step after apply when agent-cloud-kit is installed. */
43
+ cloudNext: string;
42
44
  /** A new piece left out because no one was asked about it. */
43
45
  undecided: (names: string[]) => string;
44
46
  /** The repo's own skills left out of a pick. */
@@ -47,4 +49,12 @@ export interface Catalog {
47
49
  saved: (file: string, commits: boolean) => string;
48
50
  /** The closing line when no step ran: the person left, or picked none. */
49
51
  noSteps: (why: "leave" | "none", wrote: boolean) => string;
52
+ /** With --pr, before the clone: from which branch of origin. */
53
+ cloning: (base: string) => string;
54
+ /** With --pr, the heading of the files that go in the PR. */
55
+ changes: string;
56
+ /** With --pr, nothing changed. */
57
+ noChanges: string;
58
+ /** With --pr, why no PR was opened and where the changes stay. */
59
+ prNotOpened: (why: "step" | "install" | "git", clone: string) => string;
50
60
  }
package/dist/texts/es.js CHANGED
@@ -53,14 +53,14 @@ export const es = {
53
53
  done: `github-rest subido de ${from} a ${to}, el mínimo de issue-flow ${flow}`,
54
54
  }),
55
55
  "cloud-install": ({ version }) => ({
56
- menu: `Instalar claude-cloud ${version ?? "1.x"}: prepara las sesiones en la nube de Claude`,
57
- doing: `Instalando claude-cloud ${version ?? "1.x"}…`,
58
- done: `claude-cloud ${version ?? "1.x"} instalado: prepara las sesiones en la nube de Claude`,
56
+ menu: `Instalar agent-cloud-kit ${version ?? "1.x"}: prepara las sesiones en la nube de Claude`,
57
+ doing: `Instalando agent-cloud-kit ${version ?? "1.x"}…`,
58
+ done: `agent-cloud-kit ${version ?? "1.x"} instalado: prepara las sesiones en la nube de Claude`,
59
59
  }),
60
60
  "cloud-update": ({ from, to, config }) => ({
61
- menu: `Actualizar claude-cloud ${from ?? "?"} → ${to} (la versión que fija ${config})`,
62
- doing: `Actualizando claude-cloud de ${from ?? "?"} a ${to}…`,
63
- done: `claude-cloud actualizado de ${from ?? "?"} a ${to} en ${config}`,
61
+ menu: `Actualizar agent-cloud-kit ${from ?? "?"} → ${to} (la versión que fija ${config})`,
62
+ doing: `Actualizando agent-cloud-kit de ${from ?? "?"} a ${to}…`,
63
+ done: `agent-cloud-kit actualizado de ${from ?? "?"} a ${to} en ${config}`,
64
64
  }),
65
65
  "guard-install": ({ version }) => ({
66
66
  menu: `Instalar claude-repo-guard ${version ?? "(main)"}: modo auto con sandbox, y un documento de qué protege`,
@@ -128,6 +128,7 @@ export const es = {
128
128
  suggested: (dryRun) => `Pasos sugeridos${dryRun ? "" : " (para hacerlos, corre agent-kitchen apply en una terminal, o con --yes)"}:`,
129
129
  summary: (failed) => (failed.length === 0 ? "Listo." : `Terminó, pero fallaron: ${failed.join("; ")}.`),
130
130
  next: (failedInstall) => `Falta: ${failedInstall === null ? "" : `corre '${failedInstall} install', `}revisa el diff y haz commit.`,
131
+ cloudNext: "Siguiente paso para la nube: npx agent-cloud-kit prepare",
131
132
  undecided: (names) => `Pieza nueva sin decisión: ${list(names)}. Corre agent-kitchen apply en una terminal para elegir.`,
132
133
  ownSkillsKept: (names) => `Se dejan fuera las skills que el repo ya tiene como propias: ${list(names)}. Para reemplazarlas, corre agent-kitchen apply en una terminal.`,
133
134
  saved: (file, commits) => `Guardé qué piezas usa el repo en ${file}${commits ? "" : "; commitéalo"}.`,
@@ -136,4 +137,11 @@ export const es = {
136
137
  return wrote ? "No corrí ningún paso; solo guardé qué piezas usa el repo." : "No se cambió nada.";
137
138
  return wrote ? "No elegiste pasos; solo guardé qué piezas usa el repo." : "No elegiste nada; no se cambió nada.";
138
139
  },
140
+ cloning: (base) => `Trabajo en una copia temporal de origin/${base}; tu copia no se toca.`,
141
+ changes: "Cambios del PR:",
142
+ noChanges: "No cambió ningún archivo: no hay PR que abrir.",
143
+ prNotOpened: (why, clone) => {
144
+ const reason = { step: "No abrí el PR porque algún paso falló.", install: "No abrí el PR porque falló el install.", git: "" }[why];
145
+ return `${reason === "" ? "" : `${reason} `}Los cambios quedan en la copia temporal ${clone}; bórrala con rm -rf cuando termines.`;
146
+ },
139
147
  };
package/dist/tools.d.ts CHANGED
@@ -19,13 +19,35 @@ export declare function compareVersions(left: string, right: string): number;
19
19
  * @example isRelease("1.2.3") // true
20
20
  */
21
21
  export declare function isRelease(version: string): boolean;
22
+ /**
23
+ * Whether a version is exact semver: a release like 1.2.3, or one with a prerelease like 1.2.3-rc.0.
24
+ * @param version - The text.
25
+ * @returns True for an exact version; ranges, tags and build metadata are not.
26
+ * @example isExactVersion("1.0.0-rc.0") // true
27
+ */
28
+ export declare function isExactVersion(version: string): boolean;
29
+ /**
30
+ * Compares two exact versions by semver precedence, prereleases included.
31
+ * @param left - A version like 1.0.0-rc.0.
32
+ * @param right - Another.
33
+ * @returns Negative if left is older, 0 if equal, positive if newer.
34
+ * @example compareSemver("1.0.0-rc.1", "1.0.0") // -1
35
+ */
36
+ export declare function compareSemver(left: string, right: string): number;
22
37
  /**
23
38
  * The latest version of a package on npm.
24
39
  * @param name - The package.
25
40
  * @returns The version, or null if npm cannot tell.
26
- * @example await npmLatest("@ramon-sg/claude-cloud") // "1.0.0"
41
+ * @example await npmLatest("agent-cloud-kit") // "1.0.0"
27
42
  */
28
43
  export declare function npmLatest(name: string): Promise<string | null>;
44
+ /**
45
+ * The `latest` version of a package on npm, even if it is a prerelease like 1.0.0-rc.0.
46
+ * @param name - The package.
47
+ * @returns The version, or null if npm cannot tell or it is not exact semver.
48
+ * @example await npmLatestExact("agent-cloud-kit") // "1.0.0-rc.0"
49
+ */
50
+ export declare function npmLatestExact(name: string): Promise<string | null>;
29
51
  /**
30
52
  * The dependencies a version of a package declares on npm.
31
53
  * @param spec - The package and its version, as `name@x.y.z`.
@@ -46,7 +68,7 @@ export declare function latestTag(repo: string): Promise<string | null>;
46
68
  * @param root - The repo.
47
69
  * @param spec - The package, as npx takes it.
48
70
  * @returns When it is done.
49
- * @example void download(root, "@ramon-sg/claude-cloud@1.0.0")
71
+ * @example void download(root, "agent-cloud-kit@1.0.0")
50
72
  */
51
73
  export declare function download(root: string, spec: string): Promise<void>;
52
74
  /** How a tool ended, and what it printed if it was kept. */
@@ -60,7 +82,7 @@ export interface ToolRun {
60
82
  * @param root - The repo.
61
83
  * @param args - The package and its arguments.
62
84
  * @returns Whether it exited 0, and what it printed.
63
- * @example await runNpx(root, ["@ramon-sg/claude-cloud@1.0.0", "init"])
85
+ * @example await runNpx(root, ["agent-cloud-kit@1.0.0", "init"])
64
86
  */
65
87
  export declare function runNpx(root: string, args: string[]): Promise<ToolRun>;
66
88
  /**