@falkenslab/agent-kit 0.20.0 → 0.22.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/assets/extension-launcher.mjs +37 -7
- package/dist/chat/chatController.d.ts +59 -0
- package/dist/chat/chatController.js +209 -2
- package/dist/chat/extensions.d.ts +30 -2
- package/dist/chat/extensions.js +63 -4
- package/dist/chat/runs.d.ts +3 -1
- package/dist/chat/runs.js +1 -1
- package/dist/core/agentSpec.d.ts +11 -0
- package/dist/core/elicitation.d.ts +54 -0
- package/dist/core/elicitation.js +120 -0
- package/dist/core/extensionCatalog.d.ts +94 -0
- package/dist/core/extensionCatalog.js +228 -0
- package/dist/core/extensionOptions.d.ts +56 -0
- package/dist/core/extensionOptions.js +139 -0
- package/dist/core/extensions.d.ts +9 -1
- package/dist/core/extensions.js +2 -1
- package/dist/core/externalExtensions.d.ts +116 -13
- package/dist/core/externalExtensions.js +280 -34
- package/dist/core/hooks/planGate.d.ts +2 -0
- package/dist/core/hooks/planGate.js +3 -0
- package/dist/core/interaction.d.ts +7 -0
- package/dist/core/liveExtensions.d.ts +30 -5
- package/dist/core/liveExtensions.js +78 -5
- package/dist/core/marketplaces.d.ts +6 -2
- package/dist/core/marketplaces.js +6 -4
- package/dist/core/messages/de.js +33 -1
- package/dist/core/messages/en.d.ts +36 -0
- package/dist/core/messages/en.js +33 -1
- package/dist/core/messages/es.js +33 -1
- package/dist/core/messages/fr.js +33 -1
- package/dist/core/npxPackages.d.ts +48 -0
- package/dist/core/npxPackages.js +121 -0
- package/dist/core/pluginDownloads.d.ts +2 -1
- package/dist/core/pluginDownloads.js +2 -2
- package/dist/core/readOnlyHints.d.ts +13 -0
- package/dist/core/readOnlyHints.js +26 -0
- package/dist/core/runner.js +2 -0
- package/dist/core/session.d.ts +13 -1
- package/dist/core/session.js +44 -20
- package/dist/core/sessionFacts.d.ts +5 -1
- package/dist/core/workspaceTrust.d.ts +12 -0
- package/dist/core/workspaceTrust.js +60 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +5 -1
- package/dist/tui/extensionCommand.d.ts +13 -3
- package/dist/tui/extensionCommand.js +214 -37
- package/extensions/awareness/.claude-plugin/plugin.json +13 -5
- package/extensions/knowledge/.claude-plugin/plugin.json +13 -5
- package/extensions/memory/.claude-plugin/plugin.json +13 -5
- package/extensions/sources/.claude-plugin/plugin.json +13 -5
- package/package.json +7 -5
- package/schemas/agent-kit-metadata.schema.json +62 -0
- package/schemas/marketplace-catalog.schema.json +64 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createInterface } from "node:readline/promises";
|
|
4
|
-
import { addMarketplace, findPlugin, inspectMarketplace, installFromMarketplace,
|
|
5
|
-
import { addExtension, isGitSource, listInstalled, removeExtension, setExtensionEnabled, } from "../core/externalExtensions.js";
|
|
6
|
-
import { describe } from "../chat/extensions.js";
|
|
4
|
+
import { addMarketplace, findPlugin, inspectMarketplace, installFromMarketplace, removeMarketplace, updateMarketplace, } from "../core/marketplaces.js";
|
|
5
|
+
import { addExtension, extensionOptionStates, installTarget, folderSize, isGitSource, listInstalled, removeExtension, setExtensionEnabled, setExtensionOptions, } from "../core/externalExtensions.js";
|
|
6
|
+
import { describe, describeOptions } from "../chat/extensions.js";
|
|
7
|
+
import { extensionCatalog, knownMarketplaces as catalogMarketplaces, restoreExtensions, updateExtension } from "../core/extensionCatalog.js";
|
|
8
|
+
import { trustWorkspace } from "../core/workspaceTrust.js";
|
|
7
9
|
export { chatExtensionsCommand, describe } from "../chat/extensions.js";
|
|
8
10
|
/**
|
|
9
11
|
* The `extension` command an agent exposes in its own binary (#37), e.g. `captain extension add
|
|
@@ -13,8 +15,14 @@ export { chatExtensionsCommand, describe } from "../chat/extensions.js";
|
|
|
13
15
|
*
|
|
14
16
|
* - `extension list`
|
|
15
17
|
* - `extension info <name>`: its metadata, what it offers and where its README is
|
|
16
|
-
* - `extension add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>] [--yes]`
|
|
17
|
-
* - `extension
|
|
18
|
+
* - `extension add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>] [--set key=value…] [--yes]`: asks for its options (#55) unless `--set` gives them
|
|
19
|
+
* - `extension add <folder> --link [--project]`: registered where it is, to develop it (not copied, its files not checked)
|
|
20
|
+
* - `extension remove <name> [--project | --agent] [--yes | --keep-data]`: asks before deleting its data folder
|
|
21
|
+
* - `extension enable|disable <name> [--project | --agent]`
|
|
22
|
+
* - `extension config <name> [key=value… | key… | --unset key…] [--project | --agent]`: its options; `key` alone asks for it (without echo when it's a secret)
|
|
23
|
+
* - `extension update [name] [--yes]`: one installed from a marketplace (or all that have a newer version there) to the version it offers now, its data and options kept
|
|
24
|
+
* - `extension restore [--trust]`: gets the code a workspace's lock lists and this computer lacks (#57); `--trust` trusts it too
|
|
25
|
+
* - `extension trust [name…]`: lets a workspace's extensions run, as their code is now
|
|
18
26
|
* - `extension search [words]`: what the known marketplaces offer
|
|
19
27
|
* - `extension marketplace add <folder | git URL[#ref] | owner/repo> [--yes]`, `list`, `update [name]`, `remove <name>`
|
|
20
28
|
*
|
|
@@ -35,7 +43,8 @@ export async function runExtensionCommand(argv, options) {
|
|
|
35
43
|
const index = rest.indexOf(`--${name}`);
|
|
36
44
|
return index >= 0 ? rest[index + 1] : undefined;
|
|
37
45
|
};
|
|
38
|
-
const
|
|
46
|
+
const values = (name) => rest.flatMap((arg, index) => (rest[index - 1] === `--${name}` ? [arg] : []));
|
|
47
|
+
const positional = rest.filter((arg, index) => !arg.startsWith("--") && !["--path", "--set", "--unset"].includes(rest[index - 1]));
|
|
39
48
|
const askedScope = flag("project") ? "project" : flag("agent") ? "agent" : undefined;
|
|
40
49
|
const fail = (message) => {
|
|
41
50
|
write(message);
|
|
@@ -46,19 +55,41 @@ export async function runExtensionCommand(argv, options) {
|
|
|
46
55
|
// The marketplaces live with the agent's extensions (the project's when it has no scope of its own).
|
|
47
56
|
const marketsDir = options.dirs.agent ?? options.dirs.project;
|
|
48
57
|
const ask = options.ask ?? askOnTerminal;
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return
|
|
58
|
+
const askSecret = options.askSecret ?? (options.ask ? options.ask : askSecretOnTerminal);
|
|
59
|
+
/**
|
|
60
|
+
* After installing `name` into `dir`: its options from `--set`, then asked for each still
|
|
61
|
+
* without a value (on a terminal; none without one), saying which required ones are left.
|
|
62
|
+
*/
|
|
63
|
+
const configure = async (name, dir) => {
|
|
64
|
+
const installed = (await listInstalled(options.dirs)).find((extension) => extension.name === name && extension.scopeDir === dir);
|
|
65
|
+
const declared = installed?.manifest?.userConfig ?? {};
|
|
66
|
+
if (!installed || !Object.keys(declared).length)
|
|
67
|
+
return;
|
|
68
|
+
const typed = {};
|
|
69
|
+
for (const pair of values("set")) {
|
|
70
|
+
const at = pair.indexOf("=");
|
|
71
|
+
if (at <= 0)
|
|
72
|
+
throw new Error(`--set takes key=value: "${pair}"`);
|
|
73
|
+
typed[pair.slice(0, at)] = pair.slice(at + 1);
|
|
74
|
+
}
|
|
75
|
+
const states = await describeOptionStates(installed);
|
|
76
|
+
for (const [key, option] of Object.entries(declared)) {
|
|
77
|
+
if (key in typed || states.get(key))
|
|
78
|
+
continue;
|
|
79
|
+
const question = `${option.title}${option.required ? "" : " (optional)"}${option.default !== undefined ? ` [${String(option.default)}]` : ""}: ${option.description}\n ${key} = `;
|
|
80
|
+
const answer = (await (option.sensitive ? askSecret : ask)(question)).trim();
|
|
81
|
+
if (answer)
|
|
82
|
+
typed[key] = answer;
|
|
59
83
|
}
|
|
60
|
-
|
|
84
|
+
if (Object.keys(typed).length)
|
|
85
|
+
await setExtensionOptions(installed, name, typed);
|
|
86
|
+
const missing = Object.entries(declared).filter(([key, option]) => option.required && !(key in typed) && !states.get(key) && option.default === undefined).map(([key]) => key);
|
|
87
|
+
if (missing.length)
|
|
88
|
+
write(`It stays off until ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} set: \`${command} extension config ${name} ${missing[0]}\`.`);
|
|
61
89
|
};
|
|
90
|
+
const confirmed = async (question, accepts) => flag("yes") || accepts((await ask(question)).trim());
|
|
91
|
+
// The agent's own marketplace, known without asking; a folder is copied afresh each time (it's the agent's own code).
|
|
92
|
+
const knownMarketplaces = () => catalogMarketplaces(options.dirs, options.official);
|
|
62
93
|
try {
|
|
63
94
|
switch (action) {
|
|
64
95
|
case "list": {
|
|
@@ -90,8 +121,22 @@ export async function runExtensionCommand(argv, options) {
|
|
|
90
121
|
const dir = scopeDir(scope);
|
|
91
122
|
if (!dir)
|
|
92
123
|
return fail(`This agent has no ${scope} scope for extensions.`);
|
|
124
|
+
// The packages its npx servers run, in the agent's cache, shared by both scopes (#54); a
|
|
125
|
+
// project's code in the agent's store, its lock all the workspace keeps (#57).
|
|
126
|
+
const packages = installTarget(options.dirs, scope);
|
|
127
|
+
// Installed into the workspace by the person: trusted there as it is.
|
|
128
|
+
const trusted = async (name) => void (scope === "project" && (await trustWorkspace(options.dirs, [name])));
|
|
93
129
|
// A plugin of a marketplace, unless it's a folder here or a git URL.
|
|
94
130
|
const isFolder = await stat(path.resolve(source)).then((found) => found.isDirectory(), () => false);
|
|
131
|
+
if (flag("link")) {
|
|
132
|
+
if (!isFolder)
|
|
133
|
+
return fail(`Only a folder can be linked: ${source} isn't one here.`);
|
|
134
|
+
const result = await addExtension(source, dir, { link: true, ...(packages.packages ? { packages: packages.packages } : {}) });
|
|
135
|
+
await trusted(result.name);
|
|
136
|
+
await configure(result.name, dir);
|
|
137
|
+
write(`Linked ${result.name} in the ${scope} scope: it runs from ${path.resolve(source)}, whose files aren't checked. A rebuild takes effect when its server starts again (a new session, or \`/extensions disable\` and \`enable\` in the chat).`);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
95
140
|
if (!isFolder && !isGitSource(source) && /^[A-Za-z0-9][\w.-]*(@[A-Za-z0-9][\w.-]*)?$/.test(source)) {
|
|
96
141
|
await knownMarketplaces();
|
|
97
142
|
const { marketplace, plugin } = await findPlugin(marketsDir, source);
|
|
@@ -101,12 +146,16 @@ export async function runExtensionCommand(argv, options) {
|
|
|
101
146
|
if (!(await confirmed("Install it? (y/N) ", (typed) => /^(y|yes|s|si|sí)$/i.test(typed))))
|
|
102
147
|
return fail("Not installed.");
|
|
103
148
|
}
|
|
104
|
-
const result = await installFromMarketplace(marketsDir, source, dir);
|
|
149
|
+
const result = await installFromMarketplace(marketsDir, source, dir, packages);
|
|
150
|
+
await trusted(result.name);
|
|
151
|
+
await configure(result.name, dir);
|
|
105
152
|
write(`${result.replaced ? "Reinstalled" : "Installed"} ${result.name} from ${result.marketplace} in the ${scope} scope (${dir})${result.commit ? `, at ${result.commit.slice(0, 12)}` : ""}. It's enabled: it runs from the next session.`);
|
|
106
153
|
return true;
|
|
107
154
|
}
|
|
108
155
|
const subdir = value("path");
|
|
109
|
-
const result = await addExtension(source, dir, subdir ? { subdir } : {});
|
|
156
|
+
const result = await addExtension(source, dir, { ...(subdir ? { subdir } : {}), ...packages });
|
|
157
|
+
await trusted(result.name);
|
|
158
|
+
await configure(result.name, dir);
|
|
110
159
|
write(`${result.replaced ? "Reinstalled" : "Installed"} ${result.name} in the ${scope} scope (${dir})${result.commit ? `, at ${result.commit.slice(0, 12)}` : ""}. It's enabled: it runs from the next session.`);
|
|
111
160
|
return true;
|
|
112
161
|
}
|
|
@@ -121,29 +170,105 @@ export async function runExtensionCommand(argv, options) {
|
|
|
121
170
|
return fail(`${name} isn't installed${askedScope ? ` in the ${askedScope} scope` : ""}. \`${command} extension list\` lists them.`);
|
|
122
171
|
if (found.length > 1 && !askedScope)
|
|
123
172
|
return fail(`${name} is installed in both scopes: say which, with --project or --agent.`);
|
|
124
|
-
const { scope } = found[0];
|
|
173
|
+
const { scope, dataDir, privateDir } = found[0];
|
|
125
174
|
const dir = scopeDir(scope);
|
|
126
|
-
if (action
|
|
127
|
-
await removeExtension(dir, name);
|
|
128
|
-
else
|
|
175
|
+
if (action !== "remove") {
|
|
129
176
|
await setExtensionEnabled(dir, name, action === "enable");
|
|
130
|
-
|
|
177
|
+
write(`${name} ${action}d in the ${scope} scope. It applies from the next session (in the chat, \`/extensions\` reopens it).`);
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
// Its data (a browser profile, a signed-in session) may be worth keeping for a reinstall: asked, unless said.
|
|
181
|
+
const size = await folderSize(dataDir);
|
|
182
|
+
const deleteData = size > 0 && !flag("keep-data") && (await confirmed(`Delete its data too (${dataDir}, ${formatSize(size)})? (y/N) `, (typed) => /^(y|yes|s|si|sí)$/i.test(typed)));
|
|
183
|
+
await removeExtension(dir, name, { deleteData, privateDir });
|
|
184
|
+
write(`${name} removed in the ${scope} scope${size > 0 ? (deleteData ? ", with its data" : `; its data stays in ${dataDir}`) : ""}. It applies from the next session (in the chat, \`/extensions\` reopens it).`);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
case "config": {
|
|
188
|
+
const name = positional[0];
|
|
189
|
+
if (!name)
|
|
190
|
+
return fail(`Usage: ${command} extension config <name> [key=value… | key… | --unset key…] [--project | --agent]`);
|
|
191
|
+
const found = (await listInstalled(options.dirs)).filter((extension) => extension.name === name && !extension.shadowed && (!askedScope || extension.scope === askedScope));
|
|
192
|
+
if (!found.length)
|
|
193
|
+
return fail(`${name} isn't installed${askedScope ? ` in the ${askedScope} scope` : ""}. \`${command} extension list\` lists them.`);
|
|
194
|
+
const installed = found[0];
|
|
195
|
+
const declared = installed.manifest?.userConfig ?? {};
|
|
196
|
+
const typed = {};
|
|
197
|
+
for (const key of values("unset"))
|
|
198
|
+
typed[key] = undefined;
|
|
199
|
+
for (const pair of positional.slice(1)) {
|
|
200
|
+
const at = pair.indexOf("=");
|
|
201
|
+
if (at > 0)
|
|
202
|
+
typed[pair.slice(0, at)] = pair.slice(at + 1);
|
|
203
|
+
else {
|
|
204
|
+
const option = declared[pair];
|
|
205
|
+
if (!option)
|
|
206
|
+
return fail(`${name} has no option "${pair}"${Object.keys(declared).length ? `: it has ${Object.keys(declared).join(", ")}` : ""}.`);
|
|
207
|
+
typed[pair] = (await (option.sensitive ? askSecret : ask)(`${option.title}: ${option.description}\n ${pair} = `)).trim() || undefined;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (Object.keys(typed).length) {
|
|
211
|
+
await setExtensionOptions(installed, name, typed);
|
|
212
|
+
write(`${name}'s options saved in the ${installed.scope} scope. They apply when its server starts again: the next session, or in a chat \`/extensions disable ${name}\` and \`enable\`.`);
|
|
213
|
+
}
|
|
214
|
+
const lines = await describeOptions((await listInstalled(options.dirs)).find((extension) => extension.name === name && extension.scopeDir === installed.scopeDir));
|
|
215
|
+
if (!lines.length)
|
|
216
|
+
write(`${name} has no options.`);
|
|
217
|
+
for (const line of lines)
|
|
218
|
+
write(line);
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
case "restore": {
|
|
222
|
+
const result = await restoreExtensions(options.dirs, options.official ? { official: options.official } : {});
|
|
223
|
+
for (const name of result.restored)
|
|
224
|
+
write(`Restored ${name}.`);
|
|
225
|
+
for (const { name, reason } of result.failed)
|
|
226
|
+
write(`${name} can't be restored: ${reason}.`);
|
|
227
|
+
if (!result.restored.length && !result.failed.length)
|
|
228
|
+
write("This workspace's extensions are all on this computer.");
|
|
229
|
+
if (result.failed.length)
|
|
230
|
+
process.exitCode = 1;
|
|
231
|
+
if (flag("trust"))
|
|
232
|
+
for (const name of await trustWorkspace(options.dirs))
|
|
233
|
+
write(`Trusted ${name} in this workspace.`);
|
|
234
|
+
else if (options.dirs.project && options.dirs.agent)
|
|
235
|
+
write(`They run once you trust them: \`${command} extension trust\`, or the chat asks.`);
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
case "trust": {
|
|
239
|
+
if (!options.dirs.project || !options.dirs.agent)
|
|
240
|
+
return fail("This agent has no workspace scope to trust: only its own, which never asks.");
|
|
241
|
+
const names = await trustWorkspace(options.dirs, positional.length ? positional : undefined);
|
|
242
|
+
if (!names.length)
|
|
243
|
+
write("This workspace has no extensions.");
|
|
244
|
+
for (const name of names)
|
|
245
|
+
write(`Trusted ${name} in this workspace, as it is now: another version asks again.`);
|
|
131
246
|
return true;
|
|
132
247
|
}
|
|
133
248
|
case "search": {
|
|
134
|
-
const
|
|
135
|
-
const installed = new Set((await listInstalled(options.dirs)).map((extension) => extension.name));
|
|
136
|
-
const offered = (await knownMarketplaces()).flatMap((marketplace) => (marketplace.manifest?.plugins ?? []).map((plugin) => ({ marketplace, plugin })));
|
|
137
|
-
const matching = offered.filter(({ plugin }) => {
|
|
138
|
-
const text = [plugin.name, plugin.description, plugin.category, ...(plugin.tags ?? []), ...(plugin.keywords ?? [])].join(" ").toLowerCase();
|
|
139
|
-
return words.every((word) => text.includes(word));
|
|
140
|
-
});
|
|
249
|
+
const matching = await extensionCatalog(options.dirs, { ...(options.official ? { official: options.official } : {}), ...(options.catalogTopics?.length ? { topics: options.catalogTopics } : {}), words: positional });
|
|
141
250
|
if (!matching.length) {
|
|
142
|
-
|
|
251
|
+
const offered = (await knownMarketplaces()).some((marketplace) => marketplace.manifest?.plugins.length);
|
|
252
|
+
write(offered ? "No extension matches." : `No marketplace offers extensions yet. \`${command} extension marketplace add <folder | git URL | owner/repo>\` adds one.`);
|
|
143
253
|
return true;
|
|
144
254
|
}
|
|
145
|
-
for (const
|
|
146
|
-
|
|
255
|
+
for (const entry of matching) {
|
|
256
|
+
const state = entry.installed ? (entry.update ? ` (installed: ${entry.installed.manifest?.version ?? "?"}, update available)` : " (installed)") : "";
|
|
257
|
+
write(`${entry.spec}${entry.version ? ` ${entry.version}` : ""}${state}${entry.plugin.description ? ` ${entry.plugin.description}` : ""}`);
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
case "update": {
|
|
262
|
+
const name = positional[0];
|
|
263
|
+
const official = options.official ? { official: options.official } : {};
|
|
264
|
+
const names = name ? [name] : (await extensionCatalog(options.dirs, official)).filter((entry) => entry.update).map((entry) => entry.plugin.name);
|
|
265
|
+
if (!names.length) {
|
|
266
|
+
write("Every extension from a marketplace is up to date.");
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
for (const each of names) {
|
|
270
|
+
const result = await updateExtension(options.dirs, each, official);
|
|
271
|
+
write(`Updated ${each}${result.from || result.to ? ` (${result.from ?? "?"} → ${result.to ?? "?"})` : ""} in the ${result.scope} scope: its data and options kept. It runs from the next session.`);
|
|
147
272
|
}
|
|
148
273
|
return true;
|
|
149
274
|
}
|
|
@@ -154,8 +279,14 @@ export async function runExtensionCommand(argv, options) {
|
|
|
154
279
|
`Usage: ${command} extension <action>`,
|
|
155
280
|
" list what's installed, in which scope, on or off",
|
|
156
281
|
" info <name> its metadata, what it offers, its README",
|
|
157
|
-
" add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>] [--yes]",
|
|
158
|
-
"
|
|
282
|
+
" add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>] [--set key=value…] [--yes]",
|
|
283
|
+
" add <folder> --link [--project] to develop one: runs from its folder, not copied nor checked",
|
|
284
|
+
" remove <name> [--project | --agent] [--yes | --keep-data]",
|
|
285
|
+
" enable|disable <name> [--project | --agent]",
|
|
286
|
+
" config <name> [key=value… | key… | --unset key…] its options (key alone asks for it)",
|
|
287
|
+
" update [name] to the version its marketplace offers now (all that have one, without a name)",
|
|
288
|
+
" restore [--trust] gets what this workspace's lock lists and this computer lacks",
|
|
289
|
+
" trust [name…] lets this workspace's extensions run, as they are now",
|
|
159
290
|
" search [words] what the known marketplaces offer",
|
|
160
291
|
" marketplace add <folder | git URL[#ref] | owner/repo> [--yes]",
|
|
161
292
|
" marketplace list | update [name] | remove <name>",
|
|
@@ -218,6 +349,32 @@ export async function runExtensionCommand(argv, options) {
|
|
|
218
349
|
}
|
|
219
350
|
}
|
|
220
351
|
/** A question on the terminal, or no answer without one. */
|
|
352
|
+
/** Which of an installed extension's options have a value of their own (not a default). */
|
|
353
|
+
async function describeOptionStates(installed) {
|
|
354
|
+
return new Map((await extensionOptionStates(installed)).map((state) => [state.key, state.set && !state.isDefault]));
|
|
355
|
+
}
|
|
356
|
+
/** A secret from the terminal, not echoed; "" without one. */
|
|
357
|
+
async function askSecretOnTerminal(question) {
|
|
358
|
+
if (!process.stdin.isTTY)
|
|
359
|
+
return "";
|
|
360
|
+
const terminal = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
361
|
+
// What's typed isn't written back: only the question, readline's first write.
|
|
362
|
+
const output = terminal;
|
|
363
|
+
let prompted = false;
|
|
364
|
+
output._writeToOutput = (text) => {
|
|
365
|
+
if (prompted)
|
|
366
|
+
return;
|
|
367
|
+
prompted = true;
|
|
368
|
+
output.output.write(text);
|
|
369
|
+
};
|
|
370
|
+
try {
|
|
371
|
+
return await terminal.question(question);
|
|
372
|
+
}
|
|
373
|
+
finally {
|
|
374
|
+
terminal.close();
|
|
375
|
+
process.stdout.write("\n");
|
|
376
|
+
}
|
|
377
|
+
}
|
|
221
378
|
async function askOnTerminal(question) {
|
|
222
379
|
if (!process.stdin.isTTY)
|
|
223
380
|
return "";
|
|
@@ -249,9 +406,29 @@ async function info(extension) {
|
|
|
249
406
|
...field("Requires", manifest.requires.join(", ") || undefined),
|
|
250
407
|
...field("Needs", manifest.needs.join(", ") || undefined),
|
|
251
408
|
...field("Servers", Object.keys(manifest.servers).join(", ") || undefined),
|
|
252
|
-
...field("Only read", manifest.readOnlyTools
|
|
409
|
+
...field("Only read", manifest.readOnlyTools
|
|
410
|
+
? `${manifest.readOnlyTools.join(", ") || "none"} (its manifest)`
|
|
411
|
+
: Object.keys(manifest.servers).length
|
|
412
|
+
? "what its servers mark readOnlyHint"
|
|
413
|
+
: undefined),
|
|
414
|
+
...field("Packages", Object.keys(extension.lock.packages ?? {}).join(", ") || undefined),
|
|
415
|
+
...field("Options", Object.entries(manifest.userConfig)
|
|
416
|
+
.map(([key, option]) => `${key} (${[option.title, option.required && "required", option.sensitive && "secret"].filter(Boolean).join(", ")})`)
|
|
417
|
+
.join(", ") || undefined),
|
|
253
418
|
...field("Variables", Object.values(manifest.servers).flatMap((server) => Object.keys(server.env ?? {})).join(", ") || undefined),
|
|
254
419
|
...field("Installed", `${extension.scope} scope, ${extension.lock.enabled ? "enabled" : "disabled"}, on ${extension.lock.installed}, from ${describe(extension).split(" from ")[1]}`),
|
|
420
|
+
...field("Data", (await stat(extension.dataDir).catch(() => null))?.isDirectory() ? `${extension.dataDir} (${formatSize(await folderSize(extension.dataDir))})` : undefined),
|
|
255
421
|
...field("README", (await stat(readme).catch(() => null))?.isFile() ? readme : undefined),
|
|
256
422
|
];
|
|
257
423
|
}
|
|
424
|
+
/** A size in bytes, as a person reads it: `512 B`, `3.4 KB`, `120 MB`. */
|
|
425
|
+
function formatSize(bytes) {
|
|
426
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
427
|
+
let size = bytes;
|
|
428
|
+
let unit = 0;
|
|
429
|
+
while (size >= 1024 && unit < units.length - 1) {
|
|
430
|
+
size /= 1024;
|
|
431
|
+
unit++;
|
|
432
|
+
}
|
|
433
|
+
return `${unit === 0 ? size : size.toFixed(size < 10 ? 1 : 0)} ${units[unit]}`;
|
|
434
|
+
}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "awareness",
|
|
3
|
+
"version": "1.0.0",
|
|
3
4
|
"description": "You know who you are and what you are right now (your mode, extensions, tools, subagents, commands, context) through about_me, and how your chat is used through the help skill.",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Falkenslab",
|
|
7
|
+
"url": "https://github.com/falkenslab"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"metadata": {
|
|
11
|
+
"agent-kit": {
|
|
12
|
+
"provides": [
|
|
13
|
+
"self-awareness"
|
|
14
|
+
],
|
|
15
|
+
"requires": []
|
|
16
|
+
}
|
|
9
17
|
}
|
|
10
18
|
}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "knowledge",
|
|
3
|
+
"version": "1.0.0",
|
|
3
4
|
"description": "Built-in knowledge base workflows (an LLM wiki) over the kit's knowledge_* tools: ingesting sources, querying the knowledge base and health-checking it.",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Falkenslab",
|
|
7
|
+
"url": "https://github.com/falkenslab"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"metadata": {
|
|
11
|
+
"agent-kit": {
|
|
12
|
+
"provides": [
|
|
13
|
+
"knowledge-base"
|
|
14
|
+
],
|
|
15
|
+
"requires": []
|
|
16
|
+
}
|
|
9
17
|
}
|
|
10
18
|
}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory",
|
|
3
|
+
"version": "1.0.0",
|
|
3
4
|
"description": "Your memory of the person you work for, across all their projects: who they are and how they want things done, kept only from what they tell you, through its tools: recall, remember and forget.",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Falkenslab",
|
|
7
|
+
"url": "https://github.com/falkenslab"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"metadata": {
|
|
11
|
+
"agent-kit": {
|
|
12
|
+
"provides": [
|
|
13
|
+
"person-memory"
|
|
14
|
+
],
|
|
15
|
+
"requires": []
|
|
16
|
+
}
|
|
9
17
|
}
|
|
10
18
|
}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sources",
|
|
3
|
+
"version": "1.0.0",
|
|
3
4
|
"description": "The sources folder: originals kept as obtained, listed, downloaded, asked for and retired through the kit's sources tools.",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Falkenslab",
|
|
7
|
+
"url": "https://github.com/falkenslab"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"metadata": {
|
|
11
|
+
"agent-kit": {
|
|
12
|
+
"provides": [
|
|
13
|
+
"sources"
|
|
14
|
+
],
|
|
15
|
+
"requires": []
|
|
16
|
+
}
|
|
9
17
|
}
|
|
10
18
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@falkenslab/agent-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Library for building an agent on top of the Claude Agent SDK",
|
|
5
5
|
"author": "Francisco Vargas Ruiz",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"files": [
|
|
34
34
|
"dist",
|
|
35
35
|
"assets",
|
|
36
|
-
"extensions"
|
|
36
|
+
"extensions",
|
|
37
|
+
"schemas"
|
|
37
38
|
],
|
|
38
39
|
"scripts": {
|
|
39
40
|
"build": "tsc",
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
"prepublishOnly": "npm run typecheck && npm run lint && npm test && npm run build"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
|
-
"@anthropic-ai/claude-agent-sdk": "^0.3.
|
|
52
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.300",
|
|
52
53
|
"@inkjs/ui": "^2.0.0",
|
|
53
54
|
"@inquirer/prompts": "^8.5.2",
|
|
54
55
|
"@types/react": "^19.3.0",
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
"@mozilla/readability": "^0.6.0",
|
|
67
68
|
"@types/node": "^24.0.0",
|
|
68
69
|
"@types/turndown": "^5.0.6",
|
|
70
|
+
"ajv": "^8.20.0",
|
|
69
71
|
"eslint": "^10.9.1",
|
|
70
72
|
"fflate": "^0.8.3",
|
|
71
73
|
"globals": "^17.11.0",
|
|
@@ -85,10 +87,10 @@
|
|
|
85
87
|
},
|
|
86
88
|
"peerDependencies": {
|
|
87
89
|
"@mozilla/readability": ">=0.5.0",
|
|
90
|
+
"fflate": ">=0.8.0",
|
|
88
91
|
"linkedom": ">=0.16.0",
|
|
89
|
-
"turndown": ">=7.0.0",
|
|
90
92
|
"mammoth": ">=1.6.0",
|
|
91
|
-
"
|
|
93
|
+
"turndown": ">=7.0.0"
|
|
92
94
|
},
|
|
93
95
|
"peerDependenciesMeta": {
|
|
94
96
|
"@mozilla/readability": {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://falkenslab.github.io/agent-kit/schemas/agent-kit-metadata.schema.json",
|
|
4
|
+
"title": "agent-kit's data in an extension's plugin.json",
|
|
5
|
+
"description": "What goes under metadata[\"agent-kit\"] in a Claude Code plugin's .claude-plugin/plugin.json, for agent-kit's gates, chats and catalog. Claude Code doesn't read metadata. Every field is optional.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"kit": {
|
|
10
|
+
"description": "The agent-kit versions it works with: comparators separated by spaces, all of which must hold (\">=0.22.0\", \">=0.22.0 <0.24.0\").",
|
|
11
|
+
"type": "string",
|
|
12
|
+
"pattern": "^\\s*(?:(?:>=|<=|>|<|=)?\\s*v?\\d+(?:\\.\\d+){0,2})(?:\\s+(?:>=|<=|>|<|=)?\\s*v?\\d+(?:\\.\\d+){0,2})*\\s*$"
|
|
13
|
+
},
|
|
14
|
+
"provides": { "description": "Capabilities it lets the agent use.", "type": "array", "items": { "type": "string", "minLength": 1 } },
|
|
15
|
+
"requires": { "description": "Capabilities another enabled extension must provide.", "type": "array", "items": { "type": "string", "minLength": 1 } },
|
|
16
|
+
"needs": { "description": "Programs it needs on the computer's PATH.", "type": "array", "items": { "type": "string", "minLength": 1 } },
|
|
17
|
+
"readOnlyTools": {
|
|
18
|
+
"description": "Its tools that only read (short names): plan mode lets them through. Without it, plan mode asks its servers (readOnlyHint).",
|
|
19
|
+
"type": "array",
|
|
20
|
+
"items": { "type": "string", "minLength": 1 }
|
|
21
|
+
},
|
|
22
|
+
"labels": {
|
|
23
|
+
"description": "How the chat shows each tool (short name), per language (en, es, fr, de): {field} takes the call's input.",
|
|
24
|
+
"type": "object",
|
|
25
|
+
"additionalProperties": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"propertyNames": { "enum": ["en", "es", "fr", "de"] },
|
|
28
|
+
"additionalProperties": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"additionalProperties": false,
|
|
31
|
+
"required": ["label"],
|
|
32
|
+
"properties": {
|
|
33
|
+
"label": { "type": "string", "minLength": 1 },
|
|
34
|
+
"phrase": {
|
|
35
|
+
"description": "How it counts in a folded summary: once, and {n} times.",
|
|
36
|
+
"type": "array",
|
|
37
|
+
"prefixItems": [{ "type": "string" }, { "type": "string" }],
|
|
38
|
+
"minItems": 2,
|
|
39
|
+
"maxItems": 2
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"help": { "description": "What it says about itself in a session, for the agent to tell the person.", "type": "string" },
|
|
46
|
+
"catalog": {
|
|
47
|
+
"description": "How a catalog shows it: images inside its folder (SVG, PNG, JPEG or WebP).",
|
|
48
|
+
"type": "object",
|
|
49
|
+
"additionalProperties": false,
|
|
50
|
+
"properties": {
|
|
51
|
+
"logo": { "description": "Square.", "$ref": "#/$defs/image" },
|
|
52
|
+
"banner": { "description": "Wide, about 3:1.", "$ref": "#/$defs/image" }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"$defs": {
|
|
57
|
+
"image": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"pattern": "^(?!/)(?![A-Za-z]:)(?!.*(?:^|[\\\\/])\\.\\.(?:[\\\\/]|$)).+\\.(?:svg|png|jpe?g|webp)$"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://falkenslab.github.io/agent-kit/schemas/marketplace-catalog.schema.json",
|
|
4
|
+
"title": "A marketplace's catalog.json",
|
|
5
|
+
"description": "A Claude Code marketplace's extensions as one file, for a page or a client to show without cloning the repository: written by @falkenslab/agent-kit-catalog from .claude-plugin/marketplace.json and each extension's plugin.json.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["marketplace", "description", "repository", "add", "extensions"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"marketplace": { "description": "The marketplace's name, as <extension>@<marketplace> names an extension.", "type": "string", "minLength": 1 },
|
|
11
|
+
"description": { "type": "string" },
|
|
12
|
+
"repository": { "description": "The GitHub repository's page.", "type": "string", "pattern": "^https://github\\.com/[\\w.-]+/[\\w.-]+$" },
|
|
13
|
+
"add": {
|
|
14
|
+
"description": "How to add the marketplace.",
|
|
15
|
+
"type": "object",
|
|
16
|
+
"additionalProperties": false,
|
|
17
|
+
"required": ["claudeCode", "agentKit"],
|
|
18
|
+
"properties": {
|
|
19
|
+
"claudeCode": { "type": "string" },
|
|
20
|
+
"agentKit": { "description": "After the agent's own command (<agent> …).", "type": "string" }
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"extensions": {
|
|
24
|
+
"description": "Sorted by name.",
|
|
25
|
+
"type": "array",
|
|
26
|
+
"items": { "$ref": "#/$defs/extension" }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"$defs": {
|
|
30
|
+
"url": { "type": "string", "pattern": "^https://" },
|
|
31
|
+
"extension": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"additionalProperties": false,
|
|
34
|
+
"required": ["name", "version", "description", "category", "topics", "author", "license", "homepage", "readme", "install"],
|
|
35
|
+
"properties": {
|
|
36
|
+
"name": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" },
|
|
37
|
+
"version": { "description": "Its plugin.json's, the same as its marketplace entry's.", "type": "string", "minLength": 1 },
|
|
38
|
+
"description": { "type": "string" },
|
|
39
|
+
"category": { "type": ["string", "null"] },
|
|
40
|
+
"topics": {
|
|
41
|
+
"description": "Its plugin.json's keywords and its entry's tags, keywords and category, lowercased, without repeats.",
|
|
42
|
+
"type": "array",
|
|
43
|
+
"uniqueItems": true,
|
|
44
|
+
"items": { "type": "string", "pattern": "^[^A-Z]+$" }
|
|
45
|
+
},
|
|
46
|
+
"author": { "type": ["string", "null"] },
|
|
47
|
+
"license": { "type": ["string", "null"] },
|
|
48
|
+
"homepage": { "$ref": "#/$defs/url" },
|
|
49
|
+
"readme": { "$ref": "#/$defs/url" },
|
|
50
|
+
"install": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"additionalProperties": false,
|
|
53
|
+
"required": ["claudeCode", "agentKit"],
|
|
54
|
+
"properties": {
|
|
55
|
+
"claudeCode": { "type": "string" },
|
|
56
|
+
"agentKit": { "description": "After the agent's own command (<agent> …).", "type": "string" }
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"logo": { "description": "A URL anyone can load.", "$ref": "#/$defs/url" },
|
|
60
|
+
"banner": { "description": "A URL anyone can load.", "$ref": "#/$defs/url" }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|