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/README.md
CHANGED
|
@@ -35,17 +35,27 @@ 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`.
|
|
46
|
+
|
|
47
|
+
Before Claude opens, a short screen says what will happen and waits for Enter. If the repo uses
|
|
48
|
+
[claude-repo-guard](https://github.com/ramon-sg/claude-repo-guard) and has its `claude:free`
|
|
49
|
+
script, Claude opens through it, so the skill can write the files the guard blocks.
|
|
42
50
|
3. **Configure the environment in claude.ai** following `docs/agent-cloud.md`, which `prepare`
|
|
43
51
|
generated from your `.claude/cloud.json`. The document keeps growing: when the skill, locally
|
|
44
52
|
or in the cloud, finds that a domain or an environment variable is missing, it adds it to
|
|
45
53
|
`.claude/cloud.json`, regenerates the document and tells you in the session what to add in
|
|
46
54
|
claude.ai.
|
|
47
|
-
4. **Verification in the cloud:** `prepare`
|
|
48
|
-
cloud session runs `cloud-ready verify`,
|
|
55
|
+
4. **Verification in the cloud:** `prepare` (new PR) or `verify` (current branch, once pushed)
|
|
56
|
+
launches `claude --cloud` on that branch, so a real cloud session runs `cloud-ready verify`,
|
|
57
|
+
fixes what fails and reports in the PR. Run `npx agent-cloud-kit verify` again whenever you
|
|
58
|
+
change the environment.
|
|
49
59
|
|
|
50
60
|
`claude --cloud` uses the environment chosen with `/remote-env` in `claude`, and the Claude GitHub
|
|
51
61
|
App must be installed on the repo (without it the session cannot push). Inside the cloud, `gh api`
|
|
@@ -145,7 +155,8 @@ A Node version that is not exact (`22`, `22.1`, `lts/iron`, `lts/*` or `node`) i
|
|
|
145
155
|
| `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
156
|
| `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
157
|
| `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
|
|
158
|
+
| `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. |
|
|
159
|
+
| `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
160
|
|
|
150
161
|
Environment variables: `AGENT_CLOUD_KIT_DIR` overrides `/opt/agent-cloud-kit` and `AGENT_CLOUD_KIT_HOME`
|
|
151
162
|
overrides `/home/user`; both exist mainly for tests.
|
|
@@ -158,7 +169,7 @@ package that `setup` left on the saved machine, so it needs no network and no `n
|
|
|
158
169
|
```json
|
|
159
170
|
{
|
|
160
171
|
"type": "command",
|
|
161
|
-
"command": "[ \"$CLAUDE_CODE_REMOTE\" = true ] || exit 0; /opt/agent-cloud-kit/bin/agent-cloud-kit session || echo 'agent-cloud-kit:
|
|
172
|
+
"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
173
|
"timeout": 600
|
|
163
174
|
}
|
|
164
175
|
```
|
|
@@ -169,8 +180,8 @@ When the repo pins another version than the one that is running and that version
|
|
|
169
180
|
### Machine fingerprint
|
|
170
181
|
|
|
171
182
|
`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
|
-
|
|
183
|
+
version. If the repo changes afterwards, `session` warns that "la máquina se preparó con
|
|
184
|
+
otra configuración". The saved machine is rebuilt by claude.ai about every 7 days, or when you
|
|
174
185
|
edit the setup script field.
|
|
175
186
|
|
|
176
187
|
## 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 });
|
|
@@ -315,14 +323,24 @@ function onProcessInterrupt(handler) {
|
|
|
315
323
|
return () => process.off("SIGINT", listener);
|
|
316
324
|
}
|
|
317
325
|
/**
|
|
318
|
-
* Shows a text on the terminal and waits for Enter.
|
|
326
|
+
* Shows a text on the terminal and waits for Enter. Ctrl+C rejects with `InterruptedError`.
|
|
319
327
|
*
|
|
320
|
-
* @param text - Text to show.
|
|
328
|
+
* @param text - Text to show, printed as is.
|
|
329
|
+
* @throws InterruptedError on Ctrl+C.
|
|
321
330
|
* @example
|
|
322
|
-
* await waitTerminal("
|
|
331
|
+
* await waitTerminal("Presiona Enter ");
|
|
323
332
|
*/
|
|
324
333
|
async function waitTerminal(text) {
|
|
325
|
-
|
|
334
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
335
|
+
try {
|
|
336
|
+
await new Promise((resolveAnswer, reject) => {
|
|
337
|
+
rl.on("SIGINT", () => reject(new InterruptedError()));
|
|
338
|
+
rl.question(text).then(resolveAnswer, reject);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
finally {
|
|
342
|
+
rl.close();
|
|
343
|
+
}
|
|
326
344
|
}
|
|
327
345
|
/**
|
|
328
346
|
* Runs `prepare` in the working directory.
|
|
@@ -332,23 +350,54 @@ async function waitTerminal(text) {
|
|
|
332
350
|
* @returns 0 when the cloud session was started, 130 after Ctrl+C, 1 otherwise.
|
|
333
351
|
*/
|
|
334
352
|
async function prepareCommand(args, deps) {
|
|
335
|
-
const
|
|
353
|
+
const known = [RESUME_OPTION, PR_OPTION, HERE_OPTION];
|
|
354
|
+
const unknown = args.filter((arg) => !known.includes(arg));
|
|
336
355
|
if (unknown.length) {
|
|
337
|
-
deps.print(`${OUTPUT_PREFIX}
|
|
356
|
+
deps.print(`${OUTPUT_PREFIX}opción desconocida para prepare: ${unknown.join(" ")}`);
|
|
357
|
+
return EXIT_FAIL;
|
|
358
|
+
}
|
|
359
|
+
if (args.filter((arg) => known.includes(arg)).length > 1) {
|
|
360
|
+
deps.print(`${OUTPUT_PREFIX}usa solo una de ${known.join(", ")}`);
|
|
338
361
|
return EXIT_FAIL;
|
|
339
362
|
}
|
|
340
|
-
|
|
363
|
+
const place = args.includes(PR_OPTION) ? "pr" : args.includes(HERE_OPTION) ? "here" : undefined;
|
|
364
|
+
return prepare(deps.cwd, prepareDeps(deps), { resume: args.includes(RESUME_OPTION), place });
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Runs `verify`: launches the cloud verification of the current branch.
|
|
368
|
+
*
|
|
369
|
+
* @param args - Arguments after `verify` (none allowed).
|
|
370
|
+
* @param deps - CLI dependencies.
|
|
371
|
+
* @returns 0 when the cloud session was started, 1 otherwise.
|
|
372
|
+
*/
|
|
373
|
+
async function verifyCommand(args, deps) {
|
|
374
|
+
if (args.length) {
|
|
375
|
+
deps.print(`${OUTPUT_PREFIX}opción desconocida para verify: ${args.join(" ")}`);
|
|
376
|
+
return EXIT_FAIL;
|
|
377
|
+
}
|
|
378
|
+
return verifyBranch(deps.cwd, prepareDeps(deps));
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Builds the dependencies of `prepare` and `verify` from the CLI ones, with terminal defaults.
|
|
382
|
+
*
|
|
383
|
+
* @param deps - CLI dependencies.
|
|
384
|
+
* @returns The dependencies.
|
|
385
|
+
*/
|
|
386
|
+
function prepareDeps(deps) {
|
|
387
|
+
return {
|
|
341
388
|
run: deps.run,
|
|
342
389
|
capture: deps.capture ?? captureProcess,
|
|
343
390
|
ask: deps.ask ?? askTerminal,
|
|
391
|
+
choose: deps.choose ?? chooseTerminal,
|
|
344
392
|
waitEnter: deps.waitEnter ?? waitTerminal,
|
|
345
393
|
print: deps.print,
|
|
346
394
|
packageVersion: deps.packageVersion,
|
|
347
395
|
packageRoot: deps.packageRoot ?? RUNNING_PACKAGE_ROOT,
|
|
348
396
|
today: deps.today ?? branchDay(new Date()),
|
|
349
397
|
isTty: deps.isTty ?? (process.stdin.isTTY === true && process.stdout.isTTY === true),
|
|
398
|
+
color: deps.isTty === undefined && process.stdout.isTTY === true && !deps.env.NO_COLOR,
|
|
350
399
|
onInterrupt: deps.onInterrupt ?? onProcessInterrupt,
|
|
351
|
-
}
|
|
400
|
+
};
|
|
352
401
|
}
|
|
353
402
|
/**
|
|
354
403
|
* Runs the CLI.
|
|
@@ -375,6 +424,8 @@ export async function main(argv, deps) {
|
|
|
375
424
|
return manualCommand(deps);
|
|
376
425
|
if (command === "prepare")
|
|
377
426
|
return prepareCommand(rest, deps);
|
|
427
|
+
if (command === "verify")
|
|
428
|
+
return verifyCommand(rest, deps);
|
|
378
429
|
if (command === "--version") {
|
|
379
430
|
deps.print(deps.packageVersion);
|
|
380
431
|
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
|