agent-kitchen 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -4
- package/dist/cli.js +10 -5
- package/dist/commands/doctor.d.ts +1 -9
- package/dist/commands/doctor.js +22 -6
- package/dist/commands/sync.d.ts +22 -1
- package/dist/commands/sync.js +227 -54
- package/dist/commit-texts.d.ts +18 -0
- package/dist/commit-texts.js +29 -0
- package/dist/decisions.d.ts +54 -0
- package/dist/decisions.js +91 -0
- package/dist/git-finish.d.ts +53 -0
- package/dist/git-finish.js +252 -0
- package/dist/menu.d.ts +33 -0
- package/dist/menu.js +48 -1
- package/dist/piece.d.ts +8 -2
- package/dist/pieces/agents-md.js +1 -3
- package/dist/pieces/cloud.js +2 -2
- package/dist/pieces/guard.js +3 -3
- package/dist/pieces/issue-flow.d.ts +12 -6
- package/dist/pieces/issue-flow.js +151 -29
- package/dist/pieces/skills-manager.d.ts +6 -3
- package/dist/pieces/skills-manager.js +33 -12
- package/dist/pieces/skills.js +3 -4
- package/dist/progress.d.ts +29 -0
- package/dist/progress.js +75 -0
- package/dist/texts/catalog.d.ts +50 -0
- package/dist/texts/catalog.js +1 -0
- package/dist/texts/es.d.ts +7 -0
- package/dist/texts/es.js +139 -0
- package/dist/texts/index.d.ts +30 -0
- package/dist/texts/index.js +31 -0
- package/dist/texts/step-text.d.ts +85 -0
- package/dist/texts/step-text.js +7 -0
- package/dist/tools.d.ts +33 -2
- package/dist/tools.js +52 -5
- package/package.json +1 -1
package/dist/pieces/guard.js
CHANGED
|
@@ -141,11 +141,11 @@ function installedVersion(root) {
|
|
|
141
141
|
*/
|
|
142
142
|
function stepFor(root, installed, latest, flowComing) {
|
|
143
143
|
if (installed === undefined)
|
|
144
|
-
return { piece: NAME, checked: true, downloads: [guardSpec(latest)],
|
|
144
|
+
return { piece: NAME, checked: true, downloads: [guardSpec(latest)], text: { kind: "guard-install", version: latest }, run: (context) => install(context, latest) };
|
|
145
145
|
if (latest !== null && (installed === null || compareVersions(latest, installed) > 0))
|
|
146
|
-
return { piece: NAME, checked: true, downloads: [guardSpec(latest)],
|
|
146
|
+
return { piece: NAME, checked: true, downloads: [guardSpec(latest)], text: { kind: "guard-update", from: installed, to: latest }, run: (context) => install(context, latest) };
|
|
147
147
|
if (flowComing || guardOptions(root, process.platform).changes.length > 0)
|
|
148
|
-
return { piece: NAME, checked: true, downloads: [guardSpec(installed)],
|
|
148
|
+
return { piece: NAME, checked: true, downloads: [guardSpec(installed)], text: { kind: "guard-adjust" }, run: (context) => addOptions(context, installed) };
|
|
149
149
|
return null;
|
|
150
150
|
}
|
|
151
151
|
/**
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file The issue flow (`@ramon-sg/issue-flow`): its CLI, hooks and config, and its skills.
|
|
3
|
-
* The repo's version is the one in package.json.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* The repo's version is the one in package.json. From 0.3.0, installing and updating run its
|
|
4
|
+
* `apply` at the latest version, and reading its `--json` tells what it kept as it was; at
|
|
5
|
+
* the repo's version, `apply` also repairs hooks or a `$schema` its doctor finds behind (a
|
|
6
|
+
* version raised by hand or by Dependabot). Before 0.3.0, installing runs `init` and
|
|
7
|
+
* updating runs `upgrade`. After updating or repairing, and after installing with `apply`,
|
|
8
|
+
* the flow's skills move to the same tag, because they call the CLI. If only the skills are
|
|
9
|
+
* off, they move alone. From 0.2.6 it also offers `labels sync`, after the install or
|
|
10
|
+
* update, when GitHub lacks some of the labels the flow expects. From 0.3.0, whose `apply`
|
|
11
|
+
* keeps the repo's github-rest, it also raises an exact github-rest below the floor of the
|
|
12
|
+
* range that version of the flow declares on npm, as the flow itself reads it.
|
|
13
|
+
* `apply` and `init` run with `--yes`: the person answered agent-kitchen's questions before
|
|
14
|
+
* the steps.
|
|
9
15
|
*/
|
|
10
16
|
import { type Check, type PieceStatus } from "../piece.js";
|
|
11
17
|
/** The piece's name, as `--skip` takes it. */
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file The issue flow (`@ramon-sg/issue-flow`): its CLI, hooks and config, and its skills.
|
|
3
|
-
* The repo's version is the one in package.json.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
* The repo's version is the one in package.json. From 0.3.0, installing and updating run its
|
|
4
|
+
* `apply` at the latest version, and reading its `--json` tells what it kept as it was; at
|
|
5
|
+
* the repo's version, `apply` also repairs hooks or a `$schema` its doctor finds behind (a
|
|
6
|
+
* version raised by hand or by Dependabot). Before 0.3.0, installing runs `init` and
|
|
7
|
+
* updating runs `upgrade`. After updating or repairing, and after installing with `apply`,
|
|
8
|
+
* the flow's skills move to the same tag, because they call the CLI. If only the skills are
|
|
9
|
+
* off, they move alone. From 0.2.6 it also offers `labels sync`, after the install or
|
|
10
|
+
* update, when GitHub lacks some of the labels the flow expects. From 0.3.0, whose `apply`
|
|
11
|
+
* keeps the repo's github-rest, it also raises an exact github-rest below the floor of the
|
|
12
|
+
* range that version of the flow declares on npm, as the flow itself reads it.
|
|
13
|
+
* `apply` and `init` run with `--yes`: the person answered agent-kitchen's questions before
|
|
14
|
+
* the steps.
|
|
15
|
+
*/
|
|
16
|
+
import { FLOW_CATALOG, FLOW_CONFIG, FLOW_PACKAGE } from "../piece.js";
|
|
17
|
+
import { dependencyVersion, isObject, readJson, setDependency } from "../repo.js";
|
|
12
18
|
import { catalogSkills, manifestCatalogs, pinCatalog, updateSkills } from "../skills-files.js";
|
|
13
|
-
import { captureNpx, compareVersions, isRelease, npmLatest, runNpx } from "../tools.js";
|
|
19
|
+
import { captureNpx, compareVersions, isRelease, npmDependencies, npmLatest, runNpx } from "../tools.js";
|
|
14
20
|
/** The piece's name, as `--skip` takes it. */
|
|
15
21
|
export const NAME = "issue-flow";
|
|
16
22
|
/** How the catalog's ref is named when it is off. */
|
|
@@ -19,6 +25,18 @@ const SKILLS_CATALOG_LABEL = "el catálogo";
|
|
|
19
25
|
const JSON_DOCTOR = "0.2.0";
|
|
20
26
|
/** The first version with `labels sync`. */
|
|
21
27
|
const LABELS_SINCE = "0.2.6";
|
|
28
|
+
/** The first version whose `apply` installs, updates and repairs, instead of `init` and `upgrade`. */
|
|
29
|
+
const APPLY_SINCE = "0.3.0";
|
|
30
|
+
/** The client of GitHub's REST API that the flow's skills call. */
|
|
31
|
+
const GITHUB_REST = "@ramon-sg/github-rest";
|
|
32
|
+
/**
|
|
33
|
+
* Whether a version installs and updates with `apply`.
|
|
34
|
+
* @param version - The flow's version.
|
|
35
|
+
* @returns True from 0.3.0 on.
|
|
36
|
+
*/
|
|
37
|
+
function usesApply(version) {
|
|
38
|
+
return compareVersions(version, APPLY_SINCE) >= 0;
|
|
39
|
+
}
|
|
22
40
|
/**
|
|
23
41
|
* The issue flow's version in the repo, as package.json has it: an exact release when the
|
|
24
42
|
* flow set it up, or anything else (a range, a `file:` link) someone wrote by hand.
|
|
@@ -84,6 +102,69 @@ async function runFlow(context, version, args) {
|
|
|
84
102
|
context.fail(`issue-flow ${args[0] ?? ""} falló; corre 'npx ${FLOW_PACKAGE}@${version} ${args.join(" ")}'.`, run.output);
|
|
85
103
|
return false;
|
|
86
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Runs the flow's `apply` at a version and reads its `--json`: what it kept as it was goes as
|
|
107
|
+
* a note. Output that is not its JSON counts as a failure, since it does not tell what it wrote.
|
|
108
|
+
* @param context - Where it runs.
|
|
109
|
+
* @param version - The version, 0.3.0 or later.
|
|
110
|
+
* @returns Whether it worked.
|
|
111
|
+
*/
|
|
112
|
+
async function applyFlow(context, version) {
|
|
113
|
+
const retry = `npx ${FLOW_PACKAGE}@${version} apply --yes`;
|
|
114
|
+
const result = await captureNpx(context.root, [`${FLOW_PACKAGE}@${version}`, "apply", "--yes", "--json"]);
|
|
115
|
+
const output = `${result?.out ?? ""}${result?.err ?? ""}`;
|
|
116
|
+
if (result === null || result.status !== 0) {
|
|
117
|
+
context.fail(`issue-flow apply falló; corre '${retry}'.`, output);
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
let report;
|
|
121
|
+
try {
|
|
122
|
+
report = JSON.parse(result.out);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
report = null;
|
|
126
|
+
}
|
|
127
|
+
if (!isObject(report) || !Array.isArray(report.kept)) {
|
|
128
|
+
context.fail(`issue-flow apply no dijo qué hizo; corre '${retry}'.`, output);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
for (const line of report.kept)
|
|
132
|
+
context.note(String(line));
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The flow's own `doctor --json` at a version.
|
|
137
|
+
* @param root - The repo.
|
|
138
|
+
* @param version - The version, 0.2.0 or later.
|
|
139
|
+
* @param lookup - True to give up after the lookups' time limit, as the status does.
|
|
140
|
+
* @returns Its checks, or null if it could not run or its output is not a list.
|
|
141
|
+
*/
|
|
142
|
+
async function flowDoctor(root, version, lookup = false) {
|
|
143
|
+
const result = await captureNpx(root, [`${FLOW_PACKAGE}@${version}`, "doctor", "--json"], lookup);
|
|
144
|
+
try {
|
|
145
|
+
const found = JSON.parse(result?.out ?? "");
|
|
146
|
+
return Array.isArray(found) ? found : null;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Whether the flow's doctor finds what `apply` repairs: a hook missing or behind, or the
|
|
154
|
+
* config's `$schema` behind. A config that is invalid fails the same check but `apply` cannot
|
|
155
|
+
* fix it, so the `$schema` is told by its detail; and a hook newer than the repo's version
|
|
156
|
+
* makes `apply` refuse, so it does not count.
|
|
157
|
+
* @param checks - The doctor's checks.
|
|
158
|
+
* @param version - The repo's version.
|
|
159
|
+
* @returns True if `apply` would repair them.
|
|
160
|
+
*/
|
|
161
|
+
function needsRepair(checks, version) {
|
|
162
|
+
const hookBehind = (detail) => {
|
|
163
|
+
const found = /^at (\d+\.\d+\.\d+)/.exec(detail)?.[1];
|
|
164
|
+
return found === undefined || compareVersions(found, version) < 0;
|
|
165
|
+
};
|
|
166
|
+
return checks.some((check) => check.ok === false && (String(check.name).endsWith(" hook") ? hookBehind(String(check.detail)) : check.name === FLOW_CONFIG && String(check.detail).includes("$schema")));
|
|
167
|
+
}
|
|
87
168
|
/**
|
|
88
169
|
* The labels the flow expects that GitHub lacks, as `labels sync --dry-run --json` of a
|
|
89
170
|
* version tells (a label that only differs in color or description does not count).
|
|
@@ -116,7 +197,7 @@ async function missingLabels(root, version) {
|
|
|
116
197
|
* @returns The step.
|
|
117
198
|
*/
|
|
118
199
|
function labelsStep(version, missing) {
|
|
119
|
-
return { piece: NAME, checked: true, downloads: [`${FLOW_PACKAGE}@${version}`],
|
|
200
|
+
return { piece: NAME, checked: true, downloads: [`${FLOW_PACKAGE}@${version}`], text: { kind: "flow-labels", missing }, run: async (context) => {
|
|
120
201
|
if (flowVersion(context.root) !== version) {
|
|
121
202
|
context.note(`no se crearon: el repo no tiene issue-flow ${version}`);
|
|
122
203
|
return true;
|
|
@@ -124,20 +205,67 @@ function labelsStep(version, missing) {
|
|
|
124
205
|
return runFlow(context, version, ["labels", "sync"]);
|
|
125
206
|
} };
|
|
126
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* The lowest github-rest a version of the flow works with: the floor of the range it declares
|
|
210
|
+
* on npm (`^0.3.0` gives 0.3.0), as the flow reads it from its own package.json.
|
|
211
|
+
* @param version - The flow's version.
|
|
212
|
+
* @returns The version, or null before 0.3.0 (its `upgrade` raises github-rest) or if npm cannot tell.
|
|
213
|
+
* @example await restMinimum("0.3.0") // "0.3.0"
|
|
214
|
+
*/
|
|
215
|
+
async function restMinimum(version) {
|
|
216
|
+
if (!usesApply(version))
|
|
217
|
+
return null;
|
|
218
|
+
const range = (await npmDependencies(`${FLOW_PACKAGE}@${version}`))?.[GITHUB_REST]?.replace(/^[\^~]/, "");
|
|
219
|
+
return range !== undefined && isRelease(range) ? range : null;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The step that raises github-rest to a version of the flow's minimum, if package.json has an
|
|
223
|
+
* exact version below it. A missing one gets none: `apply` adds it at the minimum. A range is
|
|
224
|
+
* the repo's own choice. At run time it reads package.json again, and raises nothing unless
|
|
225
|
+
* the repo has that version of the flow by then.
|
|
226
|
+
* @param root - The repo.
|
|
227
|
+
* @param version - The flow's version the repo ends at.
|
|
228
|
+
* @param minimum - The lowest github-rest it works with.
|
|
229
|
+
* @returns The step, or null.
|
|
230
|
+
*/
|
|
231
|
+
function restStep(root, version, minimum) {
|
|
232
|
+
const below = (rest) => rest !== null && isRelease(rest) && compareVersions(rest, minimum) < 0;
|
|
233
|
+
const current = dependencyVersion(readJson(root, "package.json"), GITHUB_REST);
|
|
234
|
+
if (!below(current))
|
|
235
|
+
return null;
|
|
236
|
+
return { piece: NAME, checked: true, downloads: [], text: { kind: "github-rest-bump", from: current, to: minimum, flow: version }, run: async (context) => {
|
|
237
|
+
if (flowVersion(context.root) !== version)
|
|
238
|
+
context.note(`no se subió: el repo no tiene issue-flow ${version}`);
|
|
239
|
+
else if (!below(dependencyVersion(readJson(context.root, "package.json"), GITHUB_REST)))
|
|
240
|
+
context.note("package.json ya tenía una versión que sirve");
|
|
241
|
+
else {
|
|
242
|
+
setDependency(context.root, GITHUB_REST, minimum);
|
|
243
|
+
context.note(`${GITHUB_REST} ${minimum} en package.json`);
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
} };
|
|
247
|
+
}
|
|
127
248
|
/**
|
|
128
249
|
* The step the repo needs, if any.
|
|
129
250
|
* @param installed - The repo's version, or null.
|
|
130
251
|
* @param latest - The latest on npm, or null.
|
|
131
252
|
* @param root - The repo.
|
|
253
|
+
* @param repair - Whether the flow's doctor finds what `apply` repairs, at the repo's version.
|
|
132
254
|
* @returns The step, or null.
|
|
133
255
|
*/
|
|
134
|
-
function stepFor(installed, latest, root) {
|
|
135
|
-
if (installed === null && latest !== null)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
256
|
+
function stepFor(installed, latest, root, repair) {
|
|
257
|
+
if (installed === null && latest !== null) {
|
|
258
|
+
const run = usesApply(latest) ? async (context) => (await applyFlow(context, latest)) && (await moveSkills(context, latest)) : (context) => runFlow(context, latest, ["init", "--yes"]);
|
|
259
|
+
return { piece: NAME, checked: true, downloads: [`${FLOW_PACKAGE}@${latest}`], text: { kind: "flow-install", version: latest }, run };
|
|
260
|
+
}
|
|
261
|
+
if (installed !== null && latest !== null && compareVersions(latest, installed) > 0) {
|
|
262
|
+
const update = (context) => (usesApply(latest) ? applyFlow(context, latest) : runFlow(context, latest, ["upgrade"]));
|
|
263
|
+
return { piece: NAME, checked: true, downloads: [`${FLOW_PACKAGE}@${latest}`], text: { kind: "flow-update", from: installed, to: latest }, run: async (context) => (await update(context)) && (await moveSkills(context, latest)) };
|
|
264
|
+
}
|
|
265
|
+
if (installed !== null && repair)
|
|
266
|
+
return { piece: NAME, checked: true, downloads: [`${FLOW_PACKAGE}@${installed}`], text: { kind: "flow-repair", version: installed }, run: async (context) => (await applyFlow(context, installed)) && (skillsOff(context.root, installed).length === 0 || (await moveSkills(context, installed))) };
|
|
139
267
|
if (installed !== null && skillsOff(root, installed).length > 0)
|
|
140
|
-
return { piece: NAME, checked: true, downloads: [],
|
|
268
|
+
return { piece: NAME, checked: true, downloads: [], text: { kind: "flow-skills", version: installed }, run: (context) => moveSkills(context, installed) };
|
|
141
269
|
return null;
|
|
142
270
|
}
|
|
143
271
|
/**
|
|
@@ -153,13 +281,14 @@ export async function issueFlowStatus(root) {
|
|
|
153
281
|
return { name: NAME, summary, installed: true, state: `package.json tiene ${spec}, no una versión exacta; no se toca`, steps: [] };
|
|
154
282
|
const installed = flowVersion(root);
|
|
155
283
|
const latest = await npmLatest(FLOW_PACKAGE);
|
|
156
|
-
const step = stepFor(installed, latest, root);
|
|
157
284
|
const behind = installed !== null && latest !== null && compareVersions(latest, installed) > 0;
|
|
158
285
|
const target = installed === null || behind ? latest : installed;
|
|
159
|
-
const missing = target === null ? [] :
|
|
286
|
+
const [missing, checks, minimum] = await Promise.all([target === null ? [] : missingLabels(root, target), installed !== null && !behind && usesApply(installed) ? flowDoctor(root, installed, true) : null, target === null ? null : restMinimum(target)]);
|
|
287
|
+
const step = stepFor(installed, latest, root, installed !== null && checks !== null && needsRepair(checks, installed));
|
|
288
|
+
const rest = target === null || minimum === null ? null : restStep(root, target, minimum);
|
|
160
289
|
const known = `${latest === null ? " (npm no responde)" : ""}${missing === null ? " (no se pudieron leer los labels de GitHub)" : ""}`;
|
|
161
290
|
const state = installed === null ? `no instalado${known}` : behind ? `${installed}, hay ${latest}${known}` : `${installed}, al día${known}`;
|
|
162
|
-
const steps = [...(step === null ? [] : [step]), ...(target !== null && missing !== null && missing.length > 0 ? [labelsStep(target, missing)] : [])];
|
|
291
|
+
const steps = [...(step === null ? [] : [step]), ...(rest === null ? [] : [rest]), ...(target !== null && missing !== null && missing.length > 0 ? [labelsStep(target, missing)] : [])];
|
|
163
292
|
return { name: NAME, summary, installed: installed !== null, state, steps };
|
|
164
293
|
}
|
|
165
294
|
/**
|
|
@@ -178,15 +307,8 @@ export async function issueFlowChecks(root) {
|
|
|
178
307
|
if (compareVersions(installed, JSON_DOCTOR) < 0)
|
|
179
308
|
return [{ name: NAME, status: "info", detail: `${installed} no tiene doctor --json: corre npx agent-kitchen apply para actualizarlo` }];
|
|
180
309
|
const labelsRead = missingLabels(root, installed);
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
found = JSON.parse(result?.out ?? "");
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
187
|
-
found = null;
|
|
188
|
-
}
|
|
189
|
-
const own = Array.isArray(found)
|
|
310
|
+
const found = await flowDoctor(root, installed);
|
|
311
|
+
const own = found !== null
|
|
190
312
|
? found.map((check) => ({ name: `${NAME}: ${String(check.name)}`, status: check.ok === true ? "ok" : "fail", detail: String(check.detail) }))
|
|
191
313
|
: [{ name: NAME, status: "fail", detail: `no se pudo correr su doctor; corre 'npx ${FLOW_PACKAGE}@${installed} doctor'` }];
|
|
192
314
|
const off = skillsOff(root, installed);
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* install and move the skills, so agent-kitchen is the one that knows the oldest version it
|
|
4
4
|
* works with: it adds it when missing and raises an exact version below it, never lowers
|
|
5
5
|
* one. A range or `workspace:` is the repo's own choice: it is left alone, with a warning if
|
|
6
|
-
* the version it runs is too old. Only in a repo
|
|
6
|
+
* the version it runs is too old. Only in a repo that uses skills and has package.json, or
|
|
7
|
+
* gets one in this run from the flow's install: then the step makes package.json first, and
|
|
8
|
+
* the flow's `apply`, which never adds skills-manager, adds its own packages to it.
|
|
7
9
|
*/
|
|
8
10
|
import { type Check, type PieceStatus } from "../piece.js";
|
|
9
11
|
/** The piece's name, as `--skip` takes it. */
|
|
@@ -15,10 +17,11 @@ export declare const MIN_VERSION = "1.2.0";
|
|
|
15
17
|
* package.json again when it runs.
|
|
16
18
|
* @param root - The repo.
|
|
17
19
|
* @param withSkills - Whether this run may install skills.
|
|
20
|
+
* @param flowComing - Whether this run installs the flow, which makes package.json if missing.
|
|
18
21
|
* @returns The piece's status, or null where the piece does not apply.
|
|
19
|
-
* @example skillsManagerStatus(root, true)?.state // "1.1.0, bajo 1.2.0"
|
|
22
|
+
* @example skillsManagerStatus(root, true, false)?.state // "1.1.0, bajo 1.2.0"
|
|
20
23
|
*/
|
|
21
|
-
export declare function skillsManagerStatus(root: string, withSkills: boolean): PieceStatus | null;
|
|
24
|
+
export declare function skillsManagerStatus(root: string, withSkills: boolean, flowComing: boolean): PieceStatus | null;
|
|
22
25
|
/**
|
|
23
26
|
* Whether the version of skills-manager the repo runs is the one agent-kitchen needs.
|
|
24
27
|
* @param root - The repo.
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
* install and move the skills, so agent-kitchen is the one that knows the oldest version it
|
|
4
4
|
* works with: it adds it when missing and raises an exact version below it, never lowers
|
|
5
5
|
* one. A range or `workspace:` is the repo's own choice: it is left alone, with a warning if
|
|
6
|
-
* the version it runs is too old. Only in a repo
|
|
6
|
+
* the version it runs is too old. Only in a repo that uses skills and has package.json, or
|
|
7
|
+
* gets one in this run from the flow's install: then the step makes package.json first, and
|
|
8
|
+
* the flow's `apply`, which never adds skills-manager, adds its own packages to it.
|
|
7
9
|
*/
|
|
8
10
|
import { SKILLS_MANAGER, SKILLS_MANIFEST } from "../piece.js";
|
|
9
|
-
import { dependencyVersion, installedVersion, readJson, setDependency } from "../repo.js";
|
|
11
|
+
import { dependencyVersion, installedVersion, readJson, setDependency, writeJson } from "../repo.js";
|
|
10
12
|
import { compareVersions, isRelease } from "../tools.js";
|
|
11
13
|
/** The piece's name, as `--skip` takes it. */
|
|
12
14
|
export const NAME = "skills-manager";
|
|
@@ -36,27 +38,46 @@ function below(version) {
|
|
|
36
38
|
return compareVersions(version, MIN_VERSION) < 0;
|
|
37
39
|
}
|
|
38
40
|
/**
|
|
39
|
-
* Whether the piece applies: the repo
|
|
40
|
-
* install
|
|
41
|
+
* Whether the piece applies: the repo uses skills, or this run may install them, and it has
|
|
42
|
+
* package.json or the flow's install is about to make one.
|
|
41
43
|
* @param root - The repo.
|
|
42
44
|
* @param withSkills - Whether this run may install skills (`skills` is not left out).
|
|
45
|
+
* @param flowComing - Whether this run installs the flow.
|
|
43
46
|
* @returns True if it applies.
|
|
44
47
|
*/
|
|
45
|
-
function applies(root, withSkills) {
|
|
46
|
-
return readJson(root, "package.json") !== null && (withSkills || readJson(root, SKILLS_MANIFEST) !== null);
|
|
48
|
+
function applies(root, withSkills, flowComing) {
|
|
49
|
+
return (flowComing || readJson(root, "package.json") !== null) && (withSkills || readJson(root, SKILLS_MANIFEST) !== null);
|
|
47
50
|
}
|
|
48
51
|
/**
|
|
49
52
|
* What the repo has of skills-manager, and the step that raises it, if any. The step reads
|
|
50
53
|
* package.json again when it runs.
|
|
51
54
|
* @param root - The repo.
|
|
52
55
|
* @param withSkills - Whether this run may install skills.
|
|
56
|
+
* @param flowComing - Whether this run installs the flow, which makes package.json if missing.
|
|
53
57
|
* @returns The piece's status, or null where the piece does not apply.
|
|
54
|
-
* @example skillsManagerStatus(root, true)?.state // "1.1.0, bajo 1.2.0"
|
|
58
|
+
* @example skillsManagerStatus(root, true, false)?.state // "1.1.0, bajo 1.2.0"
|
|
55
59
|
*/
|
|
56
|
-
export function skillsManagerStatus(root, withSkills) {
|
|
57
|
-
if (!applies(root, withSkills))
|
|
60
|
+
export function skillsManagerStatus(root, withSkills, flowComing) {
|
|
61
|
+
if (!applies(root, withSkills, flowComing))
|
|
58
62
|
return null;
|
|
59
63
|
const summary = "el CLI de las skills, que corre agent-kitchen";
|
|
64
|
+
if (readJson(root, "package.json") === null) {
|
|
65
|
+
const make = {
|
|
66
|
+
piece: NAME,
|
|
67
|
+
checked: true,
|
|
68
|
+
downloads: [`${SKILLS_MANAGER}@${MIN_VERSION}`],
|
|
69
|
+
text: { kind: "skills-manager-create", version: MIN_VERSION },
|
|
70
|
+
run: async (context) => {
|
|
71
|
+
if (readJson(context.root, "package.json") === null)
|
|
72
|
+
writeJson(context.root, "package.json", { private: true, devDependencies: { [SKILLS_MANAGER]: MIN_VERSION } });
|
|
73
|
+
else if (pinned(context.root).kind === "missing")
|
|
74
|
+
setDependency(context.root, SKILLS_MANAGER, MIN_VERSION);
|
|
75
|
+
context.note(`${SKILLS_MANAGER} ${MIN_VERSION} en package.json`);
|
|
76
|
+
return true;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
return { name: NAME, summary, installed: false, state: "no hay package.json", steps: [make] };
|
|
80
|
+
}
|
|
60
81
|
const now = pinned(root);
|
|
61
82
|
if (now.kind === "other") {
|
|
62
83
|
const old = now.runs !== null && below(now.runs) ? `; corre ${now.runs}, bajo ${MIN_VERSION}` : "";
|
|
@@ -64,13 +85,13 @@ export function skillsManagerStatus(root, withSkills) {
|
|
|
64
85
|
}
|
|
65
86
|
if (now.kind === "exact" && !below(now.version))
|
|
66
87
|
return { name: NAME, summary, installed: true, state: `${now.version}, al día`, steps: [] };
|
|
67
|
-
const
|
|
88
|
+
const text = now.kind === "missing" ? { kind: "skills-manager-add", version: MIN_VERSION } : { kind: "skills-manager-bump", from: now.version, to: MIN_VERSION };
|
|
68
89
|
const state = now.kind === "missing" ? "no está en package.json" : `${now.version}, bajo ${MIN_VERSION}`;
|
|
69
90
|
const step = {
|
|
70
91
|
piece: NAME,
|
|
71
92
|
checked: true,
|
|
72
93
|
downloads: [`${SKILLS_MANAGER}@${MIN_VERSION}`],
|
|
73
|
-
|
|
94
|
+
text,
|
|
74
95
|
run: async (context) => {
|
|
75
96
|
const current = pinned(context.root);
|
|
76
97
|
if (current.kind === "missing" || (current.kind === "exact" && below(current.version))) {
|
|
@@ -91,7 +112,7 @@ export function skillsManagerStatus(root, withSkills) {
|
|
|
91
112
|
* @example skillsManagerChecks(root)[0]?.status // "ok"
|
|
92
113
|
*/
|
|
93
114
|
export function skillsManagerChecks(root) {
|
|
94
|
-
if (!applies(root, false))
|
|
115
|
+
if (!applies(root, false, false))
|
|
95
116
|
return [];
|
|
96
117
|
const now = pinned(root);
|
|
97
118
|
if (now.kind === "missing")
|
package/dist/pieces/skills.js
CHANGED
|
@@ -174,7 +174,7 @@ async function updateStep(root, catalog, ref) {
|
|
|
174
174
|
piece: NAME,
|
|
175
175
|
checked: true,
|
|
176
176
|
downloads: [skillsManagerSpec(root)],
|
|
177
|
-
|
|
177
|
+
text: { kind: "skills-update", catalog, from, to: latest, names: behind.map((skill) => skill.name) },
|
|
178
178
|
run: async (context) => {
|
|
179
179
|
if (ref !== null)
|
|
180
180
|
pinCatalog(context.root, catalog, `v${latest}`);
|
|
@@ -215,10 +215,9 @@ export async function skillsStatus(root, withFlow) {
|
|
|
215
215
|
});
|
|
216
216
|
const offered = candidates ?? fallback;
|
|
217
217
|
const catalogs = [...new Set(offered.map((candidate) => candidate.catalog))];
|
|
218
|
-
const label = `Instalar ${offered.length} skills de ${catalogs.join(" y ")}`;
|
|
219
218
|
const skills = { candidates: offered, picked: offered.map((candidate) => candidate.id), replace: [] };
|
|
220
219
|
const state = candidates === null ? "no instaladas (no se pudieron leer los catálogos)" : "no instaladas";
|
|
221
|
-
return { name: NAME, summary, installed: false, state, steps: [{ piece: NAME, checked: true, downloads: [],
|
|
220
|
+
return { name: NAME, summary, installed: false, state, steps: [{ piece: NAME, checked: true, downloads: [], text: { kind: "skills-install", count: offered.length, catalogs }, skills, run: (context) => install(context, skills) }] };
|
|
222
221
|
}
|
|
223
222
|
const general = manifestCatalogs(root).filter((source) => source.catalog !== FLOW_CATALOG);
|
|
224
223
|
const steps = (await Promise.all(general.map((source) => updateStep(root, source.catalog, source.ref)))).flatMap((step) => step ?? []);
|
|
@@ -227,7 +226,7 @@ export async function skillsStatus(root, withFlow) {
|
|
|
227
226
|
const fresh = (candidates ?? []).filter((candidate) => locked[candidate.name] === undefined);
|
|
228
227
|
if (fresh.length > 0) {
|
|
229
228
|
const skills = { candidates: fresh, picked: [], replace: [] };
|
|
230
|
-
steps.push({ piece: NAME, checked: false, downloads: [skillsManagerSpec(root)],
|
|
229
|
+
steps.push({ piece: NAME, checked: false, downloads: [skillsManagerSpec(root)], text: { kind: "skills-add", names: fresh.map((candidate) => candidate.name) }, skills, run: (context) => install(context, skills) });
|
|
231
230
|
}
|
|
232
231
|
const count = manifestCatalogs(root).reduce((total, source) => total + catalogSkills(root, source.catalog).length, 0);
|
|
233
232
|
const behind = steps.filter((step) => step.checked).length;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Output } from "./output.js";
|
|
2
|
+
import type { StepText } from "./texts/step-text.js";
|
|
3
|
+
/** How a step's progress is shown. */
|
|
4
|
+
export interface Progress {
|
|
5
|
+
start: (text: StepText) => void;
|
|
6
|
+
stop: (text: StepText, ok: boolean) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Progress as plain lines: the start, a line now and then while it runs, and the end.
|
|
10
|
+
* @param out - Where to write.
|
|
11
|
+
* @param options - Whether to print while it runs, how often, and the clock.
|
|
12
|
+
* @returns The progress.
|
|
13
|
+
* @example plainProgress(out, { ticks: true }).start({ kind: "guard-adjust" })
|
|
14
|
+
*/
|
|
15
|
+
export declare function plainProgress(out: Output, options: {
|
|
16
|
+
ticks: boolean;
|
|
17
|
+
tickMs?: number;
|
|
18
|
+
now?: () => number;
|
|
19
|
+
}): Progress;
|
|
20
|
+
/**
|
|
21
|
+
* How progress is shown: a spinner in a terminal, plain lines otherwise or when `plain` asks
|
|
22
|
+
* for them. Only for the steps, after every question: a spinner shown before a question
|
|
23
|
+
* leaves the second question deaf to the keyboard (@clack/prompts 1.8.1).
|
|
24
|
+
* @param out - Where to write.
|
|
25
|
+
* @param verbose - Whether the tools print on the terminal.
|
|
26
|
+
* @param plain - Whether a question may come after the steps (`--commit`).
|
|
27
|
+
* @returns The progress.
|
|
28
|
+
*/
|
|
29
|
+
export declare function progressFor(out: Output, verbose: boolean, plain: boolean): Progress;
|
package/dist/progress.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file How a step's progress is shown. Its start says what is happening ("Actualizando…") and
|
|
3
|
+
* its end what is left, with how long it took. In a terminal, a spinner; otherwise plain
|
|
4
|
+
* lines, and a step that takes a while prints how long it has been running, so it does not
|
|
5
|
+
* look stuck (unless the tools print their own output, with `--verbose`).
|
|
6
|
+
*/
|
|
7
|
+
import { spinner } from "@clack/prompts";
|
|
8
|
+
import { phrases, ui } from "./texts/index.js";
|
|
9
|
+
/** How often a plain step says it is still running. */
|
|
10
|
+
const TICK_MS = 10_000;
|
|
11
|
+
/**
|
|
12
|
+
* The end line of a step.
|
|
13
|
+
* @param text - What the step does.
|
|
14
|
+
* @param ok - Whether it worked.
|
|
15
|
+
* @param ms - How long it took.
|
|
16
|
+
* @returns The line, without the mark.
|
|
17
|
+
*/
|
|
18
|
+
function endLine(text, ok, ms) {
|
|
19
|
+
const said = phrases(text);
|
|
20
|
+
return `${ok ? said.done : ui.failed(said.menu)} (${ui.duration(ms)})`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Progress as plain lines: the start, a line now and then while it runs, and the end.
|
|
24
|
+
* @param out - Where to write.
|
|
25
|
+
* @param options - Whether to print while it runs, how often, and the clock.
|
|
26
|
+
* @returns The progress.
|
|
27
|
+
* @example plainProgress(out, { ticks: true }).start({ kind: "guard-adjust" })
|
|
28
|
+
*/
|
|
29
|
+
export function plainProgress(out, options) {
|
|
30
|
+
const now = options.now ?? Date.now;
|
|
31
|
+
let started = 0;
|
|
32
|
+
let timer;
|
|
33
|
+
return {
|
|
34
|
+
start: (text) => {
|
|
35
|
+
started = now();
|
|
36
|
+
out.stdout(`${phrases(text).doing}\n`);
|
|
37
|
+
if (options.ticks)
|
|
38
|
+
timer = setInterval(() => out.stdout(`${ui.still(ui.duration(now() - started))}\n`), options.tickMs ?? TICK_MS);
|
|
39
|
+
},
|
|
40
|
+
stop: (text, ok) => {
|
|
41
|
+
clearInterval(timer);
|
|
42
|
+
timer = undefined;
|
|
43
|
+
out.stdout(`${ok ? "✓" : "✗"} ${endLine(text, ok, now() - started)}\n`);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* How progress is shown: a spinner in a terminal, plain lines otherwise or when `plain` asks
|
|
49
|
+
* for them. Only for the steps, after every question: a spinner shown before a question
|
|
50
|
+
* leaves the second question deaf to the keyboard (@clack/prompts 1.8.1).
|
|
51
|
+
* @param out - Where to write.
|
|
52
|
+
* @param verbose - Whether the tools print on the terminal.
|
|
53
|
+
* @param plain - Whether a question may come after the steps (`--commit`).
|
|
54
|
+
* @returns The progress.
|
|
55
|
+
*/
|
|
56
|
+
export function progressFor(out, verbose, plain) {
|
|
57
|
+
if (out.interactive && !verbose && !plain) {
|
|
58
|
+
const spin = spinner();
|
|
59
|
+
let started = 0;
|
|
60
|
+
return {
|
|
61
|
+
start: (text) => {
|
|
62
|
+
started = Date.now();
|
|
63
|
+
spin.start(phrases(text).doing);
|
|
64
|
+
},
|
|
65
|
+
stop: (text, ok) => {
|
|
66
|
+
const line = endLine(text, ok, Date.now() - started);
|
|
67
|
+
if (ok)
|
|
68
|
+
spin.stop(line);
|
|
69
|
+
else
|
|
70
|
+
spin.error(line);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return plainProgress(out, { ticks: !verbose });
|
|
75
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The shape of a catalog of the texts the person sees while `apply` runs its steps.
|
|
3
|
+
* Every language implements `Catalog`; the compiler then asks for every kind of step and every
|
|
4
|
+
* message. The interface uses `ui`; the commit and the PR do not (see commit-texts.ts).
|
|
5
|
+
*/
|
|
6
|
+
import type { StepKind, StepOf } from "./step-text.js";
|
|
7
|
+
/** One step's texts, one per place it shows. */
|
|
8
|
+
export interface StepPhrases {
|
|
9
|
+
/** In the menu and the lists of steps, as an order: "Actualizar issue-flow 0.2.7 → 0.3.0". */
|
|
10
|
+
menu: string;
|
|
11
|
+
/** While it runs: "Actualizando issue-flow de 0.2.7 a 0.3.0…". */
|
|
12
|
+
doing: string;
|
|
13
|
+
/** When it worked, saying what is left: "issue-flow actualizado de 0.2.7 a 0.3.0". */
|
|
14
|
+
done: string;
|
|
15
|
+
}
|
|
16
|
+
/** Every text of `apply` around its steps. */
|
|
17
|
+
export interface Catalog {
|
|
18
|
+
/** Each kind of step's texts. */
|
|
19
|
+
steps: {
|
|
20
|
+
[K in StepKind]: (step: StepOf<K>) => StepPhrases;
|
|
21
|
+
};
|
|
22
|
+
/** Added to a step of a piece the repo has not decided on yet. */
|
|
23
|
+
fresh: string;
|
|
24
|
+
/** The end line of a step that failed, from its menu text. */
|
|
25
|
+
failed: (menu: string) => string;
|
|
26
|
+
/** A duration, short: "0,4 s", "12 s", "1 min 5 s". */
|
|
27
|
+
duration: (ms: number) => string;
|
|
28
|
+
/** The line a long step prints while it runs, so it does not look stuck. */
|
|
29
|
+
still: (elapsed: string) => string;
|
|
30
|
+
/** The line before looking at the pieces. */
|
|
31
|
+
checking: string;
|
|
32
|
+
/** Nothing to install or update. */
|
|
33
|
+
upToDate: string;
|
|
34
|
+
/** With --yes and nothing suggested: the optional steps. */
|
|
35
|
+
onlyOptional: (menus: string[]) => string;
|
|
36
|
+
/** The heading of the steps listed without running them. */
|
|
37
|
+
suggested: (dryRun: boolean) => string;
|
|
38
|
+
/** The last line when every step worked or some failed. */
|
|
39
|
+
summary: (failed: string[]) => string;
|
|
40
|
+
/** What is left to do by hand after the steps, without --commit. */
|
|
41
|
+
next: (manager: string) => string;
|
|
42
|
+
/** A new piece left out because no one was asked about it. */
|
|
43
|
+
undecided: (names: string[]) => string;
|
|
44
|
+
/** The repo's own skills left out of a pick. */
|
|
45
|
+
ownSkillsKept: (names: string[]) => string;
|
|
46
|
+
/** The decisions were saved. */
|
|
47
|
+
saved: (file: string, commits: boolean) => string;
|
|
48
|
+
/** The closing line when no step ran: the person left, or picked none. */
|
|
49
|
+
noSteps: (why: "leave" | "none", wrote: boolean) => string;
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The texts in Spanish. The menu orders ("Actualizar"), progress says what is happening
|
|
3
|
+
* ("Actualizando") and the end line says what is left ("actualizado").
|
|
4
|
+
*/
|
|
5
|
+
import type { Catalog } from "./catalog.js";
|
|
6
|
+
/** The texts in Spanish. */
|
|
7
|
+
export declare const es: Catalog;
|