@falkenslab/agent-kit 0.19.0 → 0.21.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 +9 -2
- package/dist/chat/chatController.d.ts +4 -1
- package/dist/chat/chatController.js +43 -17
- package/dist/chat/extensions.d.ts +7 -3
- package/dist/chat/extensions.js +12 -9
- package/dist/chat/runs.d.ts +3 -0
- package/dist/chat/runs.js +1 -1
- package/dist/core/agentSpec.d.ts +20 -56
- package/dist/core/extensions.d.ts +23 -12
- package/dist/core/extensions.js +8 -18
- package/dist/core/externalExtensions.d.ts +49 -9
- package/dist/core/externalExtensions.js +89 -18
- package/dist/core/liveExtensions.d.ts +65 -0
- package/dist/core/liveExtensions.js +121 -0
- package/dist/core/marketplaces.d.ts +118 -0
- package/dist/core/marketplaces.js +242 -0
- package/dist/core/messages/de.js +1 -0
- package/dist/core/messages/en.d.ts +2 -0
- package/dist/core/messages/en.js +1 -0
- package/dist/core/messages/es.js +1 -0
- package/dist/core/messages/fr.js +1 -0
- package/dist/core/pluginDownloads.d.ts +14 -0
- package/dist/core/pluginDownloads.js +141 -0
- package/dist/core/runner.js +6 -1
- package/dist/core/session.d.ts +11 -4
- package/dist/core/session.js +73 -21
- package/dist/core/sessionFacts.d.ts +5 -0
- package/dist/core/sessionFacts.js +1 -0
- package/dist/extensions/awareness/index.d.ts +12 -3
- package/dist/extensions/awareness/index.js +18 -15
- package/dist/extensions/awareness/tools.d.ts +1 -1
- package/dist/extensions/awareness/tools.js +3 -3
- package/dist/extensions/knowledge/index.d.ts +20 -6
- package/dist/extensions/knowledge/index.js +32 -29
- package/dist/extensions/knowledge/knowledgeStore.d.ts +1 -1
- package/dist/extensions/knowledge/prompt.d.ts +4 -4
- package/dist/extensions/knowledge/prompt.js +4 -4
- package/dist/extensions/memory/index.d.ts +15 -6
- package/dist/extensions/memory/index.js +34 -31
- package/dist/extensions/sources/index.d.ts +17 -5
- package/dist/extensions/sources/index.js +31 -29
- package/dist/index.d.ts +7 -2
- package/dist/index.js +6 -1
- package/dist/tui/extensionCommand.d.ts +14 -3
- package/dist/tui/extensionCommand.js +164 -12
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { folderOf } from "../../core/extensions.js";
|
|
3
4
|
import { sourcesToolLabels } from "./labels.js";
|
|
4
5
|
import { addSource } from "./sources.js";
|
|
5
6
|
import { createSaveToSourcesServer, sourcesPromptSection } from "./tools.js";
|
|
@@ -8,33 +9,34 @@ export function sourcesPluginRoot() {
|
|
|
8
9
|
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "extensions", "sources");
|
|
9
10
|
}
|
|
10
11
|
/**
|
|
11
|
-
* The sources folder
|
|
12
|
-
*
|
|
13
|
-
* a knowledge base (#30).
|
|
12
|
+
* The sources folder: originals kept as obtained, read with `Read` and `extract_text`, added and
|
|
13
|
+
* retired only through its tools (`sourceFiles`). It knows nothing of a knowledge base (#30).
|
|
14
14
|
*/
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
15
|
+
export function sources(options) {
|
|
16
|
+
return {
|
|
17
|
+
name: "sources",
|
|
18
|
+
plugin: sourcesPluginRoot(),
|
|
19
|
+
missing: ({ config }) => (folderOf(options.dir, config) ? undefined : "needs a folder (`dir`)"),
|
|
20
|
+
async contribute({ config, runDir, interactive }) {
|
|
21
|
+
const sourcesDir = folderOf(options.dir, config);
|
|
22
|
+
const folder = `${path.relative(config.projectDir, sourcesDir).split(path.sep).join("/")}/`;
|
|
23
|
+
return {
|
|
24
|
+
// Asking a person (request_file, retire_source) only where there is one.
|
|
25
|
+
mcpServers: { sourceFiles: createSaveToSourcesServer(runDir, sourcesDir, options.saveDescription, { projectDir: config.projectDir, interactive }) },
|
|
26
|
+
promptSection: sourcesPromptSection(config.projectDir, sourcesDir),
|
|
27
|
+
// The originals are read and searched, never written: the only way in is its tools.
|
|
28
|
+
fileTools: ["Read", "Glob", "Grep"],
|
|
29
|
+
readOnlyDirs: [sourcesDir],
|
|
30
|
+
readOnlyTools: ["mcp__sourceFiles__list_sources", "mcp__sourceFiles__extract_text"],
|
|
31
|
+
selfAskingTools: ["mcp__sourceFiles__request_file", "mcp__sourceFiles__retire_source"],
|
|
32
|
+
toolLabels: sourcesToolLabels(),
|
|
33
|
+
// For the host (a page's "add to the chest"): a file the person gave, with the same rules
|
|
34
|
+
// as the tools (never overwriting, a duplicate recognized) and the person as its origin.
|
|
35
|
+
api: {
|
|
36
|
+
addSource: (file, name, subfolder = "") => addSource(sourcesDir, file, subfolder ? `${subfolder.replace(/[/\\]*$/, "")}/` : name, { kind: "person", from: name }, subfolder ? { name } : {}),
|
|
37
|
+
},
|
|
38
|
+
helpLines: [`Originals go in \`${folder}\`: the person drops files there (or you ask for one, or download it), and you never change them.`],
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,8 +11,13 @@ export { buildSessionOptions, createInputQueue, createDeferred, createModeContro
|
|
|
11
11
|
export { runQuery, type AgentEvent, type AgentRun, type SessionUsage, type ContextUsage } from "./core/runner.js";
|
|
12
12
|
export type { SessionFacts } from "./core/sessionFacts.js";
|
|
13
13
|
export { knowledgePluginRoot, knowledgePromptSection } from "./extensions/knowledge/prompt.js";
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
14
|
+
export { awareness, type AwarenessOptions } from "./extensions/awareness/index.js";
|
|
15
|
+
export { sources, type SourcesOptions } from "./extensions/sources/index.js";
|
|
16
|
+
export { knowledge, type KnowledgeOptions } from "./extensions/knowledge/index.js";
|
|
17
|
+
export { memory, type MemoryOptions } from "./extensions/memory/index.js";
|
|
18
|
+
export { readExtensionManifest, type Extension, type ExtensionContext, type ExtensionContribution, type ExtensionManifest, type FolderOption, } from "./core/extensions.js";
|
|
19
|
+
export { addExtension, extensionDataDir, removeExtension, setExtensionEnabled, listInstalled, readExternalManifest, type ExtensionAuthor, type ExtensionDirs, type ExtensionScope, type ExternalManifest, type ExternalToolLabel, type PluginServer, type InstalledExtension, type LockEntry, } from "./core/externalExtensions.js";
|
|
20
|
+
export { addMarketplace, findPlugin, inspectMarketplace, installFromMarketplace, listMarketplaces, readMarketplaceManifest, removeMarketplace, resolvePluginSource, updateMarketplace, type KnownMarketplace, type MarketplaceManifest, type MarketplacePlugin, type PluginSource, } from "./core/marketplaces.js";
|
|
16
21
|
export { runExtensionCommand } from "./tui/extensionCommand.js";
|
|
17
22
|
export { agentKitVersion } from "./core/version.js";
|
|
18
23
|
export { BUILT_IN_PAGE_TYPES, isSlug, parseId, linkedIds, type KnowledgeStore, type PageType, type KnowledgePage, type PageInfo, type SearchHit, type CheckReport, type FieldChanges, type NewPage, } from "./extensions/knowledge/knowledgeStore.js";
|
package/dist/index.js
CHANGED
|
@@ -11,8 +11,13 @@ export { createFriendlyToolLabel, truncate, truncatePath, withToolLabels, withTo
|
|
|
11
11
|
export { buildSessionOptions, createInputQueue, createDeferred, createModeControl, togglePlanMode } from "./core/session.js";
|
|
12
12
|
export { runQuery } from "./core/runner.js";
|
|
13
13
|
export { knowledgePluginRoot, knowledgePromptSection } from "./extensions/knowledge/prompt.js";
|
|
14
|
+
export { awareness } from "./extensions/awareness/index.js";
|
|
15
|
+
export { sources } from "./extensions/sources/index.js";
|
|
16
|
+
export { knowledge } from "./extensions/knowledge/index.js";
|
|
17
|
+
export { memory } from "./extensions/memory/index.js";
|
|
14
18
|
export { readExtensionManifest, } from "./core/extensions.js";
|
|
15
|
-
export { addExtension, removeExtension, setExtensionEnabled, listInstalled, readExternalManifest, } from "./core/externalExtensions.js";
|
|
19
|
+
export { addExtension, extensionDataDir, removeExtension, setExtensionEnabled, listInstalled, readExternalManifest, } from "./core/externalExtensions.js";
|
|
20
|
+
export { addMarketplace, findPlugin, inspectMarketplace, installFromMarketplace, listMarketplaces, readMarketplaceManifest, removeMarketplace, resolvePluginSource, updateMarketplace, } from "./core/marketplaces.js";
|
|
16
21
|
export { runExtensionCommand } from "./tui/extensionCommand.js";
|
|
17
22
|
export { agentKitVersion } from "./core/version.js";
|
|
18
23
|
export { BUILT_IN_PAGE_TYPES, isSlug, parseId, linkedIds, } from "./extensions/knowledge/knowledgeStore.js";
|
|
@@ -8,14 +8,25 @@ export { chatExtensionsCommand, describe } from "../chat/extensions.js";
|
|
|
8
8
|
*
|
|
9
9
|
* - `extension list`
|
|
10
10
|
* - `extension info <name>`: its metadata, what it offers and where its README is
|
|
11
|
-
* - `extension add <folder | git URL[#ref]> [--project] [--path <subfolder>]`
|
|
12
|
-
* - `extension
|
|
11
|
+
* - `extension add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>] [--yes]`
|
|
12
|
+
* - `extension add <folder> --link [--project]`: registered where it is, to develop it (not copied, its files not checked)
|
|
13
|
+
* - `extension remove <name> [--project | --agent] [--yes | --keep-data]`: asks before deleting its data folder
|
|
14
|
+
* - `extension enable|disable <name> [--project | --agent]`
|
|
15
|
+
* - `extension search [words]`: what the known marketplaces offer
|
|
16
|
+
* - `extension marketplace add <folder | git URL[#ref] | owner/repo> [--yes]`, `list`, `update [name]`, `remove <name>`
|
|
13
17
|
*
|
|
14
18
|
* Without a scope, `add` installs into the agent's (the project's when the agent has no scope of
|
|
15
|
-
* its own); the others act where the extension is, the project's first.
|
|
19
|
+
* its own); the others act where the extension is, the project's first. The marketplaces live in
|
|
20
|
+
* the same folder. `official` is the agent's own marketplace (a folder or git URL): known without
|
|
21
|
+
* asking, and installed from without a confirmation. Any other is added after a warning and
|
|
22
|
+
* typing its name, and installing from it asks first; `--yes` answers for the person (a script).
|
|
16
23
|
*/
|
|
17
24
|
export declare function runExtensionCommand(argv: readonly string[], options: {
|
|
18
25
|
dirs: ExtensionDirs;
|
|
19
26
|
command?: string;
|
|
20
27
|
write?: (line: string) => void;
|
|
28
|
+
/** The agent's official marketplace: a folder or a git URL (`#ref`). */
|
|
29
|
+
official?: string;
|
|
30
|
+
/** Asks the person (a confirmation); by default on the terminal, and nothing without one. */
|
|
31
|
+
ask?: (question: string) => Promise<string>;
|
|
21
32
|
}): Promise<boolean>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { addMarketplace, findPlugin, inspectMarketplace, installFromMarketplace, listMarketplaces, removeMarketplace, updateMarketplace, } from "../core/marketplaces.js";
|
|
5
|
+
import { addExtension, folderSize, isGitSource, listInstalled, removeExtension, setExtensionEnabled, } from "../core/externalExtensions.js";
|
|
4
6
|
import { describe } from "../chat/extensions.js";
|
|
5
7
|
export { chatExtensionsCommand, describe } from "../chat/extensions.js";
|
|
6
8
|
/**
|
|
@@ -11,11 +13,18 @@ export { chatExtensionsCommand, describe } from "../chat/extensions.js";
|
|
|
11
13
|
*
|
|
12
14
|
* - `extension list`
|
|
13
15
|
* - `extension info <name>`: its metadata, what it offers and where its README is
|
|
14
|
-
* - `extension add <folder | git URL[#ref]> [--project] [--path <subfolder>]`
|
|
15
|
-
* - `extension
|
|
16
|
+
* - `extension add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>] [--yes]`
|
|
17
|
+
* - `extension add <folder> --link [--project]`: registered where it is, to develop it (not copied, its files not checked)
|
|
18
|
+
* - `extension remove <name> [--project | --agent] [--yes | --keep-data]`: asks before deleting its data folder
|
|
19
|
+
* - `extension enable|disable <name> [--project | --agent]`
|
|
20
|
+
* - `extension search [words]`: what the known marketplaces offer
|
|
21
|
+
* - `extension marketplace add <folder | git URL[#ref] | owner/repo> [--yes]`, `list`, `update [name]`, `remove <name>`
|
|
16
22
|
*
|
|
17
23
|
* Without a scope, `add` installs into the agent's (the project's when the agent has no scope of
|
|
18
|
-
* its own); the others act where the extension is, the project's first.
|
|
24
|
+
* its own); the others act where the extension is, the project's first. The marketplaces live in
|
|
25
|
+
* the same folder. `official` is the agent's own marketplace (a folder or git URL): known without
|
|
26
|
+
* asking, and installed from without a confirmation. Any other is added after a warning and
|
|
27
|
+
* typing its name, and installing from it asks first; `--yes` answers for the person (a script).
|
|
19
28
|
*/
|
|
20
29
|
export async function runExtensionCommand(argv, options) {
|
|
21
30
|
if (argv[0] !== "extension")
|
|
@@ -36,6 +45,22 @@ export async function runExtensionCommand(argv, options) {
|
|
|
36
45
|
return true;
|
|
37
46
|
};
|
|
38
47
|
const scopeDir = (scope) => options.dirs[scope];
|
|
48
|
+
// The marketplaces live with the agent's extensions (the project's when it has no scope of its own).
|
|
49
|
+
const marketsDir = options.dirs.agent ?? options.dirs.project;
|
|
50
|
+
const ask = options.ask ?? askOnTerminal;
|
|
51
|
+
const confirmed = async (question, accepts) => flag("yes") || accepts((await ask(question)).trim());
|
|
52
|
+
// The agent's own marketplace, known without asking; a folder is copied afresh each time (it's the agent's own code).
|
|
53
|
+
const knownMarketplaces = async () => {
|
|
54
|
+
if (!marketsDir)
|
|
55
|
+
throw new Error("This agent has no folder for extensions.");
|
|
56
|
+
const known = await listMarketplaces(marketsDir);
|
|
57
|
+
const official = options.official;
|
|
58
|
+
if (official && !(isGitSource(official) && known.some((marketplace) => marketplace.official))) {
|
|
59
|
+
await addMarketplace(official, marketsDir, { official: true });
|
|
60
|
+
return listMarketplaces(marketsDir);
|
|
61
|
+
}
|
|
62
|
+
return known;
|
|
63
|
+
};
|
|
39
64
|
try {
|
|
40
65
|
switch (action) {
|
|
41
66
|
case "list": {
|
|
@@ -62,11 +87,33 @@ export async function runExtensionCommand(argv, options) {
|
|
|
62
87
|
case "add": {
|
|
63
88
|
const source = positional[0];
|
|
64
89
|
if (!source)
|
|
65
|
-
return fail(`Usage: ${command} extension add <folder | git URL[#ref]> [--project] [--path <subfolder>]`);
|
|
90
|
+
return fail(`Usage: ${command} extension add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>]`);
|
|
66
91
|
const scope = askedScope ?? (options.dirs.agent ? "agent" : "project");
|
|
67
92
|
const dir = scopeDir(scope);
|
|
68
93
|
if (!dir)
|
|
69
94
|
return fail(`This agent has no ${scope} scope for extensions.`);
|
|
95
|
+
// A plugin of a marketplace, unless it's a folder here or a git URL.
|
|
96
|
+
const isFolder = await stat(path.resolve(source)).then((found) => found.isDirectory(), () => false);
|
|
97
|
+
if (flag("link")) {
|
|
98
|
+
if (!isFolder)
|
|
99
|
+
return fail(`Only a folder can be linked: ${source} isn't one here.`);
|
|
100
|
+
const result = await addExtension(source, dir, { link: true });
|
|
101
|
+
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).`);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
if (!isFolder && !isGitSource(source) && /^[A-Za-z0-9][\w.-]*(@[A-Za-z0-9][\w.-]*)?$/.test(source)) {
|
|
105
|
+
await knownMarketplaces();
|
|
106
|
+
const { marketplace, plugin } = await findPlugin(marketsDir, source);
|
|
107
|
+
if (!marketplace.official) {
|
|
108
|
+
write(`${plugin.name}${plugin.version ? ` ${plugin.version}` : ""}, from the marketplace ${marketplace.name} (${marketplace.source}): ${plugin.description ?? "no description"}`);
|
|
109
|
+
write("It will run on this computer with your permissions.");
|
|
110
|
+
if (!(await confirmed("Install it? (y/N) ", (typed) => /^(y|yes|s|si|sí)$/i.test(typed))))
|
|
111
|
+
return fail("Not installed.");
|
|
112
|
+
}
|
|
113
|
+
const result = await installFromMarketplace(marketsDir, source, dir);
|
|
114
|
+
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.`);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
70
117
|
const subdir = value("path");
|
|
71
118
|
const result = await addExtension(source, dir, subdir ? { subdir } : {});
|
|
72
119
|
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.`);
|
|
@@ -83,28 +130,120 @@ export async function runExtensionCommand(argv, options) {
|
|
|
83
130
|
return fail(`${name} isn't installed${askedScope ? ` in the ${askedScope} scope` : ""}. \`${command} extension list\` lists them.`);
|
|
84
131
|
if (found.length > 1 && !askedScope)
|
|
85
132
|
return fail(`${name} is installed in both scopes: say which, with --project or --agent.`);
|
|
86
|
-
const { scope } = found[0];
|
|
133
|
+
const { scope, dataDir } = found[0];
|
|
87
134
|
const dir = scopeDir(scope);
|
|
88
|
-
if (action
|
|
89
|
-
await removeExtension(dir, name);
|
|
90
|
-
else
|
|
135
|
+
if (action !== "remove") {
|
|
91
136
|
await setExtensionEnabled(dir, name, action === "enable");
|
|
92
|
-
|
|
137
|
+
write(`${name} ${action}d in the ${scope} scope. It applies from the next session (in the chat, \`/extensions\` reopens it).`);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
// Its data (a browser profile, a signed-in session) may be worth keeping for a reinstall: asked, unless said.
|
|
141
|
+
const size = await folderSize(dataDir);
|
|
142
|
+
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)));
|
|
143
|
+
await removeExtension(dir, name, { deleteData });
|
|
144
|
+
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).`);
|
|
93
145
|
return true;
|
|
94
146
|
}
|
|
147
|
+
case "search": {
|
|
148
|
+
const words = positional.map((word) => word.toLowerCase());
|
|
149
|
+
const installed = new Set((await listInstalled(options.dirs)).map((extension) => extension.name));
|
|
150
|
+
const offered = (await knownMarketplaces()).flatMap((marketplace) => (marketplace.manifest?.plugins ?? []).map((plugin) => ({ marketplace, plugin })));
|
|
151
|
+
const matching = offered.filter(({ plugin }) => {
|
|
152
|
+
const text = [plugin.name, plugin.description, plugin.category, ...(plugin.tags ?? []), ...(plugin.keywords ?? [])].join(" ").toLowerCase();
|
|
153
|
+
return words.every((word) => text.includes(word));
|
|
154
|
+
});
|
|
155
|
+
if (!matching.length) {
|
|
156
|
+
write(offered.length ? "No extension matches." : `No marketplace offers extensions yet. \`${command} extension marketplace add <folder | git URL | owner/repo>\` adds one.`);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
for (const { marketplace, plugin } of matching) {
|
|
160
|
+
write(`${plugin.name}@${marketplace.name}${plugin.version ? ` ${plugin.version}` : ""}${installed.has(plugin.name) ? " (installed)" : ""}${plugin.description ? ` ${plugin.description}` : ""}`);
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
case "marketplace":
|
|
165
|
+
return await marketplaceCommand();
|
|
95
166
|
default:
|
|
96
167
|
return fail([
|
|
97
168
|
`Usage: ${command} extension <action>`,
|
|
98
169
|
" list what's installed, in which scope, on or off",
|
|
99
170
|
" info <name> its metadata, what it offers, its README",
|
|
100
|
-
" add <folder | git URL[#ref]> [--project] [--path <subfolder>]",
|
|
101
|
-
"
|
|
171
|
+
" add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>] [--yes]",
|
|
172
|
+
" add <folder> --link [--project] to develop one: runs from its folder, not copied nor checked",
|
|
173
|
+
" remove <name> [--project | --agent] [--yes | --keep-data]",
|
|
174
|
+
" enable|disable <name> [--project | --agent]",
|
|
175
|
+
" search [words] what the known marketplaces offer",
|
|
176
|
+
" marketplace add <folder | git URL[#ref] | owner/repo> [--yes]",
|
|
177
|
+
" marketplace list | update [name] | remove <name>",
|
|
102
178
|
].join("\n"));
|
|
103
179
|
}
|
|
104
180
|
}
|
|
105
181
|
catch (error) {
|
|
106
182
|
return fail(error instanceof Error ? error.message : String(error));
|
|
107
183
|
}
|
|
184
|
+
/** `extension marketplace <add | list | update | remove>`. */
|
|
185
|
+
async function marketplaceCommand() {
|
|
186
|
+
const [what, name] = positional;
|
|
187
|
+
switch (what) {
|
|
188
|
+
case "add": {
|
|
189
|
+
if (!name)
|
|
190
|
+
return fail(`Usage: ${command} extension marketplace add <folder | git URL[#ref] | owner/repo> [--yes]`);
|
|
191
|
+
await knownMarketplaces();
|
|
192
|
+
const manifest = await inspectMarketplace(name);
|
|
193
|
+
write(`The marketplace ${manifest.name}, by ${manifest.owner.name}${manifest.description ? `: ${manifest.description}` : ""} (${manifest.plugins.length} extension${manifest.plugins.length === 1 ? "" : "s"}).`);
|
|
194
|
+
write("Its extensions run on this computer with your permissions, and it isn't this agent's own: add it only if you trust who publishes it.");
|
|
195
|
+
if (!(await confirmed(`Type its name (${manifest.name}) to add it: `, (typed) => typed === manifest.name)))
|
|
196
|
+
return fail("Not added.");
|
|
197
|
+
const result = await addMarketplace(name, marketsDir);
|
|
198
|
+
write(`${result.replaced ? "Updated" : "Added"} the marketplace ${result.name}${result.commit ? `, at ${result.commit.slice(0, 12)}` : ""}. \`${command} extension search\` lists what it offers.`);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
case "list": {
|
|
202
|
+
const known = await knownMarketplaces();
|
|
203
|
+
if (!known.length) {
|
|
204
|
+
write(`No marketplaces. \`${command} extension marketplace add <folder | git URL | owner/repo>\` adds one.`);
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
for (const marketplace of known) {
|
|
208
|
+
write(`${marketplace.name}${marketplace.official ? " (official)" : ""} ${marketplace.manifest ? `${marketplace.manifest.plugins.length} extension${marketplace.manifest.plugins.length === 1 ? "" : "s"}` : "can't be read"} from ${marketplace.source}${marketplace.commit ? ` at ${marketplace.commit.slice(0, 12)}` : ""}, updated ${marketplace.updated.slice(0, 10)}`);
|
|
209
|
+
}
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
case "update": {
|
|
213
|
+
const known = await knownMarketplaces();
|
|
214
|
+
const chosen = name ? known.filter((marketplace) => marketplace.name === name) : known;
|
|
215
|
+
if (name && !chosen.length)
|
|
216
|
+
return fail(`No marketplace ${name}.`);
|
|
217
|
+
for (const marketplace of chosen) {
|
|
218
|
+
const { commit } = await updateMarketplace(marketsDir, marketplace.name);
|
|
219
|
+
write(`Updated ${marketplace.name}${commit ? `, at ${commit.slice(0, 12)}` : ""}. Its installed extensions stay as they are: \`${command} extension add <name>@${marketplace.name}\` installs one again.`);
|
|
220
|
+
}
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
case "remove": {
|
|
224
|
+
if (!name)
|
|
225
|
+
return fail(`Usage: ${command} extension marketplace remove <name>`);
|
|
226
|
+
if (!marketsDir || !(await removeMarketplace(marketsDir, name)))
|
|
227
|
+
return fail(`No marketplace ${name}.`);
|
|
228
|
+
write(`Removed the marketplace ${name}. The extensions installed from it stay.`);
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
default:
|
|
232
|
+
return fail(`Usage: ${command} extension marketplace <add | list | update | remove>`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/** A question on the terminal, or no answer without one. */
|
|
237
|
+
async function askOnTerminal(question) {
|
|
238
|
+
if (!process.stdin.isTTY)
|
|
239
|
+
return "";
|
|
240
|
+
const terminal = createInterface({ input: process.stdin, output: process.stdout });
|
|
241
|
+
try {
|
|
242
|
+
return await terminal.question(question);
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
terminal.close();
|
|
246
|
+
}
|
|
108
247
|
}
|
|
109
248
|
/** Everything an installed extension's manifest says about it, for `extension info`. */
|
|
110
249
|
async function info(extension) {
|
|
@@ -124,10 +263,23 @@ async function info(extension) {
|
|
|
124
263
|
...field("Works with", manifest.kit && `agent-kit ${manifest.kit}`),
|
|
125
264
|
...field("Provides", manifest.provides.join(", ") || undefined),
|
|
126
265
|
...field("Requires", manifest.requires.join(", ") || undefined),
|
|
266
|
+
...field("Needs", manifest.needs.join(", ") || undefined),
|
|
127
267
|
...field("Servers", Object.keys(manifest.servers).join(", ") || undefined),
|
|
128
268
|
...field("Only read", manifest.readOnlyTools.join(", ") || undefined),
|
|
129
269
|
...field("Variables", Object.values(manifest.servers).flatMap((server) => Object.keys(server.env ?? {})).join(", ") || undefined),
|
|
130
270
|
...field("Installed", `${extension.scope} scope, ${extension.lock.enabled ? "enabled" : "disabled"}, on ${extension.lock.installed}, from ${describe(extension).split(" from ")[1]}`),
|
|
271
|
+
...field("Data", (await stat(extension.dataDir).catch(() => null))?.isDirectory() ? `${extension.dataDir} (${formatSize(await folderSize(extension.dataDir))})` : undefined),
|
|
131
272
|
...field("README", (await stat(readme).catch(() => null))?.isFile() ? readme : undefined),
|
|
132
273
|
];
|
|
133
274
|
}
|
|
275
|
+
/** A size in bytes, as a person reads it: `512 B`, `3.4 KB`, `120 MB`. */
|
|
276
|
+
function formatSize(bytes) {
|
|
277
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
278
|
+
let size = bytes;
|
|
279
|
+
let unit = 0;
|
|
280
|
+
while (size >= 1024 && unit < units.length - 1) {
|
|
281
|
+
size /= 1024;
|
|
282
|
+
unit++;
|
|
283
|
+
}
|
|
284
|
+
return `${unit === 0 ? size : size.toFixed(size < 10 ? 1 : 0)} ${units[unit]}`;
|
|
285
|
+
}
|