agent-kitchen 0.2.0 → 0.3.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 +12 -8
- package/dist/cli.js +33 -18
- package/dist/commands/doctor.d.ts +2 -2
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/sync.d.ts +2 -1
- package/dist/commands/sync.js +183 -51
- package/dist/menu.d.ts +36 -1
- package/dist/menu.js +74 -5
- package/dist/piece.d.ts +26 -6
- package/dist/piece.js +7 -0
- package/dist/pieces/cloud.d.ts +13 -4
- package/dist/pieces/cloud.js +22 -27
- package/dist/pieces/guard.d.ts +17 -5
- package/dist/pieces/guard.js +28 -36
- package/dist/pieces/issue-flow.d.ts +11 -4
- package/dist/pieces/issue-flow.js +35 -31
- package/dist/pieces/skills.d.ts +45 -5
- package/dist/pieces/skills.js +171 -75
- package/dist/repo.js +4 -4
- package/dist/skills-files.d.ts +16 -3
- package/dist/skills-files.js +30 -7
- package/dist/tools.d.ts +51 -22
- package/dist/tools.js +118 -28
- package/package.json +1 -1
package/dist/skills-files.js
CHANGED
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
* @file What agent-kitchen reads of the files of `skills-manager`: which skills of a catalog
|
|
3
3
|
* the lock has and at which tag (the lock is where the manifest's `paths.lock` says), the
|
|
4
4
|
* catalog's source in the manifest, which `skills-manager` to run (the one the repo pins,
|
|
5
|
-
* or the latest 1.x),
|
|
6
|
-
* takes a single name.
|
|
5
|
+
* or the latest 1.x), the repo's own skills (folders the lock does not know), and updating
|
|
6
|
+
* skills one by one, because `skills-manager update` takes a single name.
|
|
7
7
|
*/
|
|
8
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
8
10
|
import { dependencyVersion, isObject, readJson, writeJson } from "./repo.js";
|
|
9
11
|
import { SKILLS_LOCK, SKILLS_MANAGER, SKILLS_MANIFEST } from "./piece.js";
|
|
10
12
|
import { runNpx } from "./tools.js";
|
|
13
|
+
/** Where `skills-manager` puts the skills when the manifest does not say. */
|
|
14
|
+
const SKILLS_DIR = ".agents/skills";
|
|
11
15
|
/**
|
|
12
16
|
* Where the repo's skills lock is: the manifest's `paths.lock`, or the default.
|
|
13
17
|
* @param root - The repo.
|
|
@@ -32,13 +36,32 @@ export function lockedSkills(root) {
|
|
|
32
36
|
* Runs `skills-manager update <name>` for each skill, stopping at the first that fails.
|
|
33
37
|
* @param root - The repo.
|
|
34
38
|
* @param names - The skills.
|
|
35
|
-
* @returns
|
|
36
|
-
* @example updateSkills(root, ["close-pr", "pr-review-comments"]) // null
|
|
39
|
+
* @returns Null if every one worked, or the command that failed, to retry it, and what it printed.
|
|
40
|
+
* @example await updateSkills(root, ["close-pr", "pr-review-comments"]) // null
|
|
37
41
|
*/
|
|
38
|
-
export function updateSkills(root, names) {
|
|
42
|
+
export async function updateSkills(root, names) {
|
|
39
43
|
const spec = skillsManagerSpec(root);
|
|
40
|
-
const
|
|
41
|
-
|
|
44
|
+
for (const name of names) {
|
|
45
|
+
const run = await runNpx(root, [spec, "update", name]);
|
|
46
|
+
if (!run.ok)
|
|
47
|
+
return { command: `npx ${spec} update ${name}`, run };
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The repo's own skills among some names: a folder in the skills folder that the lock does
|
|
53
|
+
* not know, which `skills-manager` replaces only when allowed.
|
|
54
|
+
* @param root - The repo.
|
|
55
|
+
* @param names - The skills about to be installed.
|
|
56
|
+
* @returns The names that are the repo's own.
|
|
57
|
+
* @example ownSkills(root, ["planning", "code-audit"]) // ["planning"]
|
|
58
|
+
*/
|
|
59
|
+
export function ownSkills(root, names) {
|
|
60
|
+
const paths = readJson(root, SKILLS_MANIFEST)?.paths;
|
|
61
|
+
const dir = join(root, isObject(paths) && typeof paths.skillsDir === "string" && paths.skillsDir !== "" ? paths.skillsDir : SKILLS_DIR);
|
|
62
|
+
const folders = existsSync(dir) ? readdirSync(dir) : [];
|
|
63
|
+
const locked = lockedSkills(root);
|
|
64
|
+
return [...new Set(names)].filter((name) => folders.includes(name) && locked[name] === undefined);
|
|
42
65
|
}
|
|
43
66
|
/**
|
|
44
67
|
* The skills of a catalog in the repo's lock.
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shows the tools' output as it comes instead of only when they fail.
|
|
3
|
+
* @param value - True for `--verbose`.
|
|
4
|
+
* @example setVerbose(true)
|
|
5
|
+
*/
|
|
6
|
+
export declare function setVerbose(value: boolean): void;
|
|
1
7
|
/**
|
|
2
8
|
* Compares two release versions.
|
|
3
9
|
* @param left - A version like 0.2.0.
|
|
@@ -14,35 +20,58 @@ export declare function compareVersions(left: string, right: string): number;
|
|
|
14
20
|
*/
|
|
15
21
|
export declare function isRelease(version: string): boolean;
|
|
16
22
|
/**
|
|
17
|
-
*
|
|
23
|
+
* The latest version of a package on npm.
|
|
24
|
+
* @param name - The package.
|
|
25
|
+
* @returns The version, or null if npm cannot tell.
|
|
26
|
+
* @example await npmLatest("@ramon-sg/claude-cloud") // "1.0.0"
|
|
27
|
+
*/
|
|
28
|
+
export declare function npmLatest(name: string): Promise<string | null>;
|
|
29
|
+
/**
|
|
30
|
+
* The highest `vX.Y.Z` tag of a GitHub repo.
|
|
31
|
+
* @param repo - The repo, as owner/name.
|
|
32
|
+
* @returns The version without the `v`, or null if it has none or git cannot reach it.
|
|
33
|
+
* @example await latestTag("ramon-sg/agent-skills") // "0.1.0"
|
|
34
|
+
*/
|
|
35
|
+
export declare function latestTag(repo: string): Promise<string | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Downloads a package into npx's cache without running it, once per package, so its step
|
|
38
|
+
* starts at once. A failed download is ignored: the step itself will say what failed.
|
|
18
39
|
* @param root - The repo.
|
|
19
|
-
* @param
|
|
20
|
-
* @returns
|
|
21
|
-
* @example
|
|
40
|
+
* @param spec - The package, as npx takes it.
|
|
41
|
+
* @returns When it is done.
|
|
42
|
+
* @example void download(root, "@ramon-sg/claude-cloud@1.0.0")
|
|
22
43
|
*/
|
|
23
|
-
export declare function
|
|
44
|
+
export declare function download(root: string, spec: string): Promise<void>;
|
|
45
|
+
/** How a tool ended, and what it printed if it was kept. */
|
|
46
|
+
export interface ToolRun {
|
|
47
|
+
ok: boolean;
|
|
48
|
+
output: string;
|
|
49
|
+
}
|
|
24
50
|
/**
|
|
25
|
-
* Runs `npx --yes <args>` in the repo
|
|
51
|
+
* Runs `npx --yes <args>` in the repo: quietly, or with its output on the terminal with
|
|
52
|
+
* `--verbose`. It must not ask anything: the person answered before the steps ran.
|
|
26
53
|
* @param root - The repo.
|
|
27
54
|
* @param args - The package and its arguments.
|
|
28
|
-
* @returns
|
|
29
|
-
* @example
|
|
55
|
+
* @returns Whether it exited 0, and what it printed.
|
|
56
|
+
* @example await runNpx(root, ["@ramon-sg/claude-cloud@1.0.0", "init"])
|
|
30
57
|
*/
|
|
31
|
-
export declare function
|
|
32
|
-
out: string;
|
|
33
|
-
status: number | null;
|
|
34
|
-
} | null;
|
|
58
|
+
export declare function runNpx(root: string, args: string[]): Promise<ToolRun>;
|
|
35
59
|
/**
|
|
36
|
-
* The
|
|
37
|
-
* @param
|
|
38
|
-
* @returns
|
|
39
|
-
* @example
|
|
60
|
+
* The end of a failed tool's output, indented, to show under its step.
|
|
61
|
+
* @param output - What it printed.
|
|
62
|
+
* @returns Its last lines, or an empty text.
|
|
63
|
+
* @example failedOutput("npm error 404\n") // " npm error 404\n"
|
|
40
64
|
*/
|
|
41
|
-
export declare function
|
|
65
|
+
export declare function failedOutput(output: string): string;
|
|
42
66
|
/**
|
|
43
|
-
*
|
|
44
|
-
* @param
|
|
45
|
-
* @
|
|
46
|
-
* @
|
|
67
|
+
* Runs `npx --yes <args>` in the repo and returns what it printed on stdout.
|
|
68
|
+
* @param root - The repo.
|
|
69
|
+
* @param args - The package and its arguments.
|
|
70
|
+
* @param lookup - True to give up after the lookups' time limit, for a read that only informs.
|
|
71
|
+
* @returns Its stdout and exit code, or null if npx could not start.
|
|
72
|
+
* @example await captureNpx(root, ["@ramon-sg/issue-flow@0.2.0", "doctor", "--json"])
|
|
47
73
|
*/
|
|
48
|
-
export declare function
|
|
74
|
+
export declare function captureNpx(root: string, args: string[], lookup?: boolean): Promise<{
|
|
75
|
+
out: string;
|
|
76
|
+
status: number | null;
|
|
77
|
+
} | null>;
|
package/dist/tools.js
CHANGED
|
@@ -2,16 +2,36 @@
|
|
|
2
2
|
* @file How agent-kitchen reaches the other tools and their releases: `npx` to run them (always
|
|
3
3
|
* by their scoped name or GitHub spec, because the unscoped names on npm belong to other
|
|
4
4
|
* people), `npm view` for the latest version on npm, and `git ls-remote` for the latest
|
|
5
|
-
* `vX.Y.Z` tag on GitHub.
|
|
6
|
-
*
|
|
5
|
+
* `vX.Y.Z` tag on GitHub. Everything is asynchronous so the lookups run at the same time,
|
|
6
|
+
* and each lookup runs once per process. A lookup that fails, or takes over 20 seconds,
|
|
7
|
+
* gives null: the piece is shown, not offered. git never asks for credentials here.
|
|
8
|
+
*
|
|
9
|
+
* The tools run quietly: their output is kept and shown only when they fail, so the person
|
|
10
|
+
* reads one line per step. `--verbose` shows it as it comes.
|
|
7
11
|
*/
|
|
8
|
-
import {
|
|
12
|
+
import { spawn } from "node:child_process";
|
|
9
13
|
/** A release version. */
|
|
10
14
|
const VERSION = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
11
15
|
/** How long a lookup of the latest version may take before it counts as unknown. */
|
|
12
16
|
const LOOKUP_TIMEOUT_MS = 20_000;
|
|
13
17
|
/** A release tag in `git ls-remote --tags --refs` output. */
|
|
14
18
|
const TAG_REF = /refs\/tags\/v(\d+\.\d+\.\d+)$/;
|
|
19
|
+
/** How many lines of a failed tool's output are shown. */
|
|
20
|
+
const FAILED_LINES = 25;
|
|
21
|
+
/** Whether the tools' output goes straight to the terminal. */
|
|
22
|
+
let verbose = false;
|
|
23
|
+
/** Lookups already started, by what they look up. */
|
|
24
|
+
const lookups = new Map();
|
|
25
|
+
/** Downloads already started, by package. */
|
|
26
|
+
const downloads = new Map();
|
|
27
|
+
/**
|
|
28
|
+
* Shows the tools' output as it comes instead of only when they fail.
|
|
29
|
+
* @param value - True for `--verbose`.
|
|
30
|
+
* @example setVerbose(true)
|
|
31
|
+
*/
|
|
32
|
+
export function setVerbose(value) {
|
|
33
|
+
verbose = value;
|
|
34
|
+
}
|
|
15
35
|
/**
|
|
16
36
|
* Compares two release versions.
|
|
17
37
|
* @param left - A version like 0.2.0.
|
|
@@ -39,47 +59,117 @@ export function isRelease(version) {
|
|
|
39
59
|
return VERSION.test(version);
|
|
40
60
|
}
|
|
41
61
|
/**
|
|
42
|
-
* Runs
|
|
43
|
-
* @param
|
|
44
|
-
* @param args -
|
|
45
|
-
* @
|
|
46
|
-
* @
|
|
62
|
+
* Runs a program and collects what it prints.
|
|
63
|
+
* @param program - The program.
|
|
64
|
+
* @param args - Its arguments.
|
|
65
|
+
* @param options - The folder, a time limit, extra variables, and whether its output goes to the terminal.
|
|
66
|
+
* @returns How it ended; status null if it could not start or ran out of time.
|
|
47
67
|
*/
|
|
48
|
-
|
|
49
|
-
return
|
|
68
|
+
function run(program, args, options = {}) {
|
|
69
|
+
return new Promise((resolve) => {
|
|
70
|
+
const child = spawn(program, args, { cwd: options.cwd, env: { ...process.env, ...options.env }, stdio: options.inherit === true ? "inherit" : ["ignore", "pipe", "pipe"] });
|
|
71
|
+
let out = "";
|
|
72
|
+
let err = "";
|
|
73
|
+
child.stdout?.on("data", (chunk) => (out += chunk.toString()));
|
|
74
|
+
child.stderr?.on("data", (chunk) => (err += chunk.toString()));
|
|
75
|
+
const timer = options.timeout === undefined ? undefined : setTimeout(() => child.kill(), options.timeout);
|
|
76
|
+
child.on("error", () => resolve({ status: null, out, err }));
|
|
77
|
+
child.on("close", (code, signal) => {
|
|
78
|
+
if (timer !== undefined)
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
resolve({ status: signal === null ? code : null, out, err });
|
|
81
|
+
});
|
|
82
|
+
});
|
|
50
83
|
}
|
|
51
84
|
/**
|
|
52
|
-
* Runs
|
|
53
|
-
* @param
|
|
54
|
-
* @param
|
|
55
|
-
* @returns Its
|
|
56
|
-
* @example captureNpx(root, ["@ramon-sg/issue-flow@0.2.0", "doctor", "--json"])
|
|
85
|
+
* Runs a lookup once: later calls get the same promise.
|
|
86
|
+
* @param key - What it looks up.
|
|
87
|
+
* @param lookup - How.
|
|
88
|
+
* @returns Its result.
|
|
57
89
|
*/
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
90
|
+
function once(key, lookup) {
|
|
91
|
+
const known = lookups.get(key);
|
|
92
|
+
if (known !== undefined)
|
|
93
|
+
return known;
|
|
94
|
+
const started = lookup();
|
|
95
|
+
lookups.set(key, started);
|
|
96
|
+
return started;
|
|
61
97
|
}
|
|
62
98
|
/**
|
|
63
99
|
* The latest version of a package on npm.
|
|
64
100
|
* @param name - The package.
|
|
65
101
|
* @returns The version, or null if npm cannot tell.
|
|
66
|
-
* @example npmLatest("@ramon-sg/claude-cloud") // "1.0.0"
|
|
102
|
+
* @example await npmLatest("@ramon-sg/claude-cloud") // "1.0.0"
|
|
67
103
|
*/
|
|
68
104
|
export function npmLatest(name) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
105
|
+
return once(`npm:${name}`, async () => {
|
|
106
|
+
const result = await run("npm", ["view", name, "version", "--fetch-retries=0"], { timeout: LOOKUP_TIMEOUT_MS });
|
|
107
|
+
const version = result.status === 0 ? result.out.trim() : "";
|
|
108
|
+
return isRelease(version) ? version : null;
|
|
109
|
+
});
|
|
72
110
|
}
|
|
73
111
|
/**
|
|
74
112
|
* The highest `vX.Y.Z` tag of a GitHub repo.
|
|
75
113
|
* @param repo - The repo, as owner/name.
|
|
76
114
|
* @returns The version without the `v`, or null if it has none or git cannot reach it.
|
|
77
|
-
* @example latestTag("ramon-sg/agent-skills") // "0.1.0"
|
|
115
|
+
* @example await latestTag("ramon-sg/agent-skills") // "0.1.0"
|
|
78
116
|
*/
|
|
79
117
|
export function latestTag(repo) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
118
|
+
return once(`tag:${repo}`, async () => {
|
|
119
|
+
const result = await run("git", ["ls-remote", "--tags", "--refs", `https://github.com/${repo}.git`], { timeout: LOOKUP_TIMEOUT_MS, env: { GIT_TERMINAL_PROMPT: "0" } });
|
|
120
|
+
if (result.status !== 0)
|
|
121
|
+
return null;
|
|
122
|
+
const versions = result.out.split("\n").flatMap((line) => TAG_REF.exec(line.trim())?.slice(1, 2) ?? []);
|
|
123
|
+
return versions.sort(compareVersions).at(-1) ?? null;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Downloads a package into npx's cache without running it, once per package, so its step
|
|
128
|
+
* starts at once. A failed download is ignored: the step itself will say what failed.
|
|
129
|
+
* @param root - The repo.
|
|
130
|
+
* @param spec - The package, as npx takes it.
|
|
131
|
+
* @returns When it is done.
|
|
132
|
+
* @example void download(root, "@ramon-sg/claude-cloud@1.0.0")
|
|
133
|
+
*/
|
|
134
|
+
export function download(root, spec) {
|
|
135
|
+
const known = downloads.get(spec);
|
|
136
|
+
if (known !== undefined)
|
|
137
|
+
return known;
|
|
138
|
+
const started = run("npx", ["--yes", "--package", spec, "--", "node", "-e", ""], { cwd: root }).then(() => undefined);
|
|
139
|
+
downloads.set(spec, started);
|
|
140
|
+
return started;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Runs `npx --yes <args>` in the repo: quietly, or with its output on the terminal with
|
|
144
|
+
* `--verbose`. It must not ask anything: the person answered before the steps ran.
|
|
145
|
+
* @param root - The repo.
|
|
146
|
+
* @param args - The package and its arguments.
|
|
147
|
+
* @returns Whether it exited 0, and what it printed.
|
|
148
|
+
* @example await runNpx(root, ["@ramon-sg/claude-cloud@1.0.0", "init"])
|
|
149
|
+
*/
|
|
150
|
+
export async function runNpx(root, args) {
|
|
151
|
+
const result = await run("npx", ["--yes", ...args], { cwd: root, inherit: verbose });
|
|
152
|
+
return { ok: result.status === 0, output: `${result.out}${result.err}` };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The end of a failed tool's output, indented, to show under its step.
|
|
156
|
+
* @param output - What it printed.
|
|
157
|
+
* @returns Its last lines, or an empty text.
|
|
158
|
+
* @example failedOutput("npm error 404\n") // " npm error 404\n"
|
|
159
|
+
*/
|
|
160
|
+
export function failedOutput(output) {
|
|
161
|
+
const lines = output.trimEnd().split("\n").filter((line) => line.trim() !== "");
|
|
162
|
+
return lines.slice(-FAILED_LINES).map((line) => ` ${line}\n`).join("");
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Runs `npx --yes <args>` in the repo and returns what it printed on stdout.
|
|
166
|
+
* @param root - The repo.
|
|
167
|
+
* @param args - The package and its arguments.
|
|
168
|
+
* @param lookup - True to give up after the lookups' time limit, for a read that only informs.
|
|
169
|
+
* @returns Its stdout and exit code, or null if npx could not start.
|
|
170
|
+
* @example await captureNpx(root, ["@ramon-sg/issue-flow@0.2.0", "doctor", "--json"])
|
|
171
|
+
*/
|
|
172
|
+
export async function captureNpx(root, args, lookup = false) {
|
|
173
|
+
const result = await run("npx", ["--yes", ...args], { cwd: root, ...(lookup ? { timeout: LOOKUP_TIMEOUT_MS } : {}) });
|
|
174
|
+
return result.status === null && result.out === "" ? null : { out: result.out, status: result.status };
|
|
85
175
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-kitchen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "One command to install, update and check a repo's agent tooling: the issue flow and its skills, the general skills, Claude cloud sessions and claude-repo-guard.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|