agent-cloud-kit 1.0.0-rc.1 → 1.0.0-rc.3
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 +21 -10
- package/dist/cli.js +87 -36
- package/dist/config.js +11 -11
- package/dist/docker.js +1 -1
- package/dist/doctor.js +17 -17
- package/dist/init.js +10 -10
- package/dist/installers.js +6 -6
- package/dist/launcher.js +1 -1
- package/dist/manual.js +2 -2
- package/dist/prepare/banner.js +130 -0
- package/dist/prepare/detect.js +10 -11
- package/dist/prepare/index.js +208 -44
- package/dist/run.js +2 -2
- package/dist/select.js +112 -0
- package/dist/session.js +10 -10
- package/dist/setup.js +6 -6
- package/dist/skill.js +6 -6
- package/package.json +1 -1
- package/skills/cloud-ready/SKILL.md +5 -4
package/dist/installers.js
CHANGED
|
@@ -123,7 +123,7 @@ async function nodeExpectedHash(run, env, dir, version, file) {
|
|
|
123
123
|
await run("curl", ["-fsSL", "-o", path, "--", url], { env, timeoutMs: TOOL_INSTALL_TIMEOUT_MS });
|
|
124
124
|
const hash = parseShasums(readFileSync(path, "utf8"), file);
|
|
125
125
|
if (!hash)
|
|
126
|
-
throw new Error(`${file}
|
|
126
|
+
throw new Error(`${file} no aparece en ${NODE_SHASUMS_FILE}`);
|
|
127
127
|
return hash;
|
|
128
128
|
}
|
|
129
129
|
/**
|
|
@@ -138,7 +138,7 @@ async function nodeExpectedHash(run, env, dir, version, file) {
|
|
|
138
138
|
export function nodeArch(arch) {
|
|
139
139
|
const mapped = NODE_ARCHS[arch];
|
|
140
140
|
if (!mapped)
|
|
141
|
-
throw new Error(`
|
|
141
|
+
throw new Error(`arquitectura no soportada para node: ${arch}`);
|
|
142
142
|
return mapped;
|
|
143
143
|
}
|
|
144
144
|
/**
|
|
@@ -153,7 +153,7 @@ export function nodeArch(arch) {
|
|
|
153
153
|
*/
|
|
154
154
|
export function assertVersion(tool, version) {
|
|
155
155
|
if (!VERSION_PATTERN.test(version))
|
|
156
|
-
throw new Error(`
|
|
156
|
+
throw new Error(`versión inválida para ${tool}: ${version}`);
|
|
157
157
|
}
|
|
158
158
|
/**
|
|
159
159
|
* Tells whether a version is safe to use.
|
|
@@ -254,7 +254,7 @@ export function curlNodeIndex(run, env) {
|
|
|
254
254
|
await run("curl", ["-fsSL", "--max-time", NODE_INDEX_TIMEOUT_SECONDS, "-o", file, "--", NODE_INDEX_URL], { env });
|
|
255
255
|
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
256
256
|
if (!Array.isArray(parsed))
|
|
257
|
-
throw new Error("
|
|
257
|
+
throw new Error("el índice de versiones de node no es una lista");
|
|
258
258
|
return parsed.filter((r) => typeof r === "object" && r !== null && typeof r.version === "string" && (r.lts === false || typeof r.lts === "string"));
|
|
259
259
|
}
|
|
260
260
|
finally {
|
|
@@ -283,7 +283,7 @@ export async function resolveVersions(versions, fetchIndex) {
|
|
|
283
283
|
const result = await step(`node ${requested}`, async () => {
|
|
284
284
|
exact = resolveNodeVersion(requested, await fetchIndex());
|
|
285
285
|
if (!exact)
|
|
286
|
-
throw new Error(`
|
|
286
|
+
throw new Error(`ninguna versión de node coincide con ${requested}`);
|
|
287
287
|
});
|
|
288
288
|
if (!exact)
|
|
289
289
|
return { resolved, results: [result] };
|
|
@@ -403,7 +403,7 @@ export async function installTools(versions, toolsDir, run, env) {
|
|
|
403
403
|
}
|
|
404
404
|
if (tool === "node" && !NODE_EXACT.test(version)) {
|
|
405
405
|
results.push(await step(`${tool} ${version}`, async () => {
|
|
406
|
-
throw new Error(`node ${version}
|
|
406
|
+
throw new Error(`node ${version} debe resolverse primero a una versión exacta`);
|
|
407
407
|
}));
|
|
408
408
|
continue;
|
|
409
409
|
}
|
package/dist/launcher.js
CHANGED
|
@@ -58,7 +58,7 @@ export function planSetup(repos, current, read) {
|
|
|
58
58
|
return repos.map((repo) => {
|
|
59
59
|
const { config, errors } = read(repo);
|
|
60
60
|
if (!config)
|
|
61
|
-
return { repo, action: "skip", reason: errors.length ? errors.join("; ") : `no ${CONFIG_FILE}` };
|
|
61
|
+
return { repo, action: "skip", reason: errors.length ? errors.join("; ") : `no hay ${CONFIG_FILE}` };
|
|
62
62
|
if (config.version === current)
|
|
63
63
|
return { repo, action: "run", version: current };
|
|
64
64
|
return { repo, action: "relaunch", version: config.version };
|
package/dist/manual.js
CHANGED
|
@@ -37,7 +37,7 @@ const HOW_IT_WORKS = [
|
|
|
37
37
|
"4. Una sesión en la nube verifica que el entorno quedó bien.",
|
|
38
38
|
"5. El PR guarda el resultado.",
|
|
39
39
|
"",
|
|
40
|
-
"Este documento se va actualizando. Cuando la skill `cloud-ready`, en local o en la nube, detecta que hace falta un dominio o una variable de entorno nueva, la agrega a `.claude/cloud.json`, regenera este archivo y te lo dice en la sesión. Cuando cambie la sección \"Configurar el entorno en claude.ai\", aplica lo nuevo en claude.ai y
|
|
40
|
+
"Este documento se va actualizando. Cuando la skill `cloud-ready`, en local o en la nube, detecta que hace falta un dominio o una variable de entorno nueva, la agrega a `.claude/cloud.json`, regenera este archivo y te lo dice en la sesión. Cuando cambie la sección \"Configurar el entorno en claude.ai\", aplica lo nuevo en claude.ai y vuelve a verificar con `npx agent-cloud-kit verify` desde la rama.",
|
|
41
41
|
].join("\n");
|
|
42
42
|
/**
|
|
43
43
|
* Extracts the host of an apt repo URL.
|
|
@@ -205,7 +205,7 @@ export function mergeManual(existing, block) {
|
|
|
205
205
|
export function writeManual(repo, packageVersion) {
|
|
206
206
|
const { config, errors } = readConfig(repo);
|
|
207
207
|
if (!config)
|
|
208
|
-
throw new Error(errors[0] ??
|
|
208
|
+
throw new Error(errors[0] ?? `no existe ${CONFIG_FILE}; corre init primero`);
|
|
209
209
|
const file = join(repo, MANUAL_FILE);
|
|
210
210
|
const existing = existsSync(file) ? readFileSync(file, "utf8") : undefined;
|
|
211
211
|
const block = renderManual({ config, packageVersion, notes: resolveNotes(repo, config.notes) });
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The screen `prepare` shows before opening Claude Code: a small cloud, what will happen and
|
|
3
|
+
* what the user has to do, in a few colored lines.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
/** Script of claude-repo-guard that opens Claude without sandbox nor permission prompts. */
|
|
8
|
+
export const FREE_SCRIPT = "claude:free";
|
|
9
|
+
/**
|
|
10
|
+
* Wraps a text in an ANSI code when color is on.
|
|
11
|
+
*
|
|
12
|
+
* @param on - Whether color is on.
|
|
13
|
+
* @param code - SGR code, like `36` for cyan.
|
|
14
|
+
* @param text - Text.
|
|
15
|
+
* @returns The text, colored or not.
|
|
16
|
+
* @example
|
|
17
|
+
* paint(false, "36", "a"); // "a"
|
|
18
|
+
*/
|
|
19
|
+
function paint(on, code, text) {
|
|
20
|
+
return on ? `\x1b[${code}m${text}\x1b[0m` : text;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Renders the screen shown before opening Claude Code (without the Enter line, which the caller
|
|
24
|
+
* asks).
|
|
25
|
+
*
|
|
26
|
+
* @param input - Place, mode and color.
|
|
27
|
+
* @returns The lines.
|
|
28
|
+
* @example
|
|
29
|
+
* renderBanner({ place: "pr", free: false, color: false })[2]; // " .-( ). Ahora se abre Claude Code"
|
|
30
|
+
*/
|
|
31
|
+
export function renderBanner(input) {
|
|
32
|
+
const c = (code, text) => paint(input.color, code, text);
|
|
33
|
+
const cloud = (text) => c("36", text);
|
|
34
|
+
const step = (n, text) => ` ${c("1;35", String(n))} ${text}`;
|
|
35
|
+
const after = input.place === "pr" ? "prepare hace commit, push y PR" : "quedan los cambios sin commit";
|
|
36
|
+
const lines = [
|
|
37
|
+
"",
|
|
38
|
+
cloud(" .--."),
|
|
39
|
+
`${cloud(" .-( ).")} ${c("1", "Ahora se abre Claude Code")}`,
|
|
40
|
+
`${cloud(" (___.__)__)")} ${c("2", "con la skill cloud-ready")}`,
|
|
41
|
+
"",
|
|
42
|
+
step(1, "Claude revisa el repo y te propone cómo prepararlo para la nube"),
|
|
43
|
+
step(2, "Responde sus preguntas"),
|
|
44
|
+
step(3, `Cuando diga ${c("1", "configure terminó")}, sal con ${c("1;33", "/exit")} ${c("2", `→ ${after}`)}`),
|
|
45
|
+
];
|
|
46
|
+
if (input.free)
|
|
47
|
+
lines.push("", c("2", ` Se abre con ${FREE_SCRIPT} (sin sandbox ni preguntas de permisos): el repo usa claude-repo-guard`));
|
|
48
|
+
lines.push("");
|
|
49
|
+
return lines;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The line asked after the banner.
|
|
53
|
+
*
|
|
54
|
+
* @param color - Whether to use ANSI colors.
|
|
55
|
+
* @returns The text.
|
|
56
|
+
* @example
|
|
57
|
+
* enterLine(false); // " ⏎ Enter abre Claude Code · Ctrl+C cancela "
|
|
58
|
+
*/
|
|
59
|
+
export function enterLine(color) {
|
|
60
|
+
return ` ${paint(color, "1;32", "⏎ Enter")} abre Claude Code ${paint(color, "2", "· Ctrl+C cancela")} `;
|
|
61
|
+
}
|
|
62
|
+
/** Config file claude-repo-guard installs; the free mode is used only when it is there. */
|
|
63
|
+
const GUARD_CONFIG = ".claude/repo-guard.config.json";
|
|
64
|
+
/**
|
|
65
|
+
* Tells whether the repo uses claude-repo-guard and has its `claude:free` script. Both are
|
|
66
|
+
* required: the free mode drops the sandbox and the permission prompts, so a script with that
|
|
67
|
+
* name alone is not enough.
|
|
68
|
+
*
|
|
69
|
+
* @param repo - Repo root.
|
|
70
|
+
* @returns True when the guard config exists and `package.json` has the script.
|
|
71
|
+
* @example
|
|
72
|
+
* hasFreeScript("/home/u/app"); // true
|
|
73
|
+
*/
|
|
74
|
+
export function hasFreeScript(repo) {
|
|
75
|
+
const file = join(repo, "package.json");
|
|
76
|
+
if (!existsSync(file) || !existsSync(join(repo, GUARD_CONFIG)))
|
|
77
|
+
return false;
|
|
78
|
+
try {
|
|
79
|
+
const scripts = JSON.parse(readFileSync(file, "utf8")).scripts;
|
|
80
|
+
return typeof scripts?.[FREE_SCRIPT] === "string";
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The command that opens Claude with a prompt: the repo's `claude:free` script through its package
|
|
88
|
+
* manager, or `claude` itself.
|
|
89
|
+
*
|
|
90
|
+
* @param repo - Repo root.
|
|
91
|
+
* @param prompt - First prompt.
|
|
92
|
+
* @returns Command and arguments.
|
|
93
|
+
* @example
|
|
94
|
+
* claudeCommand("/home/u/app", "/cloud-ready configure"); // { cmd: "npm", args: ["run", "claude:free", "--", "/cloud-ready configure"] }
|
|
95
|
+
*/
|
|
96
|
+
export function claudeCommand(repo, prompt) {
|
|
97
|
+
if (!hasFreeScript(repo))
|
|
98
|
+
return { cmd: "claude", args: [prompt] };
|
|
99
|
+
const manager = packageManager(repo);
|
|
100
|
+
if (manager === "npm")
|
|
101
|
+
return { cmd: "npm", args: ["run", FREE_SCRIPT, "--", prompt] };
|
|
102
|
+
return { cmd: manager, args: ["run", FREE_SCRIPT, prompt] };
|
|
103
|
+
}
|
|
104
|
+
/** Lockfiles that tell the package manager, checked in order. */
|
|
105
|
+
const LOCKFILES = [
|
|
106
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
107
|
+
["bun.lock", "bun"],
|
|
108
|
+
["bun.lockb", "bun"],
|
|
109
|
+
["yarn.lock", "yarn"],
|
|
110
|
+
];
|
|
111
|
+
/**
|
|
112
|
+
* The repo's package manager: `packageManager` in `package.json`, else the lockfile, else npm.
|
|
113
|
+
*
|
|
114
|
+
* @param repo - Repo root.
|
|
115
|
+
* @returns `npm`, `pnpm`, `yarn` or `bun`.
|
|
116
|
+
* @example
|
|
117
|
+
* packageManager("/home/u/app"); // "pnpm"
|
|
118
|
+
*/
|
|
119
|
+
function packageManager(repo) {
|
|
120
|
+
try {
|
|
121
|
+
const field = JSON.parse(readFileSync(join(repo, "package.json"), "utf8")).packageManager;
|
|
122
|
+
const name = typeof field === "string" ? field.split("@")[0] : undefined;
|
|
123
|
+
if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun")
|
|
124
|
+
return name;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// NOTE: no readable package.json; fall back to the lockfile.
|
|
128
|
+
}
|
|
129
|
+
return LOCKFILES.find(([file]) => existsSync(join(repo, file)))?.[1] ?? "npm";
|
|
130
|
+
}
|
package/dist/prepare/detect.js
CHANGED
|
@@ -4,26 +4,25 @@
|
|
|
4
4
|
/** Flag `claude` must advertise in its help to launch cloud sessions. */
|
|
5
5
|
const CLOUD_FLAG = "--cloud";
|
|
6
6
|
/**
|
|
7
|
-
* Checks that `prepare` can run.
|
|
7
|
+
* Checks that `prepare` (or `verify`) can run.
|
|
8
8
|
*
|
|
9
9
|
* @param probe What was observed about the environment.
|
|
10
|
-
* @
|
|
10
|
+
* @param command Command named in the messages.
|
|
11
|
+
* @returns Problems in Spanish, in a fixed order; empty when everything is fine.
|
|
11
12
|
* @example
|
|
12
|
-
* preflight({ isTty: false, hasOrigin: true
|
|
13
|
+
* preflight({ isTty: false, hasOrigin: true }); // ["prepare necesita una terminal interactiva", ...]
|
|
13
14
|
*/
|
|
14
|
-
export function preflight(probe) {
|
|
15
|
+
export function preflight(probe, command = "prepare") {
|
|
15
16
|
const problems = [];
|
|
16
17
|
if (!probe.isTty)
|
|
17
|
-
problems.push(
|
|
18
|
+
problems.push(`${command} necesita una terminal interactiva (corre el comando directamente en tu terminal)`);
|
|
18
19
|
if (probe.gitRoot === undefined)
|
|
19
|
-
problems.push(
|
|
20
|
+
problems.push(`esto no es un repositorio git; corre ${command} dentro del repo`);
|
|
20
21
|
if (!probe.hasOrigin)
|
|
21
|
-
problems.push(
|
|
22
|
-
if (!probe.clean)
|
|
23
|
-
problems.push("the working tree has uncommitted changes; commit or stash them first");
|
|
22
|
+
problems.push(`el repositorio no tiene el remoto 'origin'; ${command} necesita subir una rama a GitHub`);
|
|
24
23
|
if (probe.claudeHelp === undefined)
|
|
25
|
-
problems.push(
|
|
24
|
+
problems.push(`claude no está instalado o no está en el PATH; instala Claude Code y vuelve a correr ${command}`);
|
|
26
25
|
else if (!probe.claudeHelp.includes(CLOUD_FLAG))
|
|
27
|
-
problems.push(
|
|
26
|
+
problems.push(`tu versión de claude no soporta --cloud; actualiza Claude Code y vuelve a correr ${command}`);
|
|
28
27
|
return problems;
|
|
29
28
|
}
|