agent-cloud-kit 1.0.0-rc.6 → 1.0.0-rc.8
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 +14 -0
- package/dist/block.js +76 -0
- package/dist/cli.js +67 -45
- package/dist/config.js +12 -11
- package/dist/docker.js +5 -6
- package/dist/doctor.js +39 -28
- package/dist/init.js +13 -12
- package/dist/installers.js +7 -6
- package/dist/lang.js +84 -0
- package/dist/launcher.js +2 -1
- package/dist/manual.js +20 -10
- package/dist/messages/en.js +226 -0
- package/dist/messages/es.js +234 -0
- package/dist/messages/index.js +75 -0
- package/dist/prepare/banner.js +18 -18
- package/dist/prepare/detect.js +8 -6
- package/dist/prepare/index.js +101 -61
- package/dist/prepare/ui.js +0 -27
- package/dist/run.js +3 -2
- package/dist/select.js +3 -2
- package/dist/session.js +11 -14
- package/dist/setup.js +10 -9
- package/dist/skill.js +7 -6
- package/dist/steps.js +2 -3
- package/package.json +1 -1
- package/skills/cloud-ready/SKILL.md +20 -4
- package/skills/cloud-ready/reference/compat.md +156 -4
- package/skills/cloud-ready/reference/knowledge.md +97 -34
package/dist/init.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { CONFIG_FILE, OUTPUT_PREFIX, PACKAGE } from "./constants.js";
|
|
8
|
+
import { t } from "./messages/index.js";
|
|
8
9
|
import { installSkill, linkSkill } from "./skill.js";
|
|
9
10
|
/** Path of the Claude Code settings file, relative to the repo root. */
|
|
10
11
|
const SETTINGS_FILE = ".claude/settings.json";
|
|
@@ -35,8 +36,8 @@ function isRecord(value) {
|
|
|
35
36
|
* @returns The failed outcome.
|
|
36
37
|
*/
|
|
37
38
|
function addByHand(reason) {
|
|
38
|
-
const
|
|
39
|
-
return {
|
|
39
|
+
const hook = JSON.stringify({ hooks: [HOOK] });
|
|
40
|
+
return { entry: { kind: "warn", text: t().init.byHand(SETTINGS_FILE, reason, hook) }, ok: false };
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
42
43
|
* Writes a JSON file with a trailing newline, creating its folder.
|
|
@@ -70,7 +71,7 @@ function hasHook(entries) {
|
|
|
70
71
|
function scaffoldConfig(repo, packageVersion) {
|
|
71
72
|
const file = join(repo, CONFIG_FILE);
|
|
72
73
|
if (existsSync(file))
|
|
73
|
-
return
|
|
74
|
+
return { kind: "same", text: t().init.configExists(CONFIG_FILE) };
|
|
74
75
|
writeJson(file, {
|
|
75
76
|
$schema: `https://unpkg.com/${PACKAGE}@${packageVersion}/schema.json`,
|
|
76
77
|
version: packageVersion,
|
|
@@ -82,7 +83,7 @@ function scaffoldConfig(repo, packageVersion) {
|
|
|
82
83
|
requiredEnv: [],
|
|
83
84
|
domains: [],
|
|
84
85
|
});
|
|
85
|
-
return
|
|
86
|
+
return { kind: "ok", text: t().init.configCreated(CONFIG_FILE) };
|
|
86
87
|
}
|
|
87
88
|
/**
|
|
88
89
|
* Adds the SessionStart hook to `.claude/settings.json`, keeping everything else.
|
|
@@ -98,21 +99,21 @@ function registerHook(repo) {
|
|
|
98
99
|
settings = JSON.parse(readFileSync(file, "utf8"));
|
|
99
100
|
}
|
|
100
101
|
catch {
|
|
101
|
-
return addByHand(
|
|
102
|
+
return addByHand(t().init.notJson);
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
if (!isRecord(settings))
|
|
105
|
-
return addByHand(
|
|
106
|
+
return addByHand(t().init.notObject);
|
|
106
107
|
const hooks = settings.hooks ?? {};
|
|
107
108
|
if (!isRecord(hooks))
|
|
108
|
-
return addByHand(
|
|
109
|
+
return addByHand(t().init.hooksNotObject);
|
|
109
110
|
const entries = hooks.SessionStart ?? [];
|
|
110
111
|
if (!Array.isArray(entries))
|
|
111
|
-
return addByHand(
|
|
112
|
+
return addByHand(t().init.sessionStartNotArray);
|
|
112
113
|
if (hasHook(entries))
|
|
113
|
-
return {
|
|
114
|
+
return { entry: { kind: "same", text: t().init.hookExists(SETTINGS_FILE) }, ok: true };
|
|
114
115
|
writeJson(file, { ...settings, hooks: { ...hooks, SessionStart: [...entries, { hooks: [HOOK] }] } });
|
|
115
|
-
return {
|
|
116
|
+
return { entry: { kind: "ok", text: t().init.hookAdded(SETTINGS_FILE) }, ok: true };
|
|
116
117
|
}
|
|
117
118
|
/**
|
|
118
119
|
* Sets a repo up for agent-cloud-kit; safe to run again. It is the only place that copies the
|
|
@@ -131,6 +132,6 @@ export function init(repo, packageVersion, packageRoot) {
|
|
|
131
132
|
const hook = registerHook(repo);
|
|
132
133
|
const skill = installSkill(repo, packageRoot, packageVersion);
|
|
133
134
|
const link = linkSkill(repo);
|
|
134
|
-
const
|
|
135
|
-
return { lines:
|
|
135
|
+
const entries = [config, hook.entry, skill, ...(link ? [{ kind: "ok", text: link }] : [])];
|
|
136
|
+
return { lines: entries.map((e) => OUTPUT_PREFIX + e.text), entries, ok: hook.ok };
|
|
136
137
|
}
|
package/dist/installers.js
CHANGED
|
@@ -7,6 +7,7 @@ import { tmpdir } from "node:os";
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { TOOL_INSTALL_TIMEOUT_MS } from "./constants.js";
|
|
9
9
|
import { TOOLS } from "./detect.js";
|
|
10
|
+
import { t } from "./messages/index.js";
|
|
10
11
|
import { step } from "./steps.js";
|
|
11
12
|
/** Base URL of the Node.js distribution. */
|
|
12
13
|
const NODE_DIST_URL = "https://nodejs.org/dist";
|
|
@@ -123,7 +124,7 @@ async function nodeExpectedHash(run, env, dir, version, file) {
|
|
|
123
124
|
await run("curl", ["-fsSL", "-o", path, "--", url], { env, timeoutMs: TOOL_INSTALL_TIMEOUT_MS });
|
|
124
125
|
const hash = parseShasums(readFileSync(path, "utf8"), file);
|
|
125
126
|
if (!hash)
|
|
126
|
-
throw new Error(
|
|
127
|
+
throw new Error(t().installers.notInShasums(file, NODE_SHASUMS_FILE));
|
|
127
128
|
return hash;
|
|
128
129
|
}
|
|
129
130
|
/**
|
|
@@ -138,7 +139,7 @@ async function nodeExpectedHash(run, env, dir, version, file) {
|
|
|
138
139
|
export function nodeArch(arch) {
|
|
139
140
|
const mapped = NODE_ARCHS[arch];
|
|
140
141
|
if (!mapped)
|
|
141
|
-
throw new Error(
|
|
142
|
+
throw new Error(t().installers.unsupportedArch(arch));
|
|
142
143
|
return mapped;
|
|
143
144
|
}
|
|
144
145
|
/**
|
|
@@ -153,7 +154,7 @@ export function nodeArch(arch) {
|
|
|
153
154
|
*/
|
|
154
155
|
export function assertVersion(tool, version) {
|
|
155
156
|
if (!VERSION_PATTERN.test(version))
|
|
156
|
-
throw new Error(
|
|
157
|
+
throw new Error(t().installers.invalidVersion(tool, version));
|
|
157
158
|
}
|
|
158
159
|
/**
|
|
159
160
|
* Tells whether a version is safe to use.
|
|
@@ -254,7 +255,7 @@ export function curlNodeIndex(run, env) {
|
|
|
254
255
|
await run("curl", ["-fsSL", "--max-time", NODE_INDEX_TIMEOUT_SECONDS, "-o", file, "--", NODE_INDEX_URL], { env });
|
|
255
256
|
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
256
257
|
if (!Array.isArray(parsed))
|
|
257
|
-
throw new Error(
|
|
258
|
+
throw new Error(t().installers.indexNotList);
|
|
258
259
|
return parsed.filter((r) => typeof r === "object" && r !== null && typeof r.version === "string" && (r.lts === false || typeof r.lts === "string"));
|
|
259
260
|
}
|
|
260
261
|
finally {
|
|
@@ -283,7 +284,7 @@ export async function resolveVersions(versions, fetchIndex) {
|
|
|
283
284
|
const result = await step(`node ${requested}`, async () => {
|
|
284
285
|
exact = resolveNodeVersion(requested, await fetchIndex());
|
|
285
286
|
if (!exact)
|
|
286
|
-
throw new Error(
|
|
287
|
+
throw new Error(t().installers.noNodeMatch(requested));
|
|
287
288
|
});
|
|
288
289
|
if (!exact)
|
|
289
290
|
return { resolved, results: [result] };
|
|
@@ -403,7 +404,7 @@ export async function installTools(versions, toolsDir, run, env) {
|
|
|
403
404
|
}
|
|
404
405
|
if (tool === "node" && !NODE_EXACT.test(version)) {
|
|
405
406
|
results.push(await step(`${tool} ${version}`, async () => {
|
|
406
|
-
throw new Error(
|
|
407
|
+
throw new Error(t().installers.nodeMustResolve(version));
|
|
407
408
|
}));
|
|
408
409
|
continue;
|
|
409
410
|
}
|
package/dist/lang.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The language of the terminal: `--lang`, then `AGENT_CLOUD_KIT_LANG`, then Spanish.
|
|
3
|
+
* `LANG` and `LC_ALL` are not read: on macOS they are often `en_US.UTF-8` for Spanish speakers.
|
|
4
|
+
*/
|
|
5
|
+
/** Environment variable that fixes the language. */
|
|
6
|
+
export const LANG_ENV = "AGENT_CLOUD_KIT_LANG";
|
|
7
|
+
/** Option that picks the language for one run. */
|
|
8
|
+
export const LANG_OPTION = "--lang";
|
|
9
|
+
/** A `--lang` or `AGENT_CLOUD_KIT_LANG` value that is neither `es` nor `en`. */
|
|
10
|
+
export class LangError extends Error {
|
|
11
|
+
/** Where the value came from: `--lang` or `AGENT_CLOUD_KIT_LANG`. */
|
|
12
|
+
source;
|
|
13
|
+
/** The value. */
|
|
14
|
+
value;
|
|
15
|
+
/**
|
|
16
|
+
* Builds the error.
|
|
17
|
+
*
|
|
18
|
+
* @param source - Where the value came from.
|
|
19
|
+
* @param value - The value.
|
|
20
|
+
*/
|
|
21
|
+
constructor(source, value) {
|
|
22
|
+
super(`${source}: ${value}`);
|
|
23
|
+
this.name = "LangError";
|
|
24
|
+
this.source = source;
|
|
25
|
+
this.value = value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Tells whether a value is a language.
|
|
30
|
+
*
|
|
31
|
+
* @param value - The value.
|
|
32
|
+
* @returns True for `es` and `en`.
|
|
33
|
+
*/
|
|
34
|
+
function isLang(value) {
|
|
35
|
+
return value === "es" || value === "en";
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The language of the terminal.
|
|
39
|
+
*
|
|
40
|
+
* @param flag - The `--lang` value, or undefined.
|
|
41
|
+
* @param env - The environment.
|
|
42
|
+
* @returns The flag, else `AGENT_CLOUD_KIT_LANG` (empty counts as unset), else `es`.
|
|
43
|
+
* @throws LangError when the value is neither `es` nor `en`.
|
|
44
|
+
* @example
|
|
45
|
+
* resolveLang(undefined, { AGENT_CLOUD_KIT_LANG: "en" }); // "en"
|
|
46
|
+
*/
|
|
47
|
+
export function resolveLang(flag, env) {
|
|
48
|
+
const fromEnv = env[LANG_ENV]?.trim() ? env[LANG_ENV] : undefined;
|
|
49
|
+
const value = flag ?? fromEnv;
|
|
50
|
+
if (value === undefined)
|
|
51
|
+
return "es";
|
|
52
|
+
const lang = value.trim().toLowerCase();
|
|
53
|
+
if (!isLang(lang))
|
|
54
|
+
throw new LangError(flag !== undefined ? LANG_OPTION : LANG_ENV, value);
|
|
55
|
+
return lang;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Takes `--lang <x>` or `--lang=<x>` out of the arguments, wherever it is.
|
|
59
|
+
*
|
|
60
|
+
* @param argv - Arguments after the executable name.
|
|
61
|
+
* @returns The other arguments and the value (empty string when `--lang` has no value).
|
|
62
|
+
* @example
|
|
63
|
+
* splitLang(["doctor", "--lang", "en"]); // { args: ["doctor"], flag: "en" }
|
|
64
|
+
*/
|
|
65
|
+
export function splitLang(argv) {
|
|
66
|
+
const args = [];
|
|
67
|
+
let flag;
|
|
68
|
+
for (let i = 0; i < argv.length; i++) {
|
|
69
|
+
const arg = argv[i];
|
|
70
|
+
if (arg === LANG_OPTION) {
|
|
71
|
+
const next = argv[i + 1];
|
|
72
|
+
flag = next !== undefined && !next.startsWith("-") ? next : "";
|
|
73
|
+
if (flag)
|
|
74
|
+
i++;
|
|
75
|
+
}
|
|
76
|
+
else if (arg.startsWith(`${LANG_OPTION}=`)) {
|
|
77
|
+
flag = arg.slice(LANG_OPTION.length + 1);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
args.push(arg);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return flag === undefined ? { args } : { args, flag };
|
|
84
|
+
}
|
package/dist/launcher.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { CONFIG_FILE, PACKAGE } from "./constants.js";
|
|
8
|
+
import { t } from "./messages/index.js";
|
|
8
9
|
/** Directory, inside the tools dir, that holds one installed copy of this package per version. */
|
|
9
10
|
const PKG_DIR = "pkg";
|
|
10
11
|
/** Name of the CLI executable. */
|
|
@@ -58,7 +59,7 @@ export function planSetup(repos, current, read) {
|
|
|
58
59
|
return repos.map((repo) => {
|
|
59
60
|
const { config, errors } = read(repo);
|
|
60
61
|
if (!config)
|
|
61
|
-
return { repo, action: "skip", reason: errors.length ? errors.join("; ") :
|
|
62
|
+
return { repo, action: "skip", reason: errors.length ? errors.join("; ") : t().config.missing(CONFIG_FILE) };
|
|
62
63
|
if (config.version === current)
|
|
63
64
|
return { repo, action: "run", version: current };
|
|
64
65
|
return { repo, action: "relaunch", version: config.version };
|
package/dist/manual.js
CHANGED
|
@@ -6,6 +6,7 @@ import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync,
|
|
|
6
6
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { readConfig } from "./config.js";
|
|
8
8
|
import { CONFIG_FILE, PACKAGE, packageSpec } from "./constants.js";
|
|
9
|
+
import { es, t } from "./messages/index.js";
|
|
9
10
|
/** Path of the manual, relative to the repo. */
|
|
10
11
|
export const MANUAL_FILE = "docs/agent-cloud.md";
|
|
11
12
|
/** Marker that opens the generated block. */
|
|
@@ -60,35 +61,43 @@ function allowedHosts(config) {
|
|
|
60
61
|
return [...new Set([...config.domains, ...hosts])].sort();
|
|
61
62
|
}
|
|
62
63
|
/**
|
|
63
|
-
* Renders the "Configurar el entorno en claude.ai" section
|
|
64
|
+
* Renders the "Configurar el entorno en claude.ai" section: in Spanish for the manual, in the
|
|
65
|
+
* language of the terminal when `prepare` shows it.
|
|
64
66
|
*
|
|
65
67
|
* @param config - Repo config.
|
|
66
68
|
* @param packageVersion - Version of the running package, for the setup script.
|
|
69
|
+
* @param text - Texts of the section; Spanish when omitted.
|
|
67
70
|
* @returns The section, without a trailing newline.
|
|
68
71
|
* @example
|
|
69
72
|
* renderEnvironmentSection(config, "1.0.0").startsWith("## Configurar el entorno en claude.ai"); // true
|
|
70
73
|
*/
|
|
71
|
-
export function renderEnvironmentSection(config, packageVersion) {
|
|
74
|
+
export function renderEnvironmentSection(config, packageVersion, text = es.environment) {
|
|
72
75
|
const hosts = allowedHosts(config);
|
|
73
76
|
const code = (items) => items.map((item) => `\`${item}\``).join(", ");
|
|
74
77
|
const lines = [
|
|
75
|
-
|
|
78
|
+
text.title,
|
|
76
79
|
"",
|
|
77
|
-
|
|
80
|
+
text.intro,
|
|
78
81
|
"",
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
text.network(hosts.length ? code(hosts) : text.none),
|
|
83
|
+
text.variables(config.requiredEnv.length ? text.namesOnly(code(config.requiredEnv)) : text.noneVariables),
|
|
84
|
+
text.githubApp,
|
|
85
|
+
text.setupScript,
|
|
83
86
|
"",
|
|
84
87
|
"```bash",
|
|
85
88
|
setupScript(packageVersion),
|
|
86
89
|
"```",
|
|
87
90
|
];
|
|
88
91
|
if (config.requiredEnv.length)
|
|
89
|
-
lines.push("",
|
|
92
|
+
lines.push("", text.variablesWarning);
|
|
90
93
|
return lines.join("\n");
|
|
91
94
|
}
|
|
95
|
+
/** What to do when the tests change, one line. */
|
|
96
|
+
const TESTS_CHANGE_SECTION = [
|
|
97
|
+
"## Si cambian los tests",
|
|
98
|
+
"",
|
|
99
|
+
`Si agregas tests (p. ej. de Electron) o cambias cómo se corren, corre \`npx ${PACKAGE} prepare\`: la skill \`cloud-ready\` los adapta a la nube sin cambiar lo que comprueban y actualiza este documento.`,
|
|
100
|
+
].join("\n");
|
|
92
101
|
/** The "how to verify" section, one line. */
|
|
93
102
|
const VERIFY_SECTION = [
|
|
94
103
|
"## Verificar",
|
|
@@ -143,6 +152,7 @@ export function renderManual(input) {
|
|
|
143
152
|
renderEnvironmentSection(input.config, input.packageVersion),
|
|
144
153
|
VERIFY_SECTION,
|
|
145
154
|
`## Qué corre en la nube\n\n${notes?.trim() ? notes.trim() : "Nada anotado."}`,
|
|
155
|
+
TESTS_CHANGE_SECTION,
|
|
146
156
|
END,
|
|
147
157
|
];
|
|
148
158
|
return parts.join("\n\n");
|
|
@@ -179,7 +189,7 @@ export function mergeManual(existing, block) {
|
|
|
179
189
|
export function writeManual(repo, packageVersion) {
|
|
180
190
|
const { config, errors } = readConfig(repo);
|
|
181
191
|
if (!config)
|
|
182
|
-
throw new Error(errors[0] ??
|
|
192
|
+
throw new Error(errors[0] ?? t().manual.noConfig(CONFIG_FILE));
|
|
183
193
|
const file = join(repo, MANUAL_FILE);
|
|
184
194
|
const existing = existsSync(file) ? readFileSync(file, "utf8") : undefined;
|
|
185
195
|
const block = renderManual({ config, packageVersion, notes: resolveNotes(repo, config.notes) });
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/** Command that continues a prepare run. */
|
|
2
|
+
const RESUME = "npx agent-cloud-kit prepare --resume";
|
|
3
|
+
/** Command that launches the cloud verification. */
|
|
4
|
+
const VERIFY = "npx agent-cloud-kit verify";
|
|
5
|
+
export const en = {
|
|
6
|
+
help: `agent-cloud-kit: prepares repos for Claude Code cloud sessions.
|
|
7
|
+
|
|
8
|
+
Usage: agent-cloud-kit <command> [--lang es|en]
|
|
9
|
+
|
|
10
|
+
Commands:
|
|
11
|
+
setup [--repo <path>] prepares the machine for every repo in the home dir (or only <path>)
|
|
12
|
+
session reports the machine state and wires the tools into the session
|
|
13
|
+
docker starts the Docker daemon and waits until it answers
|
|
14
|
+
doctor checks the allowed domains, the tools, Docker and the machine state
|
|
15
|
+
init creates .claude/cloud.json and registers the SessionStart hook
|
|
16
|
+
manual writes docs/agent-cloud.md from .claude/cloud.json
|
|
17
|
+
prepare [--pr|--here|--resume]
|
|
18
|
+
runs locally: gets the repo ready for the cloud, in a new PR (--pr) or on the
|
|
19
|
+
current branch without committing (--here); with no option, it asks where
|
|
20
|
+
verify runs locally: starts a cloud session that verifies the current branch (already pushed)
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
--lang es|en language of the messages (Spanish by default; AGENT_CLOUD_KIT_LANG=es|en sets it)
|
|
24
|
+
--help shows this help
|
|
25
|
+
--version shows the package version
|
|
26
|
+
`,
|
|
27
|
+
lang: {
|
|
28
|
+
invalid: (source, value) => `${source} expects es or en: ${value}`,
|
|
29
|
+
},
|
|
30
|
+
cli: {
|
|
31
|
+
versionMismatch: (running, file, pinned) => `running version ${running}, although ${file} asks for ${pinned}`,
|
|
32
|
+
setupSummary: (repo, steps, failed, names) => `setup ${repo}: ${steps} steps, ${failed} failed${names.length ? ` (${names.join(", ")})` : ""}`,
|
|
33
|
+
skip: (repo, reason) => `skipping ${repo}: ${reason}`,
|
|
34
|
+
relaunching: (version, repo) => `relaunching with version ${version} for ${repo}`,
|
|
35
|
+
relaunchFailed: (version, repo, error) => `relaunching with version ${version} for ${repo} failed: ${error}`,
|
|
36
|
+
repoNeedsPath: (option) => `${option} needs a path`,
|
|
37
|
+
noRepos: (home) => `no repos found in ${home}`,
|
|
38
|
+
setupErrorFor: (repo, error) => `setup error in ${repo}: ${error}`,
|
|
39
|
+
setupError: (error) => `setup error: ${error}`,
|
|
40
|
+
sessionDelegateFailed: (version, error) => `session with version ${version} failed: ${error}`,
|
|
41
|
+
manualWritten: (file) => `wrote ${file}`,
|
|
42
|
+
unknownOption: (command, options) => `unknown option for ${command}: ${options}`,
|
|
43
|
+
onlyOne: (options) => `use only one of ${options}`,
|
|
44
|
+
},
|
|
45
|
+
config: {
|
|
46
|
+
dockerNotObject: '"docker" must be an object',
|
|
47
|
+
unknownField: (key) => `unknown field "${key}"`,
|
|
48
|
+
badPull: '"docker.pull" must be "auto", false or an array of strings',
|
|
49
|
+
notStringList: (key) => `"${key}" must be an array of strings`,
|
|
50
|
+
badHost: (host) => `"domains" has an invalid host: ${host}`,
|
|
51
|
+
badVersion: '"version" must be an exact semver, like "1.0.0"',
|
|
52
|
+
notesNotString: '"notes" must be a string',
|
|
53
|
+
unreadable: (reason) => `could not read the file: ${reason}`,
|
|
54
|
+
notObject: "must contain a JSON object",
|
|
55
|
+
missing: (file) => `no ${file}`,
|
|
56
|
+
},
|
|
57
|
+
run: {
|
|
58
|
+
timedOut: (cmd, ms) => `${cmd} went over the ${ms}ms limit`,
|
|
59
|
+
exitCode: (cmd, code) => `${cmd} exited with code ${code}`,
|
|
60
|
+
exitSignal: (cmd, signal) => `${cmd} was ended by signal ${signal}`,
|
|
61
|
+
},
|
|
62
|
+
docker: {
|
|
63
|
+
missing: "dockerd is not installed",
|
|
64
|
+
didNotStart: (log) => `dockerd did not start; check ${log}`,
|
|
65
|
+
},
|
|
66
|
+
doctor: {
|
|
67
|
+
domain: (host, ok) => `network ${host}: ${ok ? "ok" : "blocked"}`,
|
|
68
|
+
toolInvalid: (tool, version) => `tool ${tool} ${version}: invalid version`,
|
|
69
|
+
toolUnresolved: (tool, version) => `tool ${tool} ${version}: unresolved (setup did not install it)`,
|
|
70
|
+
tool: (tool, version, present) => `tool ${tool} ${version}: ${present ? "ok" : "missing"}`,
|
|
71
|
+
fileMissing: (file) => `${file}: missing`,
|
|
72
|
+
stateMissing: "state: missing (setup did not run)",
|
|
73
|
+
fingerprint: (same) => `fingerprint: ${same ? "ok" : "different"}`,
|
|
74
|
+
step: (name, ok, seconds) => `step ${name}: ${ok ? "ok" : "failed"} (${seconds}s)`,
|
|
75
|
+
docker: (ok) => `docker: ${ok ? "ok" : "missing"}`,
|
|
76
|
+
plugin: (name) => `warning: the plugin ${name} is enabled but is not in the repo; it will not reach the cloud (copy its skills with skills-manager)`,
|
|
77
|
+
skill: (dir, installed, version) => `warning: ${dir} is at ${installed} and the package at ${version}; run npx agent-cloud-kit@${version} init`,
|
|
78
|
+
groupError: (group, error) => `${group}: error: ${error}`,
|
|
79
|
+
groups: { network: "network", tools: "tools", docker: "docker", state: "state", plugins: "plugins", skill: "skill" },
|
|
80
|
+
allOk: "All good",
|
|
81
|
+
someFailed: (failed, total) => (failed === 1 ? `1 of ${total} checks failed` : `${failed} of ${total} checks failed`),
|
|
82
|
+
warnings: (count) => (count === 1 ? "1 warning" : `${count} warnings`),
|
|
83
|
+
failedHint: "look at the lines with missing, blocked or failed",
|
|
84
|
+
},
|
|
85
|
+
session: {
|
|
86
|
+
genericNote: "you are in a Claude Code cloud session. There are no open ports or previews: test against localhost. gh pr and gh issue fail (the GitHub proxy blocks GraphQL): use gh api (REST).",
|
|
87
|
+
setupNotRun: (repo, spec) => `the environment setup did not run for ${repo}. Set this as the environment setup script in claude.ai: npx -y ${spec} setup`,
|
|
88
|
+
setupFailed: (names) => `setup failed: ${names.join(", ")}. Run agent-cloud-kit doctor.`,
|
|
89
|
+
otherConfig: "the machine was prepared with another configuration. Edit the setup script field in claude.ai (any change) so it is rebuilt.",
|
|
90
|
+
slow: (time) => `setup took ${time}, close to the ~5 minute limit.`,
|
|
91
|
+
envFileFailed: (variable, file, error) => `could not write ${variable} (${file}): ${error}`,
|
|
92
|
+
stepTimedOut: (command) => `the session step went over the time limit: ${command}`,
|
|
93
|
+
stepFailed: (command) => `the session step failed: ${command}`,
|
|
94
|
+
missingEnv: (names) => `missing environment variables: ${names.join(", ")}`,
|
|
95
|
+
dockerFailed: (problem) => `could not start dockerd: ${problem}`,
|
|
96
|
+
error: (error) => `session error: ${error}`,
|
|
97
|
+
},
|
|
98
|
+
setup: {
|
|
99
|
+
aptRepoHttps: (prefix) => `the apt repo must be an ${prefix} URL`,
|
|
100
|
+
badDebianName: "invalid Debian package name",
|
|
101
|
+
badImage: "invalid image reference",
|
|
102
|
+
packageMismatch: (source, found, version) => `the package in ${source} is ${found ?? "unknown"}, not ${version}`,
|
|
103
|
+
noConfig: "no configuration",
|
|
104
|
+
},
|
|
105
|
+
steps: {
|
|
106
|
+
aptRepo: (url) => `apt repo ${url}`,
|
|
107
|
+
aptPackage: (name) => `apt package ${name}`,
|
|
108
|
+
selfInstall: (version) => `self install ${version}`,
|
|
109
|
+
writeState: "write state",
|
|
110
|
+
lane: (index) => `lane ${index}`,
|
|
111
|
+
config: "config",
|
|
112
|
+
},
|
|
113
|
+
installers: {
|
|
114
|
+
notInShasums: (file, shasums) => `${file} is not in ${shasums}`,
|
|
115
|
+
unsupportedArch: (arch) => `unsupported architecture for node: ${arch}`,
|
|
116
|
+
invalidVersion: (tool, version) => `invalid version for ${tool}: ${version}`,
|
|
117
|
+
indexNotList: "the node version index is not a list",
|
|
118
|
+
noNodeMatch: (requested) => `no node version matches ${requested}`,
|
|
119
|
+
nodeMustResolve: (version) => `node ${version} must first resolve to an exact version`,
|
|
120
|
+
},
|
|
121
|
+
init: {
|
|
122
|
+
byHand: (file, reason, entry) => `${file} ${reason}; it was left untouched. Add this entry to hooks.SessionStart by hand: ${entry}`,
|
|
123
|
+
notJson: "is not valid JSON",
|
|
124
|
+
notObject: "is not a JSON object",
|
|
125
|
+
hooksNotObject: 'has a "hooks" that is not an object',
|
|
126
|
+
sessionStartNotArray: 'has a "hooks.SessionStart" that is not an array',
|
|
127
|
+
configExists: (file) => `${file} already exists; left as it was`,
|
|
128
|
+
configCreated: (file) => `created ${file}`,
|
|
129
|
+
hookExists: (file) => `${file} already has the SessionStart hook`,
|
|
130
|
+
hookAdded: (file) => `added the SessionStart hook to ${file}`,
|
|
131
|
+
},
|
|
132
|
+
skill: {
|
|
133
|
+
current: (dir, version) => `${dir} is already at ${version}`,
|
|
134
|
+
missing: (path, root) => `${path}/SKILL.md not found in ${root}`,
|
|
135
|
+
installed: (dir, version) => `installed ${dir} ${version}`,
|
|
136
|
+
updated: (dir, from, to) => `updated ${dir} from ${from} to ${to}`,
|
|
137
|
+
linked: (link, dir) => `linked ${link} to ${dir}`,
|
|
138
|
+
},
|
|
139
|
+
manual: {
|
|
140
|
+
noConfig: (file) => `${file} does not exist; run init first`,
|
|
141
|
+
},
|
|
142
|
+
environment: {
|
|
143
|
+
title: "## Configure the environment in claude.ai",
|
|
144
|
+
intro: "Pick the environment with `/remote-env` and edit it in claude.ai/code:",
|
|
145
|
+
network: (hosts) => `- **Network access:** Limited (Custom) with "Also include default list of common package managers" turned on. Extra domains: ${hosts}.`,
|
|
146
|
+
variables: (names) => `- **Variables:** ${names}.`,
|
|
147
|
+
namesOnly: (names) => `${names} (only the names; you fill in the values)`,
|
|
148
|
+
none: "none",
|
|
149
|
+
noneVariables: "none",
|
|
150
|
+
githubApp: "- **Claude GitHub App:** installed on the repo, or the session cannot push.",
|
|
151
|
+
setupScript: "- **Setup script:**",
|
|
152
|
+
variablesWarning: "> ⚠️ Anyone who uses the environment can read the variables, and they do not reach the setup script (only the session).",
|
|
153
|
+
},
|
|
154
|
+
menu: {
|
|
155
|
+
cancelled: "Cancelled",
|
|
156
|
+
},
|
|
157
|
+
preflight: {
|
|
158
|
+
noTty: (command) => `${command} needs an interactive terminal (run the command directly in your terminal)`,
|
|
159
|
+
noGit: (command) => `this is not a git repository; run ${command} inside the repo`,
|
|
160
|
+
noOrigin: (command) => `the repository has no 'origin' remote; ${command} needs to push a branch to GitHub`,
|
|
161
|
+
noClaude: (command) => `claude is not installed or not on the PATH; install Claude Code and run ${command} again`,
|
|
162
|
+
noCloud: (command) => `your claude version does not support --cloud; update Claude Code and run ${command} again`,
|
|
163
|
+
},
|
|
164
|
+
banner: {
|
|
165
|
+
title: "Claude Code opens now",
|
|
166
|
+
subtitle: "with the cloud-ready skill",
|
|
167
|
+
step1: "Claude reviews the repo and proposes how to get it ready for the cloud",
|
|
168
|
+
step2: "Answer its questions",
|
|
169
|
+
step3: (done, exit, after) => `When it says ${done}, leave with ${exit} ${after}`,
|
|
170
|
+
configureDone: "configure finished",
|
|
171
|
+
afterPr: "→ prepare commits, pushes and opens a PR",
|
|
172
|
+
afterHere: "→ the changes stay uncommitted",
|
|
173
|
+
free: (script) => `It opens with ${script} (no sandbox nor permission prompts): the repo uses claude-repo-guard`,
|
|
174
|
+
enter: "opens Claude Code",
|
|
175
|
+
cancel: "· Ctrl+C cancels",
|
|
176
|
+
continue: "once you have configured the environment in claude.ai",
|
|
177
|
+
continueHint: "· opens the cloud session that verifies it",
|
|
178
|
+
},
|
|
179
|
+
prepare: {
|
|
180
|
+
interrupted: "interrupted",
|
|
181
|
+
resume: (reason, worktree) => `${reason}; the worktree is at ${worktree}; run ${RESUME} there to continue`,
|
|
182
|
+
noConfig: (file) => `${file} does not exist`,
|
|
183
|
+
noNotes: (file) => `the notes file ${file} does not exist`,
|
|
184
|
+
worktreeExists: (worktree) => `${worktree} already exists; remove it (git worktree remove ${worktree}) or continue there with ${RESUME}`,
|
|
185
|
+
branchExists: (branch) => `the branch ${branch} already exists; delete it (git branch -D ${branch}) or run prepare another day`,
|
|
186
|
+
worktreeCreated: (worktree, branch) => `created the worktree ${worktree} on the branch ${branch}`,
|
|
187
|
+
worktreeFailed: (error) => `could not create the worktree: ${error}`,
|
|
188
|
+
agentStopped: "the local agent stopped",
|
|
189
|
+
prepareStopped: "prepare stopped",
|
|
190
|
+
configureUnfinished: (problem) => `configure did not finish: ${problem}`,
|
|
191
|
+
otherChanges: "the local agent also changed:",
|
|
192
|
+
includeQuestion: "Include these changes in the commit? [y/N]",
|
|
193
|
+
leftUncommitted: (worktree) => `left uncommitted in ${worktree}`,
|
|
194
|
+
nothingToCommit: "nothing new to commit",
|
|
195
|
+
pushed: (branch) => `pushed the branch ${branch}`,
|
|
196
|
+
pushFailed: (error, worktree) => `could not commit and push: ${error}; fix it in ${worktree} and run ${RESUME} there`,
|
|
197
|
+
prReused: (pr) => `reusing the open PR #${pr}`,
|
|
198
|
+
prDraft: (url) => `draft PR: ${url}`,
|
|
199
|
+
prByHand: (link, branch) => `could not open the PR with gh; open it by hand${link ? `: ${link}` : ` from the branch ${branch}`}`,
|
|
200
|
+
interruptedHere: "interrupted; whatever changed is left uncommitted on your branch",
|
|
201
|
+
interruptedNothing: "interrupted; nothing was created",
|
|
202
|
+
nothingToResume: (suffix, prefix) => `nothing to resume: run prepare --resume in the *${suffix} worktree, on a ${prefix}* branch`,
|
|
203
|
+
resumeUnfinished: (worktree, problem) => `configure did not finish in ${worktree}: ${problem}; run claude "/cloud-ready configure" there first and then ${RESUME}`,
|
|
204
|
+
nothingChanged: "nothing was changed",
|
|
205
|
+
cloudFailedResume: (error, worktree) => `claude --cloud failed: ${error}; run ${RESUME} in ${worktree} to try again`,
|
|
206
|
+
placeQuestion: "Where do I prepare the repo?",
|
|
207
|
+
placePr: "In a new PR",
|
|
208
|
+
placePrHint: (base) => `recommended · starts from origin/${base}, does not touch your branch or your files`,
|
|
209
|
+
placeHere: "On the current branch, without committing",
|
|
210
|
+
placeHereHint: `you review them with git diff, commit and push, and then run ${VERIFY}`,
|
|
211
|
+
stoppedHere: "prepare stopped; whatever changed is left uncommitted on your branch",
|
|
212
|
+
hereUnfinished: (problem) => `configure did not finish: ${problem}; run claude "/cloud-ready configure" and then prepare again`,
|
|
213
|
+
manualFailed: (file, error) => `could not write ${file}: ${error}`,
|
|
214
|
+
hereTitle: "Done, without committing",
|
|
215
|
+
hereSteps: (manual) => ["Review the changes with git diff", "Commit and push", `Configure the environment in claude.ai (${manual})`, `Run ${VERIFY}`],
|
|
216
|
+
noBranch: "you are not on a branch; switch to the branch you want to verify",
|
|
217
|
+
notPrepared: (problem) => `the repo is not prepared (${problem}); run npx agent-cloud-kit prepare first`,
|
|
218
|
+
dirty: "the preparation has uncommitted changes; commit and push: the cloud only sees what is on origin",
|
|
219
|
+
notPushed: (branch) => `the branch ${branch} is not pushed or has unpushed commits; run git push and run verify again`,
|
|
220
|
+
cloudFailed: (error) => `claude --cloud failed: ${error}`,
|
|
221
|
+
cloudStarted: "The cloud session is running",
|
|
222
|
+
cloudWillReport: (branch, pr) => `the cloud session will report with a push to ${branch}${pr ? " and a comment on the PR" : ""}`,
|
|
223
|
+
rowBranch: "Branch",
|
|
224
|
+
rowPr: "PR",
|
|
225
|
+
},
|
|
226
|
+
};
|