agent-kitchen 0.9.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` | — |
@@ -51,7 +51,7 @@ npx agent-kitchen apply --yes # hace lo sugerido sin preguntar
51
51
  npx agent-kitchen apply --pr # en una copia de origin, y abre un PR en draft
52
52
  npx agent-kitchen apply --here # en tu copia, sin commit
53
53
  npx agent-kitchen apply --verbose # muestra la salida de cada herramienta
54
- 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)
55
55
  npx agent-kitchen apply --pieces # cambia qué piezas usa el repo (solo en una terminal)
56
56
  npx agent-kitchen doctor # revisa cada pieza con su propio doctor
57
57
  ```
package/dist/cli.js CHANGED
@@ -14,7 +14,7 @@ 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. */
@@ -52,7 +52,7 @@ function ownVersion() {
52
52
  */
53
53
  function startLookups() {
54
54
  void npmLatest(FLOW_PACKAGE);
55
- void npmLatest(CLOUD_PACKAGE);
55
+ void npmLatestExact(CLOUD_PACKAGE);
56
56
  void latestTag(GUARD_REPO);
57
57
  for (const catalog of GENERAL_CATALOGS)
58
58
  void latestTag(catalog);
@@ -17,7 +17,7 @@ export interface SyncOptions {
17
17
  /**
18
18
  * The names `--skip` takes.
19
19
  * @returns The pieces' names.
20
- * @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"]
21
21
  */
22
22
  export declare function pieceNames(): string[];
23
23
  /**
@@ -5,7 +5,7 @@
5
5
  * draft (see pr-clone.ts), `--here` in the person's checkout, leaving the changes uncommitted.
6
6
  * In a terminal, without either, it asks; with `--yes` or without a terminal, or without an
7
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, claude-cloud,
8
+ * Then it looks at each piece (the version of skills-manager, the issue flow, agent-cloud-kit,
9
9
  * claude-repo-guard, the skills, the rule in AGENTS.md) with
10
10
  * every lookup at once, shows what the repo has, and offers the steps that install what is
11
11
  * missing or update what is behind. In a terminal it asks first, all in one go: everything
@@ -37,7 +37,7 @@ import { issueFlowStatus, NAME as FLOW } from "../pieces/issue-flow.js";
37
37
  import { keepOwn, NAME as SKILLS, ownInPick, skillsStatus } from "../pieces/skills.js";
38
38
  import { agentsMdStatus, NAME as AGENTS_MD } from "../pieces/agents-md.js";
39
39
  import { NAME as SKILLS_MANAGER, skillsManagerStatus } from "../pieces/skills-manager.js";
40
- import { mainCheckout, packageManager, packageText } from "../repo.js";
40
+ import { mainCheckout, packageManager, packageText, readJson } from "../repo.js";
41
41
  import { download, failedOutput, runInstall, setVerbose } from "../tools.js";
42
42
  import { commitLine, DECISIONS_LINE } from "../commit-texts.js";
43
43
  import { progressFor } from "../progress.js";
@@ -47,7 +47,7 @@ const PIECES = [SKILLS_MANAGER, FLOW, CLOUD, GUARD, SKILLS, AGENTS_MD];
47
47
  /**
48
48
  * The names `--skip` takes.
49
49
  * @returns The pieces' names.
50
- * @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"]
51
51
  */
52
52
  export function pieceNames() {
53
53
  return [...PIECES];
@@ -405,7 +405,13 @@ async function applyIn(work, options, out) {
405
405
  const steps = statuses.flatMap((status) => status.steps);
406
406
  const progress = progressFor(out, options.verbose);
407
407
  // In a clone, a run that only wrote the decisions file still ends in a PR.
408
- const endWithoutSteps = async () => (work.kind === "pr" && wrote ? openPr(work, commitLabels([], true), [], progress, out) : 0);
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
+ };
409
415
  if (steps.length === 0) {
410
416
  if (saves)
411
417
  wrote = saveDecisions(root, saved, statuses, [], pr, out) || wrote;
@@ -446,8 +452,20 @@ async function applyIn(work, options, out) {
446
452
  if (installFailed)
447
453
  failed.push(phrases({ kind: "install-deps", manager: packageManager(root) }).menu);
448
454
  out.stdout(`\n${ui.summary(failed)} ${ui.next(installFailed ? packageManager(root) : null)}\n`);
455
+ if (!options.dryRun)
456
+ printCloudNext(root, skip, out);
449
457
  return failed.length === 0 ? 0 : 1;
450
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
+ }
451
469
  /**
452
470
  * Runs the repo's install with the same progress as a step, and shows its output if it fails.
453
471
  * @param root - The repo.
@@ -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/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
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
  }
@@ -39,6 +39,8 @@ export interface Catalog {
39
39
  summary: (failed: string[]) => string;
40
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. */
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"}.`,
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
  /**
package/dist/tools.js CHANGED
@@ -12,6 +12,8 @@
12
12
  import { spawn } from "node:child_process";
13
13
  /** A release version. */
14
14
  const VERSION = /^(\d+)\.(\d+)\.(\d+)$/;
15
+ /** An exact semver version: release plus an optional prerelease, no build metadata. */
16
+ const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?$/;
15
17
  /** How long a lookup of the latest version may take before it counts as unknown. */
16
18
  const LOOKUP_TIMEOUT_MS = 20_000;
17
19
  /** A release tag in `git ls-remote --tags --refs` output. */
@@ -58,6 +60,50 @@ export function compareVersions(left, right) {
58
60
  export function isRelease(version) {
59
61
  return VERSION.test(version);
60
62
  }
63
+ /**
64
+ * Whether a version is exact semver: a release like 1.2.3, or one with a prerelease like 1.2.3-rc.0.
65
+ * @param version - The text.
66
+ * @returns True for an exact version; ranges, tags and build metadata are not.
67
+ * @example isExactVersion("1.0.0-rc.0") // true
68
+ */
69
+ export function isExactVersion(version) {
70
+ return SEMVER.test(version);
71
+ }
72
+ /**
73
+ * Compares two exact versions by semver precedence, prereleases included.
74
+ * @param left - A version like 1.0.0-rc.0.
75
+ * @param right - Another.
76
+ * @returns Negative if left is older, 0 if equal, positive if newer.
77
+ * @example compareSemver("1.0.0-rc.1", "1.0.0") // -1
78
+ */
79
+ export function compareSemver(left, right) {
80
+ const [a, b] = [SEMVER.exec(left), SEMVER.exec(right)];
81
+ for (let index = 1; index <= 3; index += 1) {
82
+ const diff = Number(a?.[index] ?? 0) - Number(b?.[index] ?? 0);
83
+ if (diff !== 0)
84
+ return diff;
85
+ }
86
+ const [pre, other] = [a?.[4], b?.[4]];
87
+ if (pre === other)
88
+ return 0;
89
+ if (pre === undefined)
90
+ return 1;
91
+ if (other === undefined)
92
+ return -1;
93
+ const [x, y] = [pre.split("."), other.split(".")];
94
+ for (let index = 0; index < Math.min(x.length, y.length); index += 1) {
95
+ const [p, q] = [x[index], y[index]];
96
+ if (p === q)
97
+ continue;
98
+ const [pNumber, qNumber] = [/^\d+$/.test(p), /^\d+$/.test(q)];
99
+ if (pNumber && qNumber)
100
+ return Number(p) - Number(q);
101
+ if (pNumber !== qNumber)
102
+ return pNumber ? -1 : 1;
103
+ return p < q ? -1 : 1;
104
+ }
105
+ return x.length - y.length;
106
+ }
61
107
  /**
62
108
  * Runs a program and collects what it prints.
63
109
  * @param program - The program.
@@ -102,15 +148,35 @@ function once(key, lookup) {
102
148
  * The latest version of a package on npm.
103
149
  * @param name - The package.
104
150
  * @returns The version, or null if npm cannot tell.
105
- * @example await npmLatest("@ramon-sg/claude-cloud") // "1.0.0"
151
+ * @example await npmLatest("agent-cloud-kit") // "1.0.0"
106
152
  */
107
153
  export function npmLatest(name) {
108
154
  return once(`npm:${name}`, async () => {
109
- const result = await run("npm", ["view", name, "version", "--fetch-retries=0"], { timeout: LOOKUP_TIMEOUT_MS });
110
- const version = result.status === 0 ? result.out.trim() : "";
155
+ const version = await npmVersion(name);
111
156
  return isRelease(version) ? version : null;
112
157
  });
113
158
  }
159
+ /**
160
+ * The `latest` version of a package on npm, even if it is a prerelease like 1.0.0-rc.0.
161
+ * @param name - The package.
162
+ * @returns The version, or null if npm cannot tell or it is not exact semver.
163
+ * @example await npmLatestExact("agent-cloud-kit") // "1.0.0-rc.0"
164
+ */
165
+ export function npmLatestExact(name) {
166
+ return once(`npm-exact:${name}`, async () => {
167
+ const version = await npmVersion(name);
168
+ return isExactVersion(version) ? version : null;
169
+ });
170
+ }
171
+ /**
172
+ * What `npm view <name> version` prints.
173
+ * @param name - The package.
174
+ * @returns The text, or "" if npm fails.
175
+ */
176
+ async function npmVersion(name) {
177
+ const result = await run("npm", ["view", name, "version", "--fetch-retries=0"], { timeout: LOOKUP_TIMEOUT_MS });
178
+ return result.status === 0 ? result.out.trim() : "";
179
+ }
114
180
  /**
115
181
  * The dependencies a version of a package declares on npm.
116
182
  * @param spec - The package and its version, as `name@x.y.z`.
@@ -152,7 +218,7 @@ export function latestTag(repo) {
152
218
  * @param root - The repo.
153
219
  * @param spec - The package, as npx takes it.
154
220
  * @returns When it is done.
155
- * @example void download(root, "@ramon-sg/claude-cloud@1.0.0")
221
+ * @example void download(root, "agent-cloud-kit@1.0.0")
156
222
  */
157
223
  export function download(root, spec) {
158
224
  const known = downloads.get(spec);
@@ -168,7 +234,7 @@ export function download(root, spec) {
168
234
  * @param root - The repo.
169
235
  * @param args - The package and its arguments.
170
236
  * @returns Whether it exited 0, and what it printed.
171
- * @example await runNpx(root, ["@ramon-sg/claude-cloud@1.0.0", "init"])
237
+ * @example await runNpx(root, ["agent-cloud-kit@1.0.0", "init"])
172
238
  */
173
239
  export async function runNpx(root, args) {
174
240
  const result = await run("npx", ["--yes", ...args], { cwd: root, inherit: verbose });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-kitchen",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "One command to install, update and check a repo's agent tooling: the issue flow and its skills, the general skills, Claude cloud sessions, claude-repo-guard and the installed-versions rule in AGENTS.md.",
5
5
  "license": "MIT",
6
6
  "type": "module",