agent-cloud-kit 1.0.0-rc.1 → 1.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -10
- package/dist/cli.js +73 -33
- 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/detect.js +10 -11
- package/dist/prepare/index.js +180 -35
- 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/README.md
CHANGED
|
@@ -35,17 +35,23 @@ platform does not tell you:
|
|
|
35
35
|
## The flow
|
|
36
36
|
|
|
37
37
|
1. **Install your agent tooling** with agent-kitchen (config, hook and skill arrive in the repo).
|
|
38
|
-
2. **Run `npx agent-cloud-kit prepare`** in a local terminal. It asks
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
2. **Run `npx agent-cloud-kit prepare`** in a local terminal. It asks where to work, with the
|
|
39
|
+
same menu as agent-kitchen (`--pr` or `--here` skip it):
|
|
40
|
+
- **En un PR nuevo** (default): creates a worktree on a dated branch from `origin`, so your
|
|
41
|
+
branch and uncommitted files are untouched; runs `init`, opens Claude locally with the
|
|
42
|
+
`cloud-ready` skill so it can write the cloud preparation with you, then writes the manual,
|
|
43
|
+
commits, pushes and opens a draft PR.
|
|
44
|
+
- **En la rama actual, sin commit:** does the same in your checkout and stops there; you review
|
|
45
|
+
with `git diff`, commit, push, and then run `npx agent-cloud-kit verify`.
|
|
42
46
|
3. **Configure the environment in claude.ai** following `docs/agent-cloud.md`, which `prepare`
|
|
43
47
|
generated from your `.claude/cloud.json`. The document keeps growing: when the skill, locally
|
|
44
48
|
or in the cloud, finds that a domain or an environment variable is missing, it adds it to
|
|
45
49
|
`.claude/cloud.json`, regenerates the document and tells you in the session what to add in
|
|
46
50
|
claude.ai.
|
|
47
|
-
4. **Verification in the cloud:** `prepare`
|
|
48
|
-
cloud session runs `cloud-ready verify`,
|
|
51
|
+
4. **Verification in the cloud:** `prepare` (new PR) or `verify` (current branch, once pushed)
|
|
52
|
+
launches `claude --cloud` on that branch, so a real cloud session runs `cloud-ready verify`,
|
|
53
|
+
fixes what fails and reports in the PR. Run `npx agent-cloud-kit verify` again whenever you
|
|
54
|
+
change the environment.
|
|
49
55
|
|
|
50
56
|
`claude --cloud` uses the environment chosen with `/remote-env` in `claude`, and the Claude GitHub
|
|
51
57
|
App must be installed on the repo (without it the session cannot push). Inside the cloud, `gh api`
|
|
@@ -145,7 +151,8 @@ A Node version that is not exact (`22`, `22.1`, `lts/iron`, `lts/*` or `node`) i
|
|
|
145
151
|
| `agent-cloud-kit doctor` | Checks that the proxy lets the needed hosts through, that the pinned tools are installed, that Docker answers, and the recorded state (fingerprint and each step). Exits 1 if any check fails. |
|
|
146
152
|
| `agent-cloud-kit init` | Creates `.claude/cloud.json` if missing, adds the `SessionStart` hook to `.claude/settings.json` (keeping the hooks already there), copies the `cloud-ready` skill to `.agents/skills/cloud-ready` when its version differs, and links it from `.claude/skills/cloud-ready`. Safe to run again. |
|
|
147
153
|
| `agent-cloud-kit manual` | Writes `docs/agent-cloud.md` from `.claude/cloud.json`, deterministically: the exact setup script (`agent-cloud-kit@<major>`, or the exact version for a release candidate), domains, variable names, how to verify the environment. |
|
|
148
|
-
| `agent-cloud-kit prepare [--resume]` | Local only, needs an interactive terminal: sets the repo up for the cloud
|
|
154
|
+
| `agent-cloud-kit prepare [--pr\|--here\|--resume]` | Local only, needs an interactive terminal: sets the repo up for the cloud, in a new PR or in the current branch without committing (asks when no option is given; see "The flow"). `--resume` continues in the existing worktree. |
|
|
155
|
+
| `agent-cloud-kit verify` | Local only, needs an interactive terminal: launches a cloud session that runs `cloud-ready verify` on the current branch, which must be prepared and pushed. |
|
|
149
156
|
|
|
150
157
|
Environment variables: `AGENT_CLOUD_KIT_DIR` overrides `/opt/agent-cloud-kit` and `AGENT_CLOUD_KIT_HOME`
|
|
151
158
|
overrides `/home/user`; both exist mainly for tests.
|
|
@@ -158,7 +165,7 @@ package that `setup` left on the saved machine, so it needs no network and no `n
|
|
|
158
165
|
```json
|
|
159
166
|
{
|
|
160
167
|
"type": "command",
|
|
161
|
-
"command": "[ \"$CLAUDE_CODE_REMOTE\" = true ] || exit 0; /opt/agent-cloud-kit/bin/agent-cloud-kit session || echo 'agent-cloud-kit:
|
|
168
|
+
"command": "[ \"$CLAUDE_CODE_REMOTE\" = true ] || exit 0; /opt/agent-cloud-kit/bin/agent-cloud-kit session || echo 'agent-cloud-kit: el setup del entorno no corrió; revisa con npx agent-cloud-kit doctor'",
|
|
162
169
|
"timeout": 600
|
|
163
170
|
}
|
|
164
171
|
```
|
|
@@ -169,8 +176,8 @@ When the repo pins another version than the one that is running and that version
|
|
|
169
176
|
### Machine fingerprint
|
|
170
177
|
|
|
171
178
|
`setup` stores a fingerprint of the config, the pinned versions, the images and the package
|
|
172
|
-
version. If the repo changes afterwards, `session` warns that "
|
|
173
|
-
|
|
179
|
+
version. If the repo changes afterwards, `session` warns that "la máquina se preparó con
|
|
180
|
+
otra configuración". The saved machine is rebuilt by claude.ai about every 7 days, or when you
|
|
174
181
|
edit the setup script field.
|
|
175
182
|
|
|
176
183
|
## The proxy CA and `docker build`
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* @file Command line entry point of agent-cloud-kit: wires `setup`, `session`, `docker`, `doctor`,
|
|
4
|
-
* `init`, `manual` and `
|
|
4
|
+
* `init`, `manual`, `prepare` and `verify` to their modules, relaunching or delegating to the version each
|
|
5
5
|
* repo pins.
|
|
6
6
|
*/
|
|
7
7
|
import { execFileSync } from "node:child_process";
|
|
@@ -16,27 +16,31 @@ import { dockerResponds, realDocker } from "./docker.js";
|
|
|
16
16
|
import { init } from "./init.js";
|
|
17
17
|
import { findRepos, packageBin, planSetup, relaunch } from "./launcher.js";
|
|
18
18
|
import { MANUAL_FILE, writeManual } from "./manual.js";
|
|
19
|
-
import { EXIT_INTERRUPTED, InterruptedError, branchDay, prepare } from "./prepare/index.js";
|
|
19
|
+
import { EXIT_INTERRUPTED, InterruptedError, branchDay, prepare, verifyBranch } from "./prepare/index.js";
|
|
20
20
|
import { captureProcess, runProcess } from "./run.js";
|
|
21
|
+
import { chooseTerminal } from "./select.js";
|
|
21
22
|
import { session } from "./session.js";
|
|
22
23
|
import { setupRepo } from "./setup.js";
|
|
23
24
|
/** Help text shown by `--help`. */
|
|
24
|
-
const HELP = `${OUTPUT_PREFIX}
|
|
25
|
+
const HELP = `${OUTPUT_PREFIX}prepara repos para las sesiones cloud de Claude Code.
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
Uso: agent-cloud-kit <comando>
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
setup [--repo <path>]
|
|
30
|
-
session
|
|
31
|
-
docker
|
|
32
|
-
doctor
|
|
33
|
-
init
|
|
34
|
-
manual
|
|
35
|
-
prepare [--resume]
|
|
29
|
+
Comandos:
|
|
30
|
+
setup [--repo <path>] prepara la máquina para cada repo del directorio home (o solo <path>)
|
|
31
|
+
session informa el estado de la máquina y conecta las herramientas a la sesión
|
|
32
|
+
docker inicia el demonio de Docker y espera a que responda
|
|
33
|
+
doctor revisa los dominios permitidos, las herramientas, Docker y el estado de la máquina
|
|
34
|
+
init crea .claude/cloud.json y registra el hook SessionStart
|
|
35
|
+
manual escribe docs/agent-cloud.md a partir de .claude/cloud.json
|
|
36
|
+
prepare [--pr|--here|--resume]
|
|
37
|
+
corre en local: deja el repo listo para la nube, en un PR nuevo (--pr) o en la
|
|
38
|
+
rama actual sin commit (--here); sin opción, pregunta dónde
|
|
39
|
+
verify corre en local: lanza una sesión cloud que verifica la rama actual (ya subida)
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
--help
|
|
39
|
-
--version
|
|
41
|
+
Opciones:
|
|
42
|
+
--help muestra esta ayuda
|
|
43
|
+
--version muestra la versión del paquete
|
|
40
44
|
`;
|
|
41
45
|
/** Environment variable with the project dir that Claude Code passes to hooks. */
|
|
42
46
|
const PROJECT_DIR_VAR = "CLAUDE_PROJECT_DIR";
|
|
@@ -50,6 +54,10 @@ const REPO_OPTION = "--repo";
|
|
|
50
54
|
const GIT_TIMEOUT_MS = 10_000;
|
|
51
55
|
/** Option of `prepare` that continues in an existing prepare worktree. */
|
|
52
56
|
const RESUME_OPTION = "--resume";
|
|
57
|
+
/** Option of `prepare`: work in a new worktree that ends in a PR, without asking. */
|
|
58
|
+
const PR_OPTION = "--pr";
|
|
59
|
+
/** Option of `prepare`: work in the current checkout, without committing and without asking. */
|
|
60
|
+
const HERE_OPTION = "--here";
|
|
53
61
|
/** Exit code for success. */
|
|
54
62
|
const EXIT_OK = 0;
|
|
55
63
|
/** Exit code for failure or wrong usage. */
|
|
@@ -85,7 +93,7 @@ async function runOne(repo, deps) {
|
|
|
85
93
|
for (const error of errors)
|
|
86
94
|
deps.print(`${OUTPUT_PREFIX}${repo}: ${error}`);
|
|
87
95
|
if (config && config.version !== deps.packageVersion) {
|
|
88
|
-
deps.print(`${OUTPUT_PREFIX}
|
|
96
|
+
deps.print(`${OUTPUT_PREFIX}corriendo la versión ${deps.packageVersion}, aunque ${CONFIG_FILE} pide la ${config.version}`);
|
|
89
97
|
}
|
|
90
98
|
const state = await setupRepo(repo, {
|
|
91
99
|
toolsDir: toolsDir(deps.env),
|
|
@@ -97,7 +105,7 @@ async function runOne(repo, deps) {
|
|
|
97
105
|
});
|
|
98
106
|
const failed = state.steps.filter((s) => !s.ok).map((s) => s.name);
|
|
99
107
|
const detail = failed.length ? ` (${failed.join(", ")})` : "";
|
|
100
|
-
deps.print(`${OUTPUT_PREFIX}setup ${repo}: ${state.steps.length}
|
|
108
|
+
deps.print(`${OUTPUT_PREFIX}setup ${repo}: ${state.steps.length} pasos, ${failed.length} fallaron${detail}`);
|
|
101
109
|
}
|
|
102
110
|
/**
|
|
103
111
|
* Carries out one item of the setup plan; may throw, the caller reports it and moves on.
|
|
@@ -107,19 +115,19 @@ async function runOne(repo, deps) {
|
|
|
107
115
|
*/
|
|
108
116
|
async function runPlanItem(item, deps) {
|
|
109
117
|
if (item.action === "skip") {
|
|
110
|
-
deps.print(`${OUTPUT_PREFIX}
|
|
118
|
+
deps.print(`${OUTPUT_PREFIX}se omite ${item.repo}: ${item.reason ?? ""}`);
|
|
111
119
|
return;
|
|
112
120
|
}
|
|
113
121
|
if (item.action === "run" || !item.version) {
|
|
114
122
|
await runOne(item.repo, deps);
|
|
115
123
|
return;
|
|
116
124
|
}
|
|
117
|
-
deps.print(`${OUTPUT_PREFIX}
|
|
125
|
+
deps.print(`${OUTPUT_PREFIX}relanzando con la versión ${item.version} para ${item.repo}`);
|
|
118
126
|
try {
|
|
119
127
|
await relaunch(item.repo, item.version, deps.run, deps.env);
|
|
120
128
|
}
|
|
121
129
|
catch (err) {
|
|
122
|
-
deps.print(`${OUTPUT_PREFIX}
|
|
130
|
+
deps.print(`${OUTPUT_PREFIX}falló el relanzamiento con la versión ${item.version} para ${item.repo}: ${message(err)}`);
|
|
123
131
|
}
|
|
124
132
|
}
|
|
125
133
|
/**
|
|
@@ -136,25 +144,25 @@ async function setupCommand(args, deps) {
|
|
|
136
144
|
if (at >= 0) {
|
|
137
145
|
const value = args[at + 1];
|
|
138
146
|
if (!value || value.startsWith("-"))
|
|
139
|
-
deps.print(`${OUTPUT_PREFIX}${REPO_OPTION}
|
|
147
|
+
deps.print(`${OUTPUT_PREFIX}${REPO_OPTION} necesita una ruta`);
|
|
140
148
|
else
|
|
141
149
|
await runOne(resolve(deps.cwd, value), deps);
|
|
142
150
|
return EXIT_OK;
|
|
143
151
|
}
|
|
144
152
|
const repos = findRepos(homeDir(deps.env));
|
|
145
153
|
if (!repos.length)
|
|
146
|
-
deps.print(`${OUTPUT_PREFIX}no repos
|
|
154
|
+
deps.print(`${OUTPUT_PREFIX}no se encontraron repos en ${homeDir(deps.env)}`);
|
|
147
155
|
for (const item of planSetup(repos, deps.packageVersion, readConfig)) {
|
|
148
156
|
try {
|
|
149
157
|
await runPlanItem(item, deps);
|
|
150
158
|
}
|
|
151
159
|
catch (err) {
|
|
152
|
-
deps.print(`${OUTPUT_PREFIX}setup
|
|
160
|
+
deps.print(`${OUTPUT_PREFIX}error en setup de ${item.repo}: ${message(err)}`);
|
|
153
161
|
}
|
|
154
162
|
}
|
|
155
163
|
}
|
|
156
164
|
catch (err) {
|
|
157
|
-
deps.print(`${OUTPUT_PREFIX}setup
|
|
165
|
+
deps.print(`${OUTPUT_PREFIX}error en setup: ${message(err)}`);
|
|
158
166
|
}
|
|
159
167
|
return EXIT_OK;
|
|
160
168
|
}
|
|
@@ -198,7 +206,7 @@ async function sessionCommand(deps) {
|
|
|
198
206
|
await deps.run(packageBin(tools, wanted), ["session"], { env: { ...deps.env, [DELEGATED_ENV]: DELEGATED_VALUE } });
|
|
199
207
|
}
|
|
200
208
|
catch (err) {
|
|
201
|
-
deps.print(`${OUTPUT_PREFIX}session ${wanted}
|
|
209
|
+
deps.print(`${OUTPUT_PREFIX}falló session con la versión ${wanted}: ${message(err)}`);
|
|
202
210
|
}
|
|
203
211
|
return EXIT_OK;
|
|
204
212
|
}
|
|
@@ -216,7 +224,7 @@ async function sessionCommand(deps) {
|
|
|
216
224
|
async function dockerCommand(deps) {
|
|
217
225
|
if (await deps.docker.ensure().catch(() => false))
|
|
218
226
|
return EXIT_OK;
|
|
219
|
-
deps.print(`${OUTPUT_PREFIX}dockerd
|
|
227
|
+
deps.print(`${OUTPUT_PREFIX}dockerd no arrancó; revisa ${DOCKERD_LOG}`);
|
|
220
228
|
return EXIT_FAIL;
|
|
221
229
|
}
|
|
222
230
|
/**
|
|
@@ -269,7 +277,7 @@ function manualCommand(deps) {
|
|
|
269
277
|
deps.print(`${OUTPUT_PREFIX}${message(err)}`);
|
|
270
278
|
return EXIT_FAIL;
|
|
271
279
|
}
|
|
272
|
-
deps.print(`${OUTPUT_PREFIX}
|
|
280
|
+
deps.print(`${OUTPUT_PREFIX}se escribió ${MANUAL_FILE}`);
|
|
273
281
|
return EXIT_OK;
|
|
274
282
|
}
|
|
275
283
|
/**
|
|
@@ -282,7 +290,7 @@ function manualCommand(deps) {
|
|
|
282
290
|
* @returns The raw answer.
|
|
283
291
|
* @throws InterruptedError on Ctrl+C.
|
|
284
292
|
* @example
|
|
285
|
-
* await askTerminal("
|
|
293
|
+
* await askTerminal("¿Incluir estos cambios en el commit? [s/N]", ""); // "y"
|
|
286
294
|
*/
|
|
287
295
|
const askTerminal = async (question, suggestion) => {
|
|
288
296
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -319,7 +327,7 @@ function onProcessInterrupt(handler) {
|
|
|
319
327
|
*
|
|
320
328
|
* @param text - Text to show.
|
|
321
329
|
* @example
|
|
322
|
-
* await waitTerminal("
|
|
330
|
+
* await waitTerminal("Presiona Enter");
|
|
323
331
|
*/
|
|
324
332
|
async function waitTerminal(text) {
|
|
325
333
|
await askTerminal(text, "");
|
|
@@ -332,15 +340,45 @@ async function waitTerminal(text) {
|
|
|
332
340
|
* @returns 0 when the cloud session was started, 130 after Ctrl+C, 1 otherwise.
|
|
333
341
|
*/
|
|
334
342
|
async function prepareCommand(args, deps) {
|
|
335
|
-
const
|
|
343
|
+
const known = [RESUME_OPTION, PR_OPTION, HERE_OPTION];
|
|
344
|
+
const unknown = args.filter((arg) => !known.includes(arg));
|
|
336
345
|
if (unknown.length) {
|
|
337
|
-
deps.print(`${OUTPUT_PREFIX}
|
|
346
|
+
deps.print(`${OUTPUT_PREFIX}opción desconocida para prepare: ${unknown.join(" ")}`);
|
|
338
347
|
return EXIT_FAIL;
|
|
339
348
|
}
|
|
340
|
-
|
|
349
|
+
if (args.filter((arg) => known.includes(arg)).length > 1) {
|
|
350
|
+
deps.print(`${OUTPUT_PREFIX}usa solo una de ${known.join(", ")}`);
|
|
351
|
+
return EXIT_FAIL;
|
|
352
|
+
}
|
|
353
|
+
const place = args.includes(PR_OPTION) ? "pr" : args.includes(HERE_OPTION) ? "here" : undefined;
|
|
354
|
+
return prepare(deps.cwd, prepareDeps(deps), { resume: args.includes(RESUME_OPTION), place });
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Runs `verify`: launches the cloud verification of the current branch.
|
|
358
|
+
*
|
|
359
|
+
* @param args - Arguments after `verify` (none allowed).
|
|
360
|
+
* @param deps - CLI dependencies.
|
|
361
|
+
* @returns 0 when the cloud session was started, 1 otherwise.
|
|
362
|
+
*/
|
|
363
|
+
async function verifyCommand(args, deps) {
|
|
364
|
+
if (args.length) {
|
|
365
|
+
deps.print(`${OUTPUT_PREFIX}opción desconocida para verify: ${args.join(" ")}`);
|
|
366
|
+
return EXIT_FAIL;
|
|
367
|
+
}
|
|
368
|
+
return verifyBranch(deps.cwd, prepareDeps(deps));
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Builds the dependencies of `prepare` and `verify` from the CLI ones, with terminal defaults.
|
|
372
|
+
*
|
|
373
|
+
* @param deps - CLI dependencies.
|
|
374
|
+
* @returns The dependencies.
|
|
375
|
+
*/
|
|
376
|
+
function prepareDeps(deps) {
|
|
377
|
+
return {
|
|
341
378
|
run: deps.run,
|
|
342
379
|
capture: deps.capture ?? captureProcess,
|
|
343
380
|
ask: deps.ask ?? askTerminal,
|
|
381
|
+
choose: deps.choose ?? chooseTerminal,
|
|
344
382
|
waitEnter: deps.waitEnter ?? waitTerminal,
|
|
345
383
|
print: deps.print,
|
|
346
384
|
packageVersion: deps.packageVersion,
|
|
@@ -348,7 +386,7 @@ async function prepareCommand(args, deps) {
|
|
|
348
386
|
today: deps.today ?? branchDay(new Date()),
|
|
349
387
|
isTty: deps.isTty ?? (process.stdin.isTTY === true && process.stdout.isTTY === true),
|
|
350
388
|
onInterrupt: deps.onInterrupt ?? onProcessInterrupt,
|
|
351
|
-
}
|
|
389
|
+
};
|
|
352
390
|
}
|
|
353
391
|
/**
|
|
354
392
|
* Runs the CLI.
|
|
@@ -375,6 +413,8 @@ export async function main(argv, deps) {
|
|
|
375
413
|
return manualCommand(deps);
|
|
376
414
|
if (command === "prepare")
|
|
377
415
|
return prepareCommand(rest, deps);
|
|
416
|
+
if (command === "verify")
|
|
417
|
+
return verifyCommand(rest, deps);
|
|
378
418
|
if (command === "--version") {
|
|
379
419
|
deps.print(deps.packageVersion);
|
|
380
420
|
return EXIT_OK;
|
package/dist/config.js
CHANGED
|
@@ -78,12 +78,12 @@ function parseDocker(value, errors) {
|
|
|
78
78
|
if (value === undefined)
|
|
79
79
|
return { pull: PULL_AUTO };
|
|
80
80
|
if (!isRecord(value)) {
|
|
81
|
-
errors.push(fail('"docker"
|
|
81
|
+
errors.push(fail('"docker" debe ser un objeto'));
|
|
82
82
|
return { pull: PULL_AUTO };
|
|
83
83
|
}
|
|
84
84
|
for (const key of Object.keys(value)) {
|
|
85
85
|
if (!DOCKER_KEYS.includes(key)) {
|
|
86
|
-
errors.push(fail(`
|
|
86
|
+
errors.push(fail(`campo desconocido "docker.${key}"`));
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
const pull = value.pull;
|
|
@@ -91,7 +91,7 @@ function parseDocker(value, errors) {
|
|
|
91
91
|
return { pull: PULL_AUTO };
|
|
92
92
|
if (pull === false || isStringList(pull))
|
|
93
93
|
return { pull };
|
|
94
|
-
errors.push(fail('"docker.pull"
|
|
94
|
+
errors.push(fail('"docker.pull" debe ser "auto", false o un arreglo de strings'));
|
|
95
95
|
return { pull: PULL_AUTO };
|
|
96
96
|
}
|
|
97
97
|
/**
|
|
@@ -108,7 +108,7 @@ function parseList(raw, key, errors) {
|
|
|
108
108
|
return [];
|
|
109
109
|
if (isStringList(value))
|
|
110
110
|
return value;
|
|
111
|
-
errors.push(fail(`"${key}"
|
|
111
|
+
errors.push(fail(`"${key}" debe ser un arreglo de strings`));
|
|
112
112
|
return [];
|
|
113
113
|
}
|
|
114
114
|
/**
|
|
@@ -124,13 +124,13 @@ function parseDomains(value, errors) {
|
|
|
124
124
|
if (value === undefined)
|
|
125
125
|
return [];
|
|
126
126
|
if (!isStringList(value)) {
|
|
127
|
-
errors.push(fail('"domains"
|
|
127
|
+
errors.push(fail('"domains" debe ser un arreglo de strings'));
|
|
128
128
|
return [];
|
|
129
129
|
}
|
|
130
130
|
return value.filter((host) => {
|
|
131
131
|
const valid = isHostName(host);
|
|
132
132
|
if (!valid)
|
|
133
|
-
errors.push(fail(`"domains"
|
|
133
|
+
errors.push(fail(`"domains" tiene un host inválido: ${JSON.stringify(host)}`));
|
|
134
134
|
return valid;
|
|
135
135
|
});
|
|
136
136
|
}
|
|
@@ -145,11 +145,11 @@ function parseConfig(raw, errors) {
|
|
|
145
145
|
const known = [...CONFIG_KEYS, SCHEMA_KEY];
|
|
146
146
|
for (const key of Object.keys(raw)) {
|
|
147
147
|
if (!known.includes(key))
|
|
148
|
-
errors.push(fail(`
|
|
148
|
+
errors.push(fail(`campo desconocido "${key}"`));
|
|
149
149
|
}
|
|
150
150
|
const version = raw.version;
|
|
151
151
|
if (typeof version !== "string" || !EXACT_SEMVER.test(version)) {
|
|
152
|
-
errors.push(fail('"version"
|
|
152
|
+
errors.push(fail('"version" debe ser un semver exacto, como "1.0.0"'));
|
|
153
153
|
return undefined;
|
|
154
154
|
}
|
|
155
155
|
const lists = Object.fromEntries(LIST_KEYS.map((key) => [key, parseList(raw, key, errors)]));
|
|
@@ -166,7 +166,7 @@ function parseConfig(raw, errors) {
|
|
|
166
166
|
if (typeof raw.notes === "string")
|
|
167
167
|
config.notes = raw.notes;
|
|
168
168
|
else if (raw.notes !== undefined)
|
|
169
|
-
errors.push(fail('"notes"
|
|
169
|
+
errors.push(fail('"notes" debe ser un string'));
|
|
170
170
|
return config;
|
|
171
171
|
}
|
|
172
172
|
/**
|
|
@@ -187,10 +187,10 @@ export function readConfig(repo) {
|
|
|
187
187
|
}
|
|
188
188
|
catch (error) {
|
|
189
189
|
const reason = error instanceof Error ? error.message : String(error);
|
|
190
|
-
return { errors: [fail(`
|
|
190
|
+
return { errors: [fail(`no se pudo leer el archivo: ${reason}`)] };
|
|
191
191
|
}
|
|
192
192
|
if (!isRecord(raw))
|
|
193
|
-
return { errors: [fail("
|
|
193
|
+
return { errors: [fail("debe contener un objeto JSON")] };
|
|
194
194
|
const errors = [];
|
|
195
195
|
const config = parseConfig(raw, errors);
|
|
196
196
|
return config ? { config, errors } : { errors };
|
package/dist/docker.js
CHANGED
|
@@ -11,7 +11,7 @@ const POLL_MS = 1000;
|
|
|
11
11
|
/** Milliseconds in a second. */
|
|
12
12
|
const MS_PER_SECOND = 1000;
|
|
13
13
|
/** Message returned when there is no `dockerd` executable on PATH. */
|
|
14
|
-
export const DOCKERD_MISSING = "dockerd
|
|
14
|
+
export const DOCKERD_MISSING = "dockerd no está instalado";
|
|
15
15
|
/**
|
|
16
16
|
* Tells whether a command exits with code 0.
|
|
17
17
|
*
|
package/dist/doctor.js
CHANGED
|
@@ -49,10 +49,10 @@ async function checkDomain(host, capture) {
|
|
|
49
49
|
try {
|
|
50
50
|
const status = (await capture("curl", args)).trim();
|
|
51
51
|
const ok = status !== BLOCKED_STATUS && status !== NO_ANSWER_STATUS;
|
|
52
|
-
return { text: `
|
|
52
|
+
return { text: `red ${host}: ${ok ? "ok" : "bloqueado"}`, level: verdict(ok) };
|
|
53
53
|
}
|
|
54
54
|
catch {
|
|
55
|
-
return { text: `
|
|
55
|
+
return { text: `red ${host}: bloqueado`, level: "fail" };
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
@@ -82,12 +82,12 @@ function checkTools(repo, toolsDir, state) {
|
|
|
82
82
|
if (!version)
|
|
83
83
|
return [];
|
|
84
84
|
if (!isValidRequest(tool, version))
|
|
85
|
-
return [{ text: `
|
|
85
|
+
return [{ text: `herramienta ${tool} ${version}: versión inválida`, level: "fail" }];
|
|
86
86
|
const executable = toolExecutable(tool, version, toolsDir, state?.resolved ?? {});
|
|
87
87
|
if (!executable)
|
|
88
|
-
return [{ text: `
|
|
88
|
+
return [{ text: `herramienta ${tool} ${version}: sin resolver (setup no la instaló)`, level: "fail" }];
|
|
89
89
|
const present = existsSync(executable);
|
|
90
|
-
return [{ text: `
|
|
90
|
+
return [{ text: `herramienta ${tool} ${version}: ${present ? "ok" : "falta"}`, level: verdict(present) }];
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
@@ -99,19 +99,19 @@ function checkTools(repo, toolsDir, state) {
|
|
|
99
99
|
*/
|
|
100
100
|
function checkState(repo, ctx) {
|
|
101
101
|
if (!existsSync(join(repo, CONFIG_FILE)))
|
|
102
|
-
return [{ text: `${CONFIG_FILE}:
|
|
102
|
+
return [{ text: `${CONFIG_FILE}: falta`, level: "fail" }];
|
|
103
103
|
const { config, errors } = readConfig(repo);
|
|
104
104
|
const out = errors.map((text) => ({ text: `${CONFIG_FILE}: ${text}`, level: "fail" }));
|
|
105
105
|
if (!config)
|
|
106
106
|
return out;
|
|
107
107
|
const state = readState(ctx.toolsDir, repo);
|
|
108
108
|
if (!state)
|
|
109
|
-
return [...out, { text: "
|
|
109
|
+
return [...out, { text: "estado: falta (setup no corrió)", level: "fail" }];
|
|
110
110
|
const same = expectedFingerprint(repo, config, state) === state.fingerprint;
|
|
111
|
-
out.push({ text: `
|
|
111
|
+
out.push({ text: `huella: ${same ? "ok" : "distinta"}`, level: verdict(same) });
|
|
112
112
|
for (const s of state.steps) {
|
|
113
113
|
const seconds = (s.ms / MS_PER_SECOND).toFixed(DURATION_DECIMALS);
|
|
114
|
-
out.push({ text: `
|
|
114
|
+
out.push({ text: `paso ${s.name}: ${s.ok ? "ok" : "falló"} (${seconds}s)`, level: verdict(s.ok) });
|
|
115
115
|
}
|
|
116
116
|
return out;
|
|
117
117
|
}
|
|
@@ -122,7 +122,7 @@ function checkState(repo, ctx) {
|
|
|
122
122
|
* @param repo - Repo root.
|
|
123
123
|
* @returns One warning per enabled plugin.
|
|
124
124
|
* @example
|
|
125
|
-
* checkPlugins("/home/user/app"); // [{ text: "plugin x@m
|
|
125
|
+
* checkPlugins("/home/user/app"); // [{ text: "aviso: el plugin x@m está activado pero ...", level: "warn" }]
|
|
126
126
|
*/
|
|
127
127
|
function checkPlugins(repo) {
|
|
128
128
|
try {
|
|
@@ -133,7 +133,7 @@ function checkPlugins(repo) {
|
|
|
133
133
|
return Object.entries(plugins)
|
|
134
134
|
.filter(([, enabled]) => enabled === true)
|
|
135
135
|
.map(([name]) => ({
|
|
136
|
-
text: `
|
|
136
|
+
text: `aviso: el plugin ${name} está activado pero no está en el repo; no llegará a la nube (copia sus skills con skills-manager)`,
|
|
137
137
|
level: "warn",
|
|
138
138
|
}));
|
|
139
139
|
}
|
|
@@ -148,7 +148,7 @@ function checkPlugins(repo) {
|
|
|
148
148
|
* @param packageVersion - Version of this package.
|
|
149
149
|
* @returns A warning, or nothing when there is no skill or the versions match.
|
|
150
150
|
* @example
|
|
151
|
-
* checkSkill("/home/user/app", "1.0.0"); // [{ text: "
|
|
151
|
+
* checkSkill("/home/user/app", "1.0.0"); // [{ text: "aviso: .agents/skills/cloud-ready está en 0.9.0, ...", level: "warn" }]
|
|
152
152
|
*/
|
|
153
153
|
function checkSkill(repo, packageVersion) {
|
|
154
154
|
const installed = skillVersion(repo);
|
|
@@ -156,7 +156,7 @@ function checkSkill(repo, packageVersion) {
|
|
|
156
156
|
return [];
|
|
157
157
|
return [
|
|
158
158
|
{
|
|
159
|
-
text: `
|
|
159
|
+
text: `aviso: ${SKILL_DIR} está en ${installed} y el paquete en ${packageVersion}; corre npx agent-cloud-kit@${packageVersion} init`,
|
|
160
160
|
level: "warn",
|
|
161
161
|
},
|
|
162
162
|
];
|
|
@@ -187,13 +187,13 @@ async function guarded(name, fn) {
|
|
|
187
187
|
*/
|
|
188
188
|
export async function doctor(repo, ctx) {
|
|
189
189
|
const checks = [];
|
|
190
|
-
checks.push(...(await guarded("
|
|
191
|
-
checks.push(...(await guarded("
|
|
190
|
+
checks.push(...(await guarded("red", async () => Promise.all(domainsToProbe(repo).map((host) => checkDomain(host, ctx.capture))))));
|
|
191
|
+
checks.push(...(await guarded("herramientas", () => checkTools(repo, ctx.toolsDir, readState(ctx.toolsDir, repo)))));
|
|
192
192
|
checks.push(...(await guarded("docker", () => {
|
|
193
193
|
const docker = ctx.dockerInfo();
|
|
194
|
-
return [{ text: `docker: ${docker ? "ok" : "
|
|
194
|
+
return [{ text: `docker: ${docker ? "ok" : "falta"}`, level: verdict(docker) }];
|
|
195
195
|
})));
|
|
196
|
-
checks.push(...(await guarded("
|
|
196
|
+
checks.push(...(await guarded("estado", () => checkState(repo, ctx))));
|
|
197
197
|
checks.push(...(await guarded("plugins", () => checkPlugins(repo))));
|
|
198
198
|
checks.push(...(await guarded("skill", () => checkSkill(repo, ctx.packageVersion))));
|
|
199
199
|
return { lines: checks.map((c) => OUTPUT_PREFIX + c.text), ok: checks.every((c) => c.level !== "fail") };
|
package/dist/init.js
CHANGED
|
@@ -15,7 +15,7 @@ const HOOK_TIMEOUT_SECONDS = 600;
|
|
|
15
15
|
/** Indentation of the files written. */
|
|
16
16
|
const JSON_INDENT = 2;
|
|
17
17
|
/** Command of the SessionStart hook: does nothing outside cloud sessions. */
|
|
18
|
-
const HOOK_COMMAND = "[ \"$CLAUDE_CODE_REMOTE\" = true ] || exit 0; /opt/agent-cloud-kit/bin/agent-cloud-kit session || echo 'agent-cloud-kit:
|
|
18
|
+
const HOOK_COMMAND = "[ \"$CLAUDE_CODE_REMOTE\" = true ] || exit 0; /opt/agent-cloud-kit/bin/agent-cloud-kit session || echo 'agent-cloud-kit: el setup del entorno no corrió; revisa con npx agent-cloud-kit doctor'";
|
|
19
19
|
/** The hook `init` registers. */
|
|
20
20
|
const HOOK = { type: "command", command: HOOK_COMMAND, timeout: HOOK_TIMEOUT_SECONDS };
|
|
21
21
|
/**
|
|
@@ -36,7 +36,7 @@ function isRecord(value) {
|
|
|
36
36
|
*/
|
|
37
37
|
function addByHand(reason) {
|
|
38
38
|
const entry = JSON.stringify({ hooks: [HOOK] });
|
|
39
|
-
return { line: `${SETTINGS_FILE} ${reason}
|
|
39
|
+
return { line: `${SETTINGS_FILE} ${reason}; no se tocó. Agrega esta entrada a hooks.SessionStart a mano: ${entry}`, ok: false };
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* Writes a JSON file with a trailing newline, creating its folder.
|
|
@@ -70,7 +70,7 @@ function hasHook(entries) {
|
|
|
70
70
|
function scaffoldConfig(repo, packageVersion) {
|
|
71
71
|
const file = join(repo, CONFIG_FILE);
|
|
72
72
|
if (existsSync(file))
|
|
73
|
-
return `${CONFIG_FILE}
|
|
73
|
+
return `${CONFIG_FILE} ya existe; se dejó como estaba`;
|
|
74
74
|
writeJson(file, {
|
|
75
75
|
$schema: `https://unpkg.com/${PACKAGE}@${packageVersion}/schema.json`,
|
|
76
76
|
version: packageVersion,
|
|
@@ -82,7 +82,7 @@ function scaffoldConfig(repo, packageVersion) {
|
|
|
82
82
|
requiredEnv: [],
|
|
83
83
|
domains: [],
|
|
84
84
|
});
|
|
85
|
-
return `
|
|
85
|
+
return `se creó ${CONFIG_FILE}`;
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
88
88
|
* Adds the SessionStart hook to `.claude/settings.json`, keeping everything else.
|
|
@@ -98,21 +98,21 @@ function registerHook(repo) {
|
|
|
98
98
|
settings = JSON.parse(readFileSync(file, "utf8"));
|
|
99
99
|
}
|
|
100
100
|
catch {
|
|
101
|
-
return addByHand("
|
|
101
|
+
return addByHand("no es JSON válido");
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
if (!isRecord(settings))
|
|
105
|
-
return addByHand("
|
|
105
|
+
return addByHand("no es un objeto JSON");
|
|
106
106
|
const hooks = settings.hooks ?? {};
|
|
107
107
|
if (!isRecord(hooks))
|
|
108
|
-
return addByHand('
|
|
108
|
+
return addByHand('tiene un "hooks" que no es un objeto');
|
|
109
109
|
const entries = hooks.SessionStart ?? [];
|
|
110
110
|
if (!Array.isArray(entries))
|
|
111
|
-
return addByHand('
|
|
111
|
+
return addByHand('tiene un "hooks.SessionStart" que no es un arreglo');
|
|
112
112
|
if (hasHook(entries))
|
|
113
|
-
return { line: `${SETTINGS_FILE}
|
|
113
|
+
return { line: `${SETTINGS_FILE} ya tiene el hook SessionStart`, ok: true };
|
|
114
114
|
writeJson(file, { ...settings, hooks: { ...hooks, SessionStart: [...entries, { hooks: [HOOK] }] } });
|
|
115
|
-
return { line: `
|
|
115
|
+
return { line: `se agregó el hook SessionStart a ${SETTINGS_FILE}`, ok: true };
|
|
116
116
|
}
|
|
117
117
|
/**
|
|
118
118
|
* Sets a repo up for agent-cloud-kit; safe to run again. It is the only place that copies the
|
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 };
|