@falkenslab/agent-kit 0.19.0 → 0.20.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/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 +11 -8
- 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 +21 -12
- package/dist/core/extensions.js +8 -18
- package/dist/core/externalExtensions.d.ts +15 -3
- package/dist/core/externalExtensions.js +33 -5
- package/dist/core/liveExtensions.d.ts +51 -0
- package/dist/core/liveExtensions.js +70 -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 +74 -22
- 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 +6 -1
- package/dist/index.js +5 -0
- package/dist/tui/extensionCommand.d.ts +11 -2
- package/dist/tui/extensionCommand.js +129 -5
- package/package.json +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { PageType } from "./knowledgeStore.js";
|
|
2
2
|
/**
|
|
3
|
-
* The built-in knowledge base (an "LLM wiki"):
|
|
4
|
-
* is the wiki the agent maintains through the `knowledge_*` tools (ADR-024), and the rules below
|
|
3
|
+
* The built-in knowledge base (an "LLM wiki"): the sources folder holds the originals, its own
|
|
4
|
+
* folder is the wiki the agent maintains through the `knowledge_*` tools (ADR-024), and the rules below
|
|
5
5
|
* plus the plugin's skills (knowledge-ingest, knowledge-query, knowledge-lint) and commands
|
|
6
6
|
* (/knowledge:ingest, /knowledge:query, /knowledge:lint) are the schema that tells it how.
|
|
7
|
-
* Domain-agnostic on purpose: an agent adds its own page types (`
|
|
8
|
-
*
|
|
7
|
+
* Domain-agnostic on purpose: an agent adds its own page types (`knowledge({ pageTypes })`), or
|
|
8
|
+
* leaves it out of `AgentSpec.extensions` and keeps notes of its own in `extraWritableDirs`.
|
|
9
9
|
*/
|
|
10
10
|
/** Absolute path of the knowledge base's plugin shipped with the kit, next to `dist/` (or `src/` under tsx). */
|
|
11
11
|
export declare function knowledgePluginRoot(): string;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
/**
|
|
4
|
-
* The built-in knowledge base (an "LLM wiki"):
|
|
5
|
-
* is the wiki the agent maintains through the `knowledge_*` tools (ADR-024), and the rules below
|
|
4
|
+
* The built-in knowledge base (an "LLM wiki"): the sources folder holds the originals, its own
|
|
5
|
+
* folder is the wiki the agent maintains through the `knowledge_*` tools (ADR-024), and the rules below
|
|
6
6
|
* plus the plugin's skills (knowledge-ingest, knowledge-query, knowledge-lint) and commands
|
|
7
7
|
* (/knowledge:ingest, /knowledge:query, /knowledge:lint) are the schema that tells it how.
|
|
8
|
-
* Domain-agnostic on purpose: an agent adds its own page types (`
|
|
9
|
-
*
|
|
8
|
+
* Domain-agnostic on purpose: an agent adds its own page types (`knowledge({ pageTypes })`), or
|
|
9
|
+
* leaves it out of `AgentSpec.extensions` and keeps notes of its own in `extraWritableDirs`.
|
|
10
10
|
*/
|
|
11
11
|
/** Absolute path of the knowledge base's plugin shipped with the kit, next to `dist/` (or `src/` under tsx). */
|
|
12
12
|
export function knowledgePluginRoot() {
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BaseSessionConfig } from "../../core/agentSpec.js";
|
|
2
|
+
import { type Extension, type FolderOption } from "../../core/extensions.js";
|
|
2
3
|
/** Absolute path of the memory extension's plugin, next to `dist/` (or `src/` under tsx). */
|
|
3
4
|
export declare function memoryPluginRoot(): string;
|
|
4
5
|
/** The memory's section of the system prompt: what it is, what it holds now, and its rules. */
|
|
5
6
|
export declare function memoryPromptSection(index: string): string;
|
|
7
|
+
/** The memory's options. */
|
|
8
|
+
export interface MemoryOptions<TConfig extends BaseSessionConfig = BaseSessionConfig> {
|
|
9
|
+
/**
|
|
10
|
+
* Its folder, the agent's own outside any project (e.g. `~/.miyagi/memory`), kept across all
|
|
11
|
+
* the person's projects and never shared with another agent. Without a folder the extension is off.
|
|
12
|
+
*/
|
|
13
|
+
dir: FolderOption<TConfig>;
|
|
14
|
+
}
|
|
6
15
|
/**
|
|
7
|
-
* The memory of the person (
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* a `UserPromptSubmit` hook (and, for a resumed run, from the run's conversation).
|
|
16
|
+
* The memory of the person (#34): what the agent learns about the person it works for, kept
|
|
17
|
+
* across all their projects in a folder of its own, reached only through its tools (`recall`,
|
|
18
|
+
* `remember`, `forget`). What it remembers must quote the person's own messages, which it hears
|
|
19
|
+
* through a `UserPromptSubmit` hook (and, for a resumed run, from the run's conversation).
|
|
11
20
|
*/
|
|
12
|
-
export declare
|
|
21
|
+
export declare function memory<TConfig extends BaseSessionConfig = BaseSessionConfig>(options: MemoryOptions<TConfig>): Extension;
|
|
@@ -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 { readConversation } from "../../core/runs.js";
|
|
4
5
|
import { memoryToolLabels } from "./labels.js";
|
|
5
6
|
import { createMemoryStore } from "./memoryStore.js";
|
|
@@ -22,35 +23,37 @@ ${index}
|
|
|
22
23
|
- Write entries in the language you reply in.`;
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
25
|
-
* The memory of the person (
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* a `UserPromptSubmit` hook (and, for a resumed run, from the run's conversation).
|
|
26
|
+
* The memory of the person (#34): what the agent learns about the person it works for, kept
|
|
27
|
+
* across all their projects in a folder of its own, reached only through its tools (`recall`,
|
|
28
|
+
* `remember`, `forget`). What it remembers must quote the person's own messages, which it hears
|
|
29
|
+
* through a `UserPromptSubmit` hook (and, for a resumed run, from the run's conversation).
|
|
29
30
|
*/
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
31
|
+
export function memory(options) {
|
|
32
|
+
return {
|
|
33
|
+
name: "memory",
|
|
34
|
+
plugin: memoryPluginRoot(),
|
|
35
|
+
missing: ({ config }) => (folderOf(options.dir, config) ? undefined : "needs a folder (`dir`)"),
|
|
36
|
+
async contribute({ config, runDir }) {
|
|
37
|
+
const memoryDir = folderOf(options.dir, config);
|
|
38
|
+
const store = createMemoryStore(memoryDir);
|
|
39
|
+
// What the person wrote: earlier in a resumed run, then each message as it's sent.
|
|
40
|
+
const said = (await readConversation(runDir)).filter((message) => message.role === "user").map((message) => message.text);
|
|
41
|
+
const hear = async (input) => {
|
|
42
|
+
if (input.hook_event_name === "UserPromptSubmit")
|
|
43
|
+
said.push(input.prompt);
|
|
44
|
+
return {};
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
mcpServers: { memory: createMemoryServer(store, said) },
|
|
48
|
+
promptSection: memoryPromptSection(memoryIndex(await store.list())),
|
|
49
|
+
toolOnlyDirs: [{ dir: memoryDir, instead: "its tools: recall, remember, forget" }],
|
|
50
|
+
readOnlyTools: ["mcp__memory__recall"],
|
|
51
|
+
hooks: { UserPromptSubmit: [{ hooks: [hear] }] },
|
|
52
|
+
toolLabels: memoryToolLabels(),
|
|
53
|
+
helpLines: [
|
|
54
|
+
"You keep a memory of the person across all their projects (how they like things done, who they are): `/memory:list` shows it, `/memory:forget <entry>` forgets an entry. Only what they tell you goes in it.",
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BaseSessionConfig } from "../../core/agentSpec.js";
|
|
2
|
+
import { type Extension, type FolderOption } from "../../core/extensions.js";
|
|
2
3
|
/** Absolute path of the sources extension's plugin, next to `dist/` (or `src/` under tsx). */
|
|
3
4
|
export declare function sourcesPluginRoot(): string;
|
|
5
|
+
/** The sources extension's options. */
|
|
6
|
+
export interface SourcesOptions<TConfig extends BaseSessionConfig = BaseSessionConfig> {
|
|
7
|
+
/**
|
|
8
|
+
* The sources folder: original files kept as obtained, apart from any notes. Material the
|
|
9
|
+
* person drops in, plus whatever the agent saves with `save_to_sources` (downloaded documents,
|
|
10
|
+
* transcripts…). The agent reads and searches it, never edits it, and `save_to_sources` never
|
|
11
|
+
* overwrites. Without a folder the extension is off.
|
|
12
|
+
*/
|
|
13
|
+
dir: FolderOption<TConfig>;
|
|
14
|
+
/** The `save_to_sources` tool's description in the agent's own words, instead of the kit's generic one. */
|
|
15
|
+
saveDescription?: string;
|
|
16
|
+
}
|
|
4
17
|
/**
|
|
5
|
-
* The sources folder
|
|
6
|
-
*
|
|
7
|
-
* a knowledge base (#30).
|
|
18
|
+
* The sources folder: originals kept as obtained, read with `Read` and `extract_text`, added and
|
|
19
|
+
* retired only through its tools (`sourceFiles`). It knows nothing of a knowledge base (#30).
|
|
8
20
|
*/
|
|
9
|
-
export declare
|
|
21
|
+
export declare function sources<TConfig extends BaseSessionConfig = BaseSessionConfig>(options: SourcesOptions<TConfig>): Extension;
|
|
@@ -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 {
|
|
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";
|
|
15
19
|
export { addExtension, 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
19
|
export { addExtension, 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,23 @@ 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>]`
|
|
11
|
+
* - `extension add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>] [--yes]`
|
|
12
12
|
* - `extension remove|enable|disable <name> [--project | --agent]`
|
|
13
|
+
* - `extension search [words]`: what the known marketplaces offer
|
|
14
|
+
* - `extension marketplace add <folder | git URL[#ref] | owner/repo> [--yes]`, `list`, `update [name]`, `remove <name>`
|
|
13
15
|
*
|
|
14
16
|
* 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.
|
|
17
|
+
* its own); the others act where the extension is, the project's first. The marketplaces live in
|
|
18
|
+
* the same folder. `official` is the agent's own marketplace (a folder or git URL): known without
|
|
19
|
+
* asking, and installed from without a confirmation. Any other is added after a warning and
|
|
20
|
+
* typing its name, and installing from it asks first; `--yes` answers for the person (a script).
|
|
16
21
|
*/
|
|
17
22
|
export declare function runExtensionCommand(argv: readonly string[], options: {
|
|
18
23
|
dirs: ExtensionDirs;
|
|
19
24
|
command?: string;
|
|
20
25
|
write?: (line: string) => void;
|
|
26
|
+
/** The agent's official marketplace: a folder or a git URL (`#ref`). */
|
|
27
|
+
official?: string;
|
|
28
|
+
/** Asks the person (a confirmation); by default on the terminal, and nothing without one. */
|
|
29
|
+
ask?: (question: string) => Promise<string>;
|
|
21
30
|
}): 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, 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,16 @@ 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>]`
|
|
16
|
+
* - `extension add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>] [--yes]`
|
|
15
17
|
* - `extension remove|enable|disable <name> [--project | --agent]`
|
|
18
|
+
* - `extension search [words]`: what the known marketplaces offer
|
|
19
|
+
* - `extension marketplace add <folder | git URL[#ref] | owner/repo> [--yes]`, `list`, `update [name]`, `remove <name>`
|
|
16
20
|
*
|
|
17
21
|
* 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.
|
|
22
|
+
* its own); the others act where the extension is, the project's first. The marketplaces live in
|
|
23
|
+
* the same folder. `official` is the agent's own marketplace (a folder or git URL): known without
|
|
24
|
+
* asking, and installed from without a confirmation. Any other is added after a warning and
|
|
25
|
+
* typing its name, and installing from it asks first; `--yes` answers for the person (a script).
|
|
19
26
|
*/
|
|
20
27
|
export async function runExtensionCommand(argv, options) {
|
|
21
28
|
if (argv[0] !== "extension")
|
|
@@ -36,6 +43,22 @@ export async function runExtensionCommand(argv, options) {
|
|
|
36
43
|
return true;
|
|
37
44
|
};
|
|
38
45
|
const scopeDir = (scope) => options.dirs[scope];
|
|
46
|
+
// The marketplaces live with the agent's extensions (the project's when it has no scope of its own).
|
|
47
|
+
const marketsDir = options.dirs.agent ?? options.dirs.project;
|
|
48
|
+
const ask = options.ask ?? askOnTerminal;
|
|
49
|
+
const confirmed = async (question, accepts) => flag("yes") || accepts((await ask(question)).trim());
|
|
50
|
+
// The agent's own marketplace, known without asking; a folder is copied afresh each time (it's the agent's own code).
|
|
51
|
+
const knownMarketplaces = async () => {
|
|
52
|
+
if (!marketsDir)
|
|
53
|
+
throw new Error("This agent has no folder for extensions.");
|
|
54
|
+
const known = await listMarketplaces(marketsDir);
|
|
55
|
+
const official = options.official;
|
|
56
|
+
if (official && !(isGitSource(official) && known.some((marketplace) => marketplace.official))) {
|
|
57
|
+
await addMarketplace(official, marketsDir, { official: true });
|
|
58
|
+
return listMarketplaces(marketsDir);
|
|
59
|
+
}
|
|
60
|
+
return known;
|
|
61
|
+
};
|
|
39
62
|
try {
|
|
40
63
|
switch (action) {
|
|
41
64
|
case "list": {
|
|
@@ -62,11 +85,26 @@ export async function runExtensionCommand(argv, options) {
|
|
|
62
85
|
case "add": {
|
|
63
86
|
const source = positional[0];
|
|
64
87
|
if (!source)
|
|
65
|
-
return fail(`Usage: ${command} extension add <folder | git URL[#ref]> [--project] [--path <subfolder>]`);
|
|
88
|
+
return fail(`Usage: ${command} extension add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>]`);
|
|
66
89
|
const scope = askedScope ?? (options.dirs.agent ? "agent" : "project");
|
|
67
90
|
const dir = scopeDir(scope);
|
|
68
91
|
if (!dir)
|
|
69
92
|
return fail(`This agent has no ${scope} scope for extensions.`);
|
|
93
|
+
// A plugin of a marketplace, unless it's a folder here or a git URL.
|
|
94
|
+
const isFolder = await stat(path.resolve(source)).then((found) => found.isDirectory(), () => false);
|
|
95
|
+
if (!isFolder && !isGitSource(source) && /^[A-Za-z0-9][\w.-]*(@[A-Za-z0-9][\w.-]*)?$/.test(source)) {
|
|
96
|
+
await knownMarketplaces();
|
|
97
|
+
const { marketplace, plugin } = await findPlugin(marketsDir, source);
|
|
98
|
+
if (!marketplace.official) {
|
|
99
|
+
write(`${plugin.name}${plugin.version ? ` ${plugin.version}` : ""}, from the marketplace ${marketplace.name} (${marketplace.source}): ${plugin.description ?? "no description"}`);
|
|
100
|
+
write("It will run on this computer with your permissions.");
|
|
101
|
+
if (!(await confirmed("Install it? (y/N) ", (typed) => /^(y|yes|s|si|sí)$/i.test(typed))))
|
|
102
|
+
return fail("Not installed.");
|
|
103
|
+
}
|
|
104
|
+
const result = await installFromMarketplace(marketsDir, source, dir);
|
|
105
|
+
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
|
+
return true;
|
|
107
|
+
}
|
|
70
108
|
const subdir = value("path");
|
|
71
109
|
const result = await addExtension(source, dir, subdir ? { subdir } : {});
|
|
72
110
|
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.`);
|
|
@@ -92,19 +130,104 @@ export async function runExtensionCommand(argv, options) {
|
|
|
92
130
|
write(`${name} ${action === "remove" ? "removed" : `${action}d`} in the ${scope} scope. It applies from the next session (in the chat, \`/extensions\` reopens it).`);
|
|
93
131
|
return true;
|
|
94
132
|
}
|
|
133
|
+
case "search": {
|
|
134
|
+
const words = positional.map((word) => word.toLowerCase());
|
|
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
|
+
});
|
|
141
|
+
if (!matching.length) {
|
|
142
|
+
write(offered.length ? "No extension matches." : `No marketplace offers extensions yet. \`${command} extension marketplace add <folder | git URL | owner/repo>\` adds one.`);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
for (const { marketplace, plugin } of matching) {
|
|
146
|
+
write(`${plugin.name}@${marketplace.name}${plugin.version ? ` ${plugin.version}` : ""}${installed.has(plugin.name) ? " (installed)" : ""}${plugin.description ? ` ${plugin.description}` : ""}`);
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
case "marketplace":
|
|
151
|
+
return await marketplaceCommand();
|
|
95
152
|
default:
|
|
96
153
|
return fail([
|
|
97
154
|
`Usage: ${command} extension <action>`,
|
|
98
155
|
" list what's installed, in which scope, on or off",
|
|
99
156
|
" info <name> its metadata, what it offers, its README",
|
|
100
|
-
" add <folder | git URL[#ref]> [--project] [--path <subfolder>]",
|
|
157
|
+
" add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>] [--yes]",
|
|
101
158
|
" remove|enable|disable <name> [--project | --agent]",
|
|
159
|
+
" search [words] what the known marketplaces offer",
|
|
160
|
+
" marketplace add <folder | git URL[#ref] | owner/repo> [--yes]",
|
|
161
|
+
" marketplace list | update [name] | remove <name>",
|
|
102
162
|
].join("\n"));
|
|
103
163
|
}
|
|
104
164
|
}
|
|
105
165
|
catch (error) {
|
|
106
166
|
return fail(error instanceof Error ? error.message : String(error));
|
|
107
167
|
}
|
|
168
|
+
/** `extension marketplace <add | list | update | remove>`. */
|
|
169
|
+
async function marketplaceCommand() {
|
|
170
|
+
const [what, name] = positional;
|
|
171
|
+
switch (what) {
|
|
172
|
+
case "add": {
|
|
173
|
+
if (!name)
|
|
174
|
+
return fail(`Usage: ${command} extension marketplace add <folder | git URL[#ref] | owner/repo> [--yes]`);
|
|
175
|
+
await knownMarketplaces();
|
|
176
|
+
const manifest = await inspectMarketplace(name);
|
|
177
|
+
write(`The marketplace ${manifest.name}, by ${manifest.owner.name}${manifest.description ? `: ${manifest.description}` : ""} (${manifest.plugins.length} extension${manifest.plugins.length === 1 ? "" : "s"}).`);
|
|
178
|
+
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.");
|
|
179
|
+
if (!(await confirmed(`Type its name (${manifest.name}) to add it: `, (typed) => typed === manifest.name)))
|
|
180
|
+
return fail("Not added.");
|
|
181
|
+
const result = await addMarketplace(name, marketsDir);
|
|
182
|
+
write(`${result.replaced ? "Updated" : "Added"} the marketplace ${result.name}${result.commit ? `, at ${result.commit.slice(0, 12)}` : ""}. \`${command} extension search\` lists what it offers.`);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
case "list": {
|
|
186
|
+
const known = await knownMarketplaces();
|
|
187
|
+
if (!known.length) {
|
|
188
|
+
write(`No marketplaces. \`${command} extension marketplace add <folder | git URL | owner/repo>\` adds one.`);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
for (const marketplace of known) {
|
|
192
|
+
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)}`);
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
case "update": {
|
|
197
|
+
const known = await knownMarketplaces();
|
|
198
|
+
const chosen = name ? known.filter((marketplace) => marketplace.name === name) : known;
|
|
199
|
+
if (name && !chosen.length)
|
|
200
|
+
return fail(`No marketplace ${name}.`);
|
|
201
|
+
for (const marketplace of chosen) {
|
|
202
|
+
const { commit } = await updateMarketplace(marketsDir, marketplace.name);
|
|
203
|
+
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.`);
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
case "remove": {
|
|
208
|
+
if (!name)
|
|
209
|
+
return fail(`Usage: ${command} extension marketplace remove <name>`);
|
|
210
|
+
if (!marketsDir || !(await removeMarketplace(marketsDir, name)))
|
|
211
|
+
return fail(`No marketplace ${name}.`);
|
|
212
|
+
write(`Removed the marketplace ${name}. The extensions installed from it stay.`);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
default:
|
|
216
|
+
return fail(`Usage: ${command} extension marketplace <add | list | update | remove>`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/** A question on the terminal, or no answer without one. */
|
|
221
|
+
async function askOnTerminal(question) {
|
|
222
|
+
if (!process.stdin.isTTY)
|
|
223
|
+
return "";
|
|
224
|
+
const terminal = createInterface({ input: process.stdin, output: process.stdout });
|
|
225
|
+
try {
|
|
226
|
+
return await terminal.question(question);
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
terminal.close();
|
|
230
|
+
}
|
|
108
231
|
}
|
|
109
232
|
/** Everything an installed extension's manifest says about it, for `extension info`. */
|
|
110
233
|
async function info(extension) {
|
|
@@ -124,6 +247,7 @@ async function info(extension) {
|
|
|
124
247
|
...field("Works with", manifest.kit && `agent-kit ${manifest.kit}`),
|
|
125
248
|
...field("Provides", manifest.provides.join(", ") || undefined),
|
|
126
249
|
...field("Requires", manifest.requires.join(", ") || undefined),
|
|
250
|
+
...field("Needs", manifest.needs.join(", ") || undefined),
|
|
127
251
|
...field("Servers", Object.keys(manifest.servers).join(", ") || undefined),
|
|
128
252
|
...field("Only read", manifest.readOnlyTools.join(", ") || undefined),
|
|
129
253
|
...field("Variables", Object.values(manifest.servers).flatMap((server) => Object.keys(server.env ?? {})).join(", ") || undefined),
|