@falkenslab/agent-kit 0.21.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 +28 -5
- 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 +62 -3
- 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 +7 -1
- package/dist/core/extensions.js +2 -1
- package/dist/core/externalExtensions.d.ts +83 -8
- package/dist/core/externalExtensions.js +233 -30
- 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 +11 -0
- package/dist/core/liveExtensions.js +22 -0
- 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 +31 -7
- 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 +10 -2
- package/dist/tui/extensionCommand.js +182 -33
- 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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { readLock } from "./externalExtensions.js";
|
|
5
|
+
/**
|
|
6
|
+
* Trusting a workspace's extensions (#57): a project's scope comes with the project, from whoever
|
|
7
|
+
* shared it (a clone, a download), and its extensions' servers run code on this computer. They
|
|
8
|
+
* run only once the person trusts them in that workspace: what's trusted is each one's code (its
|
|
9
|
+
* files' SHA-256 and its npx packages'), kept in the agent's folder, outside the project, so a
|
|
10
|
+
* project can't trust itself. Another version (a changed lock, an update from someone else) asks
|
|
11
|
+
* again; turning one on or off, or its options, don't. What the person installs into the
|
|
12
|
+
* project's scope themselves is trusted as they install it. The agent's own scope is the
|
|
13
|
+
* person's: it never asks.
|
|
14
|
+
*/
|
|
15
|
+
const TRUST_FILE = ".trust.json";
|
|
16
|
+
/** What's trusted of an extension: its code, its files' hash and its npx packages'. */
|
|
17
|
+
export function codeHash(lock) {
|
|
18
|
+
const packages = Object.entries(lock.packages ?? {})
|
|
19
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
20
|
+
.map(([spec, cached]) => `${spec}=${cached.sha256}`);
|
|
21
|
+
// A linked one has no hash of its own: its folder is what's trusted.
|
|
22
|
+
return createHash("sha256").update([lock.linked ? `linked:${path.resolve(lock.source)}` : lock.sha256, ...packages].join("\n")).digest("hex");
|
|
23
|
+
}
|
|
24
|
+
async function readTrust(agentDir) {
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(await readFile(path.join(agentDir, TRUST_FILE), "utf8"));
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error.code === "ENOENT")
|
|
30
|
+
return {};
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** The workspace a project scope is, as the trust file names it. */
|
|
35
|
+
const workspaceKey = (projectScope) => path.resolve(projectScope);
|
|
36
|
+
/** Whether an extension of a project's scope is trusted in that workspace, with the code its lock has now. */
|
|
37
|
+
export async function isTrusted(agentDir, projectScope, name, lock) {
|
|
38
|
+
return (await readTrust(agentDir))[workspaceKey(projectScope)]?.[name] === codeHash(lock);
|
|
39
|
+
}
|
|
40
|
+
/** Trusts these extensions of a project's scope, with the code their lock has now. */
|
|
41
|
+
export async function trust(agentDir, projectScope, entries) {
|
|
42
|
+
const file = await readTrust(agentDir);
|
|
43
|
+
const workspace = (file[workspaceKey(projectScope)] ??= {});
|
|
44
|
+
for (const [name, lock] of Object.entries(entries))
|
|
45
|
+
workspace[name] = codeHash(lock);
|
|
46
|
+
await mkdir(agentDir, { recursive: true });
|
|
47
|
+
await writeFile(path.join(agentDir, TRUST_FILE), `${JSON.stringify(file, null, 2)}\n`, "utf8");
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Trusts a workspace's extensions (its project scope's, all or `names`) with the code its lock
|
|
51
|
+
* has now: what the person does when the chat asks, or with `extension trust`. The names trusted.
|
|
52
|
+
*/
|
|
53
|
+
export async function trustWorkspace(dirs, names) {
|
|
54
|
+
if (!dirs.agent || !dirs.project)
|
|
55
|
+
return [];
|
|
56
|
+
const lock = await readLock(dirs.project);
|
|
57
|
+
const entries = Object.fromEntries(Object.entries(lock).filter(([name]) => !names || names.includes(name)));
|
|
58
|
+
await trust(dirs.agent, dirs.project, entries);
|
|
59
|
+
return Object.keys(entries);
|
|
60
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -16,8 +16,13 @@ export { sources, type SourcesOptions } from "./extensions/sources/index.js";
|
|
|
16
16
|
export { knowledge, type KnowledgeOptions } from "./extensions/knowledge/index.js";
|
|
17
17
|
export { memory, type MemoryOptions } from "./extensions/memory/index.js";
|
|
18
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";
|
|
19
|
+
export { addExtension, codeStoreDir, installTarget, scopePrivateDir, extensionDataDir, extensionOptionStates, setExtensionOptions, removeExtension, setExtensionEnabled, listInstalled, readExternalManifest, type ExtensionAuthor, type ExtensionDirs, type ExtensionScope, type ExternalManifest, type ExternalToolLabel, type PluginServer, type InstalledExtension, type LockEntry, type ExtensionOptionState, } from "./core/externalExtensions.js";
|
|
20
|
+
export type { ExtensionOption, OptionValue } from "./core/extensionOptions.js";
|
|
21
|
+
export type { ExtensionRestart, ExtensionSwitch } from "./core/liveExtensions.js";
|
|
22
|
+
export { packageCacheDir, type CachedPackage } from "./core/npxPackages.js";
|
|
20
23
|
export { addMarketplace, findPlugin, inspectMarketplace, installFromMarketplace, listMarketplaces, readMarketplaceManifest, removeMarketplace, resolvePluginSource, updateMarketplace, type KnownMarketplace, type MarketplaceManifest, type MarketplacePlugin, type PluginSource, } from "./core/marketplaces.js";
|
|
24
|
+
export { extensionCatalog, installExtension, restoreExtensions, updateExtension, type CatalogEntry } from "./core/extensionCatalog.js";
|
|
25
|
+
export { trustWorkspace } from "./core/workspaceTrust.js";
|
|
21
26
|
export { runExtensionCommand } from "./tui/extensionCommand.js";
|
|
22
27
|
export { agentKitVersion } from "./core/version.js";
|
|
23
28
|
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";
|
|
@@ -37,6 +42,7 @@ export { createFileScopeGate, checkFileScope, type FileScope } from "./core/hook
|
|
|
37
42
|
export { createPlanGate, checkPlanScope, type PlanScope } from "./core/hooks/planGate.js";
|
|
38
43
|
export { askForDecision, askForManualIntervention, askForChoice, askForText, type ChoiceAnswer } from "./core/hooks/humanInput.js";
|
|
39
44
|
export { setInteractionPort, getInteractionPort, type InteractionPort, type ApprovalPrompt, type ChoiceSettings } from "./core/interaction.js";
|
|
45
|
+
export { formFields, type FormAnswer, type FormField, type FormRequest } from "./core/elicitation.js";
|
|
40
46
|
export { terminalInteractionPort, setSharedReadline, getSharedReadline } from "./tui/terminalInteraction.js";
|
|
41
47
|
export { createHumanApprovalServer, DEFAULT_HUMAN_APPROVAL_TEXTS, type HumanApprovalTexts } from "./core/tools/humanApproval.js";
|
|
42
48
|
export { createManualLoginServer, type ManualInterventionTexts } from "./core/tools/manualLogin.js";
|
package/dist/index.js
CHANGED
|
@@ -16,8 +16,11 @@ export { sources } from "./extensions/sources/index.js";
|
|
|
16
16
|
export { knowledge } from "./extensions/knowledge/index.js";
|
|
17
17
|
export { memory } from "./extensions/memory/index.js";
|
|
18
18
|
export { readExtensionManifest, } from "./core/extensions.js";
|
|
19
|
-
export { addExtension, extensionDataDir, removeExtension, setExtensionEnabled, listInstalled, readExternalManifest, } from "./core/externalExtensions.js";
|
|
19
|
+
export { addExtension, codeStoreDir, installTarget, scopePrivateDir, extensionDataDir, extensionOptionStates, setExtensionOptions, removeExtension, setExtensionEnabled, listInstalled, readExternalManifest, } from "./core/externalExtensions.js";
|
|
20
|
+
export { packageCacheDir } from "./core/npxPackages.js";
|
|
20
21
|
export { addMarketplace, findPlugin, inspectMarketplace, installFromMarketplace, listMarketplaces, readMarketplaceManifest, removeMarketplace, resolvePluginSource, updateMarketplace, } from "./core/marketplaces.js";
|
|
22
|
+
export { extensionCatalog, installExtension, restoreExtensions, updateExtension } from "./core/extensionCatalog.js";
|
|
23
|
+
export { trustWorkspace } from "./core/workspaceTrust.js";
|
|
21
24
|
export { runExtensionCommand } from "./tui/extensionCommand.js";
|
|
22
25
|
export { agentKitVersion } from "./core/version.js";
|
|
23
26
|
export { BUILT_IN_PAGE_TYPES, isSlug, parseId, linkedIds, } from "./extensions/knowledge/knowledgeStore.js";
|
|
@@ -38,6 +41,7 @@ export { createFileScopeGate, checkFileScope } from "./core/hooks/fileScopeGate.
|
|
|
38
41
|
export { createPlanGate, checkPlanScope } from "./core/hooks/planGate.js";
|
|
39
42
|
export { askForDecision, askForManualIntervention, askForChoice, askForText } from "./core/hooks/humanInput.js";
|
|
40
43
|
export { setInteractionPort, getInteractionPort } from "./core/interaction.js";
|
|
44
|
+
export { formFields } from "./core/elicitation.js";
|
|
41
45
|
export { terminalInteractionPort, setSharedReadline, getSharedReadline } from "./tui/terminalInteraction.js";
|
|
42
46
|
// The checkpoints answer on the terminal by default, as they always have; a non-terminal
|
|
43
47
|
// host replaces the port with its own (or null, to answer through the response file only).
|
|
@@ -8,10 +8,14 @@ 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] | plugin[@marketplace]> [--project] [--path <subfolder>] [--yes]`
|
|
11
|
+
* - `extension add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>] [--set key=value…] [--yes]`: asks for its options (#55) unless `--set` gives them
|
|
12
12
|
* - `extension add <folder> --link [--project]`: registered where it is, to develop it (not copied, its files not checked)
|
|
13
13
|
* - `extension remove <name> [--project | --agent] [--yes | --keep-data]`: asks before deleting its data folder
|
|
14
14
|
* - `extension enable|disable <name> [--project | --agent]`
|
|
15
|
+
* - `extension config <name> [key=value… | key… | --unset key…] [--project | --agent]`: its options; `key` alone asks for it (without echo when it's a secret)
|
|
16
|
+
* - `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
|
|
17
|
+
* - `extension restore [--trust]`: gets the code a workspace's lock lists and this computer lacks (#57); `--trust` trusts it too
|
|
18
|
+
* - `extension trust [name…]`: lets a workspace's extensions run, as their code is now
|
|
15
19
|
* - `extension search [words]`: what the known marketplaces offer
|
|
16
20
|
* - `extension marketplace add <folder | git URL[#ref] | owner/repo> [--yes]`, `list`, `update [name]`, `remove <name>`
|
|
17
21
|
*
|
|
@@ -27,6 +31,10 @@ export declare function runExtensionCommand(argv: readonly string[], options: {
|
|
|
27
31
|
write?: (line: string) => void;
|
|
28
32
|
/** The agent's official marketplace: a folder or a git URL (`#ref`). */
|
|
29
33
|
official?: string;
|
|
30
|
-
/**
|
|
34
|
+
/** The topics of the extensions `search` shows (#59), as the agent's `catalogTopics`; without it, every one. */
|
|
35
|
+
catalogTopics?: string[];
|
|
36
|
+
/** Asks the person (a confirmation, an option's value); by default on the terminal, and nothing without one. */
|
|
31
37
|
ask?: (question: string) => Promise<string>;
|
|
38
|
+
/** Asks for a secret (a sensitive option), without echo; by default on the terminal. */
|
|
39
|
+
askSecret?: (question: string) => Promise<string>;
|
|
32
40
|
}): Promise<boolean>;
|
|
@@ -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, folderSize, 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,10 +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]`
|
|
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
|
|
17
19
|
* - `extension add <folder> --link [--project]`: registered where it is, to develop it (not copied, its files not checked)
|
|
18
20
|
* - `extension remove <name> [--project | --agent] [--yes | --keep-data]`: asks before deleting its data folder
|
|
19
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
|
|
20
26
|
* - `extension search [words]`: what the known marketplaces offer
|
|
21
27
|
* - `extension marketplace add <folder | git URL[#ref] | owner/repo> [--yes]`, `list`, `update [name]`, `remove <name>`
|
|
22
28
|
*
|
|
@@ -37,7 +43,8 @@ export async function runExtensionCommand(argv, options) {
|
|
|
37
43
|
const index = rest.indexOf(`--${name}`);
|
|
38
44
|
return index >= 0 ? rest[index + 1] : undefined;
|
|
39
45
|
};
|
|
40
|
-
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]));
|
|
41
48
|
const askedScope = flag("project") ? "project" : flag("agent") ? "agent" : undefined;
|
|
42
49
|
const fail = (message) => {
|
|
43
50
|
write(message);
|
|
@@ -48,19 +55,41 @@ export async function runExtensionCommand(argv, options) {
|
|
|
48
55
|
// The marketplaces live with the agent's extensions (the project's when it has no scope of its own).
|
|
49
56
|
const marketsDir = options.dirs.agent ?? options.dirs.project;
|
|
50
57
|
const ask = options.ask ?? askOnTerminal;
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
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;
|
|
61
83
|
}
|
|
62
|
-
|
|
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]}\`.`);
|
|
63
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);
|
|
64
93
|
try {
|
|
65
94
|
switch (action) {
|
|
66
95
|
case "list": {
|
|
@@ -92,12 +121,19 @@ export async function runExtensionCommand(argv, options) {
|
|
|
92
121
|
const dir = scopeDir(scope);
|
|
93
122
|
if (!dir)
|
|
94
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])));
|
|
95
129
|
// A plugin of a marketplace, unless it's a folder here or a git URL.
|
|
96
130
|
const isFolder = await stat(path.resolve(source)).then((found) => found.isDirectory(), () => false);
|
|
97
131
|
if (flag("link")) {
|
|
98
132
|
if (!isFolder)
|
|
99
133
|
return fail(`Only a folder can be linked: ${source} isn't one here.`);
|
|
100
|
-
const result = await addExtension(source, dir, { link: true });
|
|
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);
|
|
101
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).`);
|
|
102
138
|
return true;
|
|
103
139
|
}
|
|
@@ -110,12 +146,16 @@ export async function runExtensionCommand(argv, options) {
|
|
|
110
146
|
if (!(await confirmed("Install it? (y/N) ", (typed) => /^(y|yes|s|si|sí)$/i.test(typed))))
|
|
111
147
|
return fail("Not installed.");
|
|
112
148
|
}
|
|
113
|
-
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);
|
|
114
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.`);
|
|
115
153
|
return true;
|
|
116
154
|
}
|
|
117
155
|
const subdir = value("path");
|
|
118
|
-
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);
|
|
119
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.`);
|
|
120
160
|
return true;
|
|
121
161
|
}
|
|
@@ -130,7 +170,7 @@ export async function runExtensionCommand(argv, options) {
|
|
|
130
170
|
return fail(`${name} isn't installed${askedScope ? ` in the ${askedScope} scope` : ""}. \`${command} extension list\` lists them.`);
|
|
131
171
|
if (found.length > 1 && !askedScope)
|
|
132
172
|
return fail(`${name} is installed in both scopes: say which, with --project or --agent.`);
|
|
133
|
-
const { scope, dataDir } = found[0];
|
|
173
|
+
const { scope, dataDir, privateDir } = found[0];
|
|
134
174
|
const dir = scopeDir(scope);
|
|
135
175
|
if (action !== "remove") {
|
|
136
176
|
await setExtensionEnabled(dir, name, action === "enable");
|
|
@@ -140,24 +180,95 @@ export async function runExtensionCommand(argv, options) {
|
|
|
140
180
|
// Its data (a browser profile, a signed-in session) may be worth keeping for a reinstall: asked, unless said.
|
|
141
181
|
const size = await folderSize(dataDir);
|
|
142
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)));
|
|
143
|
-
await removeExtension(dir, name, { deleteData });
|
|
183
|
+
await removeExtension(dir, name, { deleteData, privateDir });
|
|
144
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).`);
|
|
145
185
|
return true;
|
|
146
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.`);
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
147
248
|
case "search": {
|
|
148
|
-
const
|
|
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
|
-
});
|
|
249
|
+
const matching = await extensionCatalog(options.dirs, { ...(options.official ? { official: options.official } : {}), ...(options.catalogTopics?.length ? { topics: options.catalogTopics } : {}), words: positional });
|
|
155
250
|
if (!matching.length) {
|
|
156
|
-
|
|
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.`);
|
|
157
253
|
return true;
|
|
158
254
|
}
|
|
159
|
-
for (const
|
|
160
|
-
|
|
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.`);
|
|
161
272
|
}
|
|
162
273
|
return true;
|
|
163
274
|
}
|
|
@@ -168,10 +279,14 @@ export async function runExtensionCommand(argv, options) {
|
|
|
168
279
|
`Usage: ${command} extension <action>`,
|
|
169
280
|
" list what's installed, in which scope, on or off",
|
|
170
281
|
" info <name> its metadata, what it offers, its README",
|
|
171
|
-
" add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>] [--yes]",
|
|
282
|
+
" add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>] [--set key=value…] [--yes]",
|
|
172
283
|
" add <folder> --link [--project] to develop one: runs from its folder, not copied nor checked",
|
|
173
284
|
" remove <name> [--project | --agent] [--yes | --keep-data]",
|
|
174
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",
|
|
175
290
|
" search [words] what the known marketplaces offer",
|
|
176
291
|
" marketplace add <folder | git URL[#ref] | owner/repo> [--yes]",
|
|
177
292
|
" marketplace list | update [name] | remove <name>",
|
|
@@ -234,6 +349,32 @@ export async function runExtensionCommand(argv, options) {
|
|
|
234
349
|
}
|
|
235
350
|
}
|
|
236
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
|
+
}
|
|
237
378
|
async function askOnTerminal(question) {
|
|
238
379
|
if (!process.stdin.isTTY)
|
|
239
380
|
return "";
|
|
@@ -265,7 +406,15 @@ async function info(extension) {
|
|
|
265
406
|
...field("Requires", manifest.requires.join(", ") || undefined),
|
|
266
407
|
...field("Needs", manifest.needs.join(", ") || undefined),
|
|
267
408
|
...field("Servers", Object.keys(manifest.servers).join(", ") || undefined),
|
|
268
|
-
...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),
|
|
269
418
|
...field("Variables", Object.values(manifest.servers).flatMap((server) => Object.keys(server.env ?? {})).join(", ") || undefined),
|
|
270
419
|
...field("Installed", `${extension.scope} scope, ${extension.lock.enabled ? "enabled" : "disabled"}, on ${extension.lock.installed}, from ${describe(extension).split(" from ")[1]}`),
|
|
271
420
|
...field("Data", (await stat(extension.dataDir).catch(() => null))?.isDirectory() ? `${extension.dataDir} (${formatSize(await folderSize(extension.dataDir))})` : undefined),
|
|
@@ -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": {
|