@falkenslab/agent-kit 0.23.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/chat/chatController.d.ts +10 -0
- package/dist/chat/chatController.js +39 -1
- package/dist/chat/extensions.d.ts +9 -0
- package/dist/chat/extensions.js +14 -2
- package/dist/chat/runs.d.ts +3 -1
- package/dist/chat/runs.js +1 -1
- package/dist/core/extensionCatalog.d.ts +8 -1
- package/dist/core/extensionCatalog.js +13 -9
- package/dist/core/extensionOptions.d.ts +5 -0
- package/dist/core/extensionOptions.js +10 -0
- package/dist/core/extensions.d.ts +13 -0
- package/dist/core/extensions.js +13 -0
- package/dist/core/externalExtensions.d.ts +27 -1
- package/dist/core/externalExtensions.js +80 -13
- package/dist/core/liveExtensions.d.ts +38 -0
- package/dist/core/liveExtensions.js +69 -0
- package/dist/core/marketplaces.d.ts +1 -0
- package/dist/core/messages/de.js +10 -1
- package/dist/core/messages/en.d.ts +14 -0
- package/dist/core/messages/en.js +10 -1
- package/dist/core/messages/es.js +10 -1
- package/dist/core/messages/fr.js +10 -1
- package/dist/core/npxPackages.d.ts +2 -2
- package/dist/core/runner.js +17 -0
- package/dist/core/session.d.ts +6 -2
- package/dist/core/session.js +5 -2
- package/dist/core/sessionFacts.d.ts +14 -0
- package/dist/extensions/awareness/catalog.d.ts +8 -0
- package/dist/extensions/awareness/catalog.js +92 -0
- package/dist/extensions/awareness/index.d.ts +3 -1
- package/dist/extensions/awareness/index.js +15 -6
- package/dist/extensions/awareness/labels.js +8 -5
- package/dist/extensions/awareness/tools.d.ts +2 -2
- package/dist/extensions/awareness/tools.js +4 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/tui/extensionCommand.js +17 -9
- package/extensions/awareness/skills/extensions/SKILL.md +56 -0
- package/package.json +1 -1
- package/schemas/agent-kit-metadata.schema.json +6 -0
- package/schemas/marketplace-catalog.schema.json +21 -1
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](https://falkenslab.github.io/agent-kit/)
|
|
11
11
|
|
|
12
|
+
**[📖 Documentation](https://falkenslab.github.io/agent-kit/)** · [📖 Documentación en español](https://falkenslab.github.io/agent-kit/es/)
|
|
13
|
+
|
|
12
14
|
A foundation for building AI agents on top of the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk). It isn't an agent itself: it solves once what every agent needs (human oversight, safety, memory and a good terminal interface), so each concrete agent only has to write what belongs to its own domain: what it knows how to do, with which tools, and how it talks.
|
|
13
15
|
|
|
14
16
|
## What an agent built with it can do
|
|
@@ -9,6 +9,7 @@ import { type ExtensionsStatus, type ModeControl } from "../core/session.js";
|
|
|
9
9
|
import { type Todo } from "../core/todos.js";
|
|
10
10
|
import { type ToolLabels } from "../core/toolLabels.js";
|
|
11
11
|
import type { ToolPhrase } from "../core/messages/index.js";
|
|
12
|
+
import type { SignIn } from "../core/liveExtensions.js";
|
|
12
13
|
import { type ExtensionOptionState, type ExtensionScope } from "../core/externalExtensions.js";
|
|
13
14
|
import { type CatalogEntry } from "../core/extensionCatalog.js";
|
|
14
15
|
import { type SessionOpener } from "./runs.js";
|
|
@@ -268,12 +269,21 @@ export declare function createChatController(options: Options | SessionOpener, s
|
|
|
268
269
|
* installed until `confirmed`: the result says which (`confirm`), for the view to ask the
|
|
269
270
|
* person. The running session is untouched: it's there in a new conversation, or with `reopen()`.
|
|
270
271
|
*/
|
|
272
|
+
/**
|
|
273
|
+
* Signs in to an installed extension's remote servers (#61): gives the person the links (never
|
|
274
|
+
* opens a browser), then says when the sign-in finishes. With `callbackUrl`, the address the
|
|
275
|
+
* browser ended on (from another computer), finishes it instead.
|
|
276
|
+
*/
|
|
277
|
+
signInExtension(name: string, callbackUrl?: string): Promise<SignIn>;
|
|
278
|
+
/** Signs out of an installed extension's remote servers (#61). */
|
|
279
|
+
signOutExtension(name: string): Promise<boolean>;
|
|
271
280
|
installExtension(spec: string, options?: {
|
|
272
281
|
scope?: ExtensionScope;
|
|
273
282
|
confirmed?: boolean;
|
|
274
283
|
}): Promise<{
|
|
275
284
|
name?: string;
|
|
276
285
|
missing?: string[];
|
|
286
|
+
remote?: string[];
|
|
277
287
|
confirm?: {
|
|
278
288
|
marketplace: string;
|
|
279
289
|
description?: string;
|
|
@@ -315,6 +315,10 @@ export async function createChatController(options, settings = {}) {
|
|
|
315
315
|
await controller.removeExtension(action.name, { deleteData: action.deleteData });
|
|
316
316
|
else if (action.type === "trust")
|
|
317
317
|
await controller.trustWorkspace();
|
|
318
|
+
else if (action.type === "sign-in")
|
|
319
|
+
await controller.signInExtension(action.name, action.callbackUrl);
|
|
320
|
+
else if (action.type === "sign-out")
|
|
321
|
+
await controller.signOutExtension(action.name);
|
|
318
322
|
else
|
|
319
323
|
await controller.reopen();
|
|
320
324
|
}
|
|
@@ -595,6 +599,37 @@ export async function createChatController(options, settings = {}) {
|
|
|
595
599
|
* installed until `confirmed`: the result says which (`confirm`), for the view to ask the
|
|
596
600
|
* person. The running session is untouched: it's there in a new conversation, or with `reopen()`.
|
|
597
601
|
*/
|
|
602
|
+
/**
|
|
603
|
+
* Signs in to an installed extension's remote servers (#61): gives the person the links (never
|
|
604
|
+
* opens a browser), then says when the sign-in finishes. With `callbackUrl`, the address the
|
|
605
|
+
* browser ended on (from another computer), finishes it instead.
|
|
606
|
+
*/
|
|
607
|
+
async signInExtension(name, callbackUrl) {
|
|
608
|
+
const auth = current.extensionAuth;
|
|
609
|
+
const result = auth ? await auth.signIn(name, callbackUrl) : { status: "unavailable", reason: "not-running" };
|
|
610
|
+
if (result.status === "unavailable")
|
|
611
|
+
notice(t().extensionSignInUnavailable(name));
|
|
612
|
+
else if (result.status === "signed-in")
|
|
613
|
+
notice(callbackUrl ? t().extensionSignedIn(name) : t().extensionAlreadySignedIn(name));
|
|
614
|
+
else {
|
|
615
|
+
for (const link of result.links)
|
|
616
|
+
notice(t().extensionSignInOpen(name, link.url));
|
|
617
|
+
notice(t().extensionSignInPaste(name));
|
|
618
|
+
// Waits in the background: the person signs in in their browser, whenever they do.
|
|
619
|
+
void auth.connected(name, 5 * 60_000).then((done) => {
|
|
620
|
+
notice(done ? t().extensionSignedIn(name) : t().extensionSignInWaiting(name));
|
|
621
|
+
if (done)
|
|
622
|
+
extensionNotices.push(`<system-reminder>The user signed in to the ${name} extension: its remote tools are available now.</system-reminder>`);
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
return result;
|
|
626
|
+
},
|
|
627
|
+
/** Signs out of an installed extension's remote servers (#61). */
|
|
628
|
+
async signOutExtension(name) {
|
|
629
|
+
const done = (await current.extensionAuth?.signOut(name).catch(() => false)) ?? false;
|
|
630
|
+
notice(done ? t().extensionSignedOut(name) : t().extensionSignInUnavailable(name));
|
|
631
|
+
return done;
|
|
632
|
+
},
|
|
598
633
|
async installExtension(spec, options = {}) {
|
|
599
634
|
if (!current.extensions)
|
|
600
635
|
throw new Error("This chat's session has no extensions folder.");
|
|
@@ -608,7 +643,10 @@ export async function createChatController(options, settings = {}) {
|
|
|
608
643
|
// What it needs and this computer lacks (#63): the person installs it, the kit never does.
|
|
609
644
|
for (const line of result.missing)
|
|
610
645
|
notice(t().extensionLacks(result.name, line));
|
|
611
|
-
|
|
646
|
+
// Where what the agent sends goes (#61).
|
|
647
|
+
if (result.remote.length)
|
|
648
|
+
notice(t().extensionTalksTo(result.name, result.remote.join(", ")));
|
|
649
|
+
return { name: result.name, missing: result.missing, remote: result.remote };
|
|
612
650
|
},
|
|
613
651
|
/** Updates one installed extension from its marketplace, or every one with a newer version there; the names updated. */
|
|
614
652
|
async updateExtension(name) {
|
|
@@ -25,6 +25,15 @@ export type ExtensionAction = {
|
|
|
25
25
|
type: "reload";
|
|
26
26
|
} | {
|
|
27
27
|
type: "trust";
|
|
28
|
+
}
|
|
29
|
+
/** Signing in to an extension's remote servers (#61), or finishing it with the callback URL pasted from another computer. */
|
|
30
|
+
| {
|
|
31
|
+
type: "sign-in";
|
|
32
|
+
name: string;
|
|
33
|
+
callbackUrl?: string;
|
|
34
|
+
} | {
|
|
35
|
+
type: "sign-out";
|
|
36
|
+
name: string;
|
|
28
37
|
};
|
|
29
38
|
/** One line for a catalog entry: what installs it, its version, whether it's installed or has an update, what it is. */
|
|
30
39
|
export declare function describeEntry(entry: CatalogEntry): string;
|
package/dist/chat/extensions.js
CHANGED
|
@@ -35,10 +35,17 @@ export function describeCapabilities(status) {
|
|
|
35
35
|
];
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
+
/** The running extensions whose remote servers need the person to sign in (#61). */
|
|
39
|
+
async function needingSignIn(status) {
|
|
40
|
+
const waiting = new Set((await status.serverStatus?.().catch(() => []))?.filter((server) => server.status === "needs-auth").map((server) => server.name));
|
|
41
|
+
if (!waiting.size)
|
|
42
|
+
return [];
|
|
43
|
+
return status.active.filter((name) => status.about[name]?.servers.some((server) => waiting.has(server)));
|
|
44
|
+
}
|
|
38
45
|
/** One line for a catalog entry: what installs it, its version, whether it's installed or has an update, what it is. */
|
|
39
46
|
export function describeEntry(entry) {
|
|
40
47
|
const state = entry.installed ? (entry.update ? ` ${t().extensionUpdateAvailable(entry.installed.manifest?.version ?? "?")}` : ` ${t().extensionIsInstalled}`) : "";
|
|
41
|
-
return `${entry.spec}${entry.version ? ` ${entry.version}` : ""}${state}${entry.
|
|
48
|
+
return `${entry.spec}${entry.version ? ` ${entry.version}` : ""}${state}${entry.description ? ` ${entry.description}` : ""}`;
|
|
42
49
|
}
|
|
43
50
|
/** An installed extension's options, one line each: its value, its default, or whether a sensitive one is set (never its value). */
|
|
44
51
|
export async function describeOptions(extension) {
|
|
@@ -78,6 +85,11 @@ export async function chatExtensionsCommand(line, status, canReopen) {
|
|
|
78
85
|
return canReopen ? { lines: [], action: { type: "reload" } } : { lines: [t().extensionsCantReopen] };
|
|
79
86
|
if (verb === "trust" && !name)
|
|
80
87
|
return canReopen ? { lines: [], action: { type: "trust" } } : { lines: [t().extensionsCantReopen] };
|
|
88
|
+
// Signing in happens in the running session: no reopening (#61).
|
|
89
|
+
if (verb === "sign-in" && name)
|
|
90
|
+
return { lines: [], action: { type: "sign-in", name, ...(extra[0] ? { callbackUrl: extra[0] } : {}) } };
|
|
91
|
+
if (verb === "sign-out" && name)
|
|
92
|
+
return { lines: [], action: { type: "sign-out", name } };
|
|
81
93
|
if (action?.toLowerCase() === "config" && name) {
|
|
82
94
|
const installed = (status?.installed ?? []).find((extension) => extension.name === name && !extension.shadowed);
|
|
83
95
|
if (!installed)
|
|
@@ -107,7 +119,7 @@ export async function chatExtensionsCommand(line, status, canReopen) {
|
|
|
107
119
|
if (!action) {
|
|
108
120
|
const lines = [status?.active.length ? t().extensionsRunning(status.active.join(", ")) : t().extensionsNone];
|
|
109
121
|
if (status)
|
|
110
|
-
lines.push(...describeCapabilities(status));
|
|
122
|
+
lines.push(...describeCapabilities(status), ...(await needingSignIn(status)).map((extension) => t().extensionNeedsSignIn(extension)));
|
|
111
123
|
for (const { name: off, reason } of status?.inactive ?? [])
|
|
112
124
|
lines.push(t().extensionOff(off, reason));
|
|
113
125
|
if (status?.installed.length)
|
package/dist/chat/runs.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Options } from "@anthropic-ai/claude-agent-sdk";
|
|
2
2
|
import type { ExtensionsStatus, ModeControl } from "../core/session.js";
|
|
3
3
|
import type { ToolLabels } from "../core/toolLabels.js";
|
|
4
|
-
import type { ExtensionRestart, ExtensionSwitch } from "../core/liveExtensions.js";
|
|
4
|
+
import type { ExtensionAuth, ExtensionRestart, ExtensionSwitch } from "../core/liveExtensions.js";
|
|
5
5
|
import { type RunFolder, type RunSummary } from "../core/runs.js";
|
|
6
6
|
/**
|
|
7
7
|
* Builds the session's options for a run folder, e.g.
|
|
@@ -17,6 +17,7 @@ export type SessionOpener = (run: RunFolder) => Promise<Options | {
|
|
|
17
17
|
apis?: Record<string, Record<string, unknown>>;
|
|
18
18
|
switchExtension?: ExtensionSwitch;
|
|
19
19
|
restartExtension?: ExtensionRestart;
|
|
20
|
+
extensionAuth?: ExtensionAuth;
|
|
20
21
|
}>;
|
|
21
22
|
export interface OpenedSession {
|
|
22
23
|
run: RunFolder;
|
|
@@ -27,6 +28,7 @@ export interface OpenedSession {
|
|
|
27
28
|
apis?: Record<string, Record<string, unknown>>;
|
|
28
29
|
switchExtension?: ExtensionSwitch;
|
|
29
30
|
restartExtension?: ExtensionRestart;
|
|
31
|
+
extensionAuth?: ExtensionAuth;
|
|
30
32
|
}
|
|
31
33
|
/** The run a chat starts with: the latest one with `--continue` (if any), a new one otherwise. */
|
|
32
34
|
export declare function firstRun(runsDir: string, argv?: readonly string[]): Promise<RunFolder>;
|
package/dist/chat/runs.js
CHANGED
|
@@ -13,7 +13,7 @@ export async function firstRun(runsDir, argv = process.argv.slice(2)) {
|
|
|
13
13
|
export async function openSession(opener, run) {
|
|
14
14
|
const opened = await opener(run);
|
|
15
15
|
// The SDK's Options has no "options" field: this is buildSessionOptions()'s result.
|
|
16
|
-
return "options" in opened ? { run, options: opened.options, modeControl: opened.modeControl, toolLabels: opened.toolLabels, extensions: opened.extensions, apis: opened.apis, switchExtension: opened.switchExtension, restartExtension: opened.restartExtension } : { run, options: opened };
|
|
16
|
+
return "options" in opened ? { run, options: opened.options, modeControl: opened.modeControl, toolLabels: opened.toolLabels, extensions: opened.extensions, apis: opened.apis, switchExtension: opened.switchExtension, restartExtension: opened.restartExtension, extensionAuth: opened.extensionAuth } : { run, options: opened };
|
|
17
17
|
}
|
|
18
18
|
/** A run's date for a person, in the kit's language. */
|
|
19
19
|
export function runDate(date) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ExtensionDirs, type ExtensionScope, type InstalledExtension } from "./externalExtensions.js";
|
|
2
2
|
import { type DescribedCapability } from "./extensions.js";
|
|
3
|
+
import type { Language } from "./language.js";
|
|
3
4
|
import { type KnownMarketplace, type MarketplacePlugin } from "./marketplaces.js";
|
|
4
5
|
/**
|
|
5
6
|
* The agent's catalog of extensions (#56): what its known marketplaces offer, what's installed
|
|
@@ -22,6 +23,8 @@ export interface CatalogEntry {
|
|
|
22
23
|
/** From the agent's own marketplace: installed without a confirmation. */
|
|
23
24
|
official: boolean;
|
|
24
25
|
plugin: MarketplacePlugin;
|
|
26
|
+
/** What it is, in the kit's language (#67): its own there, else the marketplace entry's, else its `plugin.json`'s, in English. */
|
|
27
|
+
description: string;
|
|
25
28
|
/** The version it offers: its `plugin.json`'s when the marketplace has its folder, else the marketplace's entry's. */
|
|
26
29
|
version?: string;
|
|
27
30
|
/** Installed (and used: not shadowed), from wherever it came. */
|
|
@@ -40,14 +43,17 @@ export interface CatalogEntry {
|
|
|
40
43
|
name: string;
|
|
41
44
|
for: string[];
|
|
42
45
|
}[];
|
|
46
|
+
/** The hosts its remote servers talk to (#61): what the agent sends there leaves the computer. */
|
|
47
|
+
remote: string[];
|
|
43
48
|
}
|
|
44
49
|
/** Whether `offered` is newer than `installed` (x.y.z); a version that can't be compared isn't. */
|
|
45
50
|
export declare function isNewer(offered: string | undefined, installed: string | undefined): boolean;
|
|
46
|
-
/** Everything the known marketplaces offer, matching `words` (in its name, description, category, tags or keywords) when given. */
|
|
51
|
+
/** Everything the known marketplaces offer, matching `words` (in its name, description, category, tags or keywords) when given; its texts in `language` (the kit's by default). */
|
|
47
52
|
export declare function extensionCatalog(dirs: ExtensionDirs, options?: {
|
|
48
53
|
official?: string;
|
|
49
54
|
words?: readonly string[];
|
|
50
55
|
topics?: readonly string[];
|
|
56
|
+
language?: Language;
|
|
51
57
|
}): Promise<CatalogEntry[]>;
|
|
52
58
|
/** Whether installing `spec` needs the person's confirmation first: a plugin of a marketplace that isn't the agent's own. */
|
|
53
59
|
export declare function needsConfirmation(dirs: ExtensionDirs, spec: string, official?: string): Promise<{
|
|
@@ -70,6 +76,7 @@ export declare function installExtension(dirs: ExtensionDirs, spec: string, opti
|
|
|
70
76
|
marketplace: string;
|
|
71
77
|
commit?: string;
|
|
72
78
|
missing: string[];
|
|
79
|
+
remote: string[];
|
|
73
80
|
}>;
|
|
74
81
|
/**
|
|
75
82
|
* Updates an installed extension from the marketplace it came from: a fresh copy of the
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { mkdtemp, rm, stat } from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { addExtension, codeStoreDir, hashExtension, inKitRange, installTarget, isGitSource, listInstalled, readExternalManifest, readLock, storedCodeDir } from "./externalExtensions.js";
|
|
4
|
+
import { addExtension, remoteHosts, codeStoreDir, hashExtension, inKitRange, installTarget, isGitSource, listInstalled, readExternalManifest, readLock, storedCodeDir } from "./externalExtensions.js";
|
|
5
5
|
import { cachePackage, cachedPackageDir } from "./npxPackages.js";
|
|
6
6
|
import { trustWorkspace } from "./workspaceTrust.js";
|
|
7
|
-
import { describeCapability } from "./extensions.js";
|
|
7
|
+
import { describeCapability, describeInLanguage } from "./extensions.js";
|
|
8
8
|
import { getLanguage } from "./messages/index.js";
|
|
9
9
|
import { addMarketplace, findPlugin, installFromMarketplace, listMarketplaces, resolvePluginSource, updateMarketplace } from "./marketplaces.js";
|
|
10
10
|
/**
|
|
@@ -51,7 +51,7 @@ const IMAGE = /\.(svg|png|jpe?g|webp)$/i;
|
|
|
51
51
|
* the version (its `plugin.json` wins over the entry, as Claude Code computes it), its keywords,
|
|
52
52
|
* and its look, an image inside its folder only.
|
|
53
53
|
*/
|
|
54
|
-
async function offered(marketplace, plugin) {
|
|
54
|
+
async function offered(marketplace, plugin, language) {
|
|
55
55
|
try {
|
|
56
56
|
const resolved = resolvePluginSource(marketplace, plugin);
|
|
57
57
|
if ("source" in resolved && !isGitSource(resolved.source) && (await stat(resolved.source).catch(() => null))?.isDirectory()) {
|
|
@@ -66,15 +66,17 @@ async function offered(marketplace, plugin) {
|
|
|
66
66
|
};
|
|
67
67
|
const [logo, banner] = [await image(manifest?.catalog?.logo), await image(manifest?.catalog?.banner)];
|
|
68
68
|
const version = manifest?.version ?? plugin.version;
|
|
69
|
-
const capabilities = (manifest?.capabilities ?? []).map((capability) => describeCapability(capability,
|
|
69
|
+
const capabilities = (manifest?.capabilities ?? []).map((capability) => describeCapability(capability, language));
|
|
70
70
|
const requirements = (manifest?.requirements ?? []).map((requirement) => ({ name: requirement.name, for: requirement.for }));
|
|
71
|
-
|
|
71
|
+
const description = describeInLanguage(plugin.description ?? manifest?.description ?? "", manifest?.descriptions, language);
|
|
72
|
+
const remote = manifest ? remoteHosts(manifest) : [];
|
|
73
|
+
return { ...(version ? { version } : {}), keywords: manifest?.keywords ?? [], ...(logo ? { logo } : {}), ...(banner ? { banner } : {}), capabilities, requirements, description, remote };
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
catch {
|
|
75
77
|
// A source the kit doesn't install: the entry's own fields.
|
|
76
78
|
}
|
|
77
|
-
return { ...(plugin.version ? { version: plugin.version } : {}), keywords: [], capabilities: [], requirements: [] };
|
|
79
|
+
return { ...(plugin.version ? { version: plugin.version } : {}), keywords: [], capabilities: [], requirements: [], description: plugin.description ?? "", remote: [] };
|
|
78
80
|
}
|
|
79
81
|
/** Whether `offered` is newer than `installed` (x.y.z); a version that can't be compared isn't. */
|
|
80
82
|
export function isNewer(offered, installed) {
|
|
@@ -82,7 +84,7 @@ export function isNewer(offered, installed) {
|
|
|
82
84
|
return false;
|
|
83
85
|
return inKitRange(offered, `>${installed.split("-")[0]}`);
|
|
84
86
|
}
|
|
85
|
-
/** Everything the known marketplaces offer, matching `words` (in its name, description, category, tags or keywords) when given. */
|
|
87
|
+
/** Everything the known marketplaces offer, matching `words` (in its name, description, category, tags or keywords) when given; its texts in `language` (the kit's by default). */
|
|
86
88
|
export async function extensionCatalog(dirs, options = {}) {
|
|
87
89
|
const installed = (await listInstalled(dirs)).filter((extension) => !extension.shadowed);
|
|
88
90
|
const words = (options.words ?? []).map((word) => word.toLowerCase());
|
|
@@ -91,12 +93,12 @@ export async function extensionCatalog(dirs, options = {}) {
|
|
|
91
93
|
const entries = [];
|
|
92
94
|
for (const marketplace of await knownMarketplaces(dirs, options.official)) {
|
|
93
95
|
for (const plugin of marketplace.manifest?.plugins ?? []) {
|
|
94
|
-
const look = await offered(marketplace, plugin);
|
|
96
|
+
const look = await offered(marketplace, plugin, options.language ?? getLanguage());
|
|
95
97
|
const topics = [...new Set([...look.keywords, ...(plugin.tags ?? []), ...(plugin.keywords ?? []), ...(plugin.category ? [plugin.category] : [])].map((topic) => topic.toLowerCase()))];
|
|
96
98
|
if (wanted && !topics.some((topic) => wanted.has(topic)))
|
|
97
99
|
continue;
|
|
98
100
|
// Its capabilities count too: a search for "grading" finds what grades.
|
|
99
|
-
const text = [plugin.name, plugin.description, ...topics, ...look.capabilities.flatMap(({ id, label, description }) => [id, label, description])].join(" ").toLowerCase();
|
|
101
|
+
const text = [plugin.name, plugin.description, look.description, ...topics, ...look.capabilities.flatMap(({ id, label, description }) => [id, label, description])].join(" ").toLowerCase();
|
|
100
102
|
if (!words.every((word) => text.includes(word)))
|
|
101
103
|
continue;
|
|
102
104
|
const version = look.version;
|
|
@@ -104,6 +106,7 @@ export async function extensionCatalog(dirs, options = {}) {
|
|
|
104
106
|
const fromHere = mine?.lock.marketplace === `${plugin.name}@${marketplace.name}`;
|
|
105
107
|
entries.push({
|
|
106
108
|
spec: `${plugin.name}@${marketplace.name}`,
|
|
109
|
+
description: look.description,
|
|
107
110
|
marketplace: marketplace.name,
|
|
108
111
|
official: marketplace.official,
|
|
109
112
|
plugin,
|
|
@@ -115,6 +118,7 @@ export async function extensionCatalog(dirs, options = {}) {
|
|
|
115
118
|
...(look.banner ? { banner: look.banner } : {}),
|
|
116
119
|
capabilities: look.capabilities,
|
|
117
120
|
requirements: look.requirements,
|
|
121
|
+
remote: look.remote,
|
|
118
122
|
});
|
|
119
123
|
}
|
|
120
124
|
}
|
|
@@ -52,5 +52,10 @@ export declare function missingOptions(declared: Record<string, ExtensionOption>
|
|
|
52
52
|
export declare function storeOptionValues(scopeDir: string, name: string, declared: Record<string, ExtensionOption>, stored: Record<string, OptionValue>, changes: Record<string, OptionValue | undefined>): Promise<Record<string, OptionValue>>;
|
|
53
53
|
/** Removes an extension's sensitive values. */
|
|
54
54
|
export declare function removeSecrets(scopeDir: string, name: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* `${user_config.KEY}` in `text` replaced by its value: a list comma-separated, none as empty, as
|
|
57
|
+
* the launcher does for a local server. The kit's own, for a remote server's URL and headers (#61).
|
|
58
|
+
*/
|
|
59
|
+
export declare function fillOptions(text: string, values: Record<string, OptionValue>): string;
|
|
55
60
|
/** The `${user_config.KEY}` a server's values name. */
|
|
56
61
|
export declare function optionsNamed(values: readonly string[]): string[];
|
|
@@ -133,6 +133,16 @@ export async function storeOptionValues(scopeDir, name, declared, stored, change
|
|
|
133
133
|
export async function removeSecrets(scopeDir, name) {
|
|
134
134
|
await rm(extensionSecretsPath(scopeDir, name), { force: true });
|
|
135
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* `${user_config.KEY}` in `text` replaced by its value: a list comma-separated, none as empty, as
|
|
138
|
+
* the launcher does for a local server. The kit's own, for a remote server's URL and headers (#61).
|
|
139
|
+
*/
|
|
140
|
+
export function fillOptions(text, values) {
|
|
141
|
+
return text.replace(/\$\{user_config\.([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, key) => {
|
|
142
|
+
const value = values[key];
|
|
143
|
+
return value === undefined ? "" : Array.isArray(value) ? value.join(",") : String(value);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
136
146
|
/** The `${user_config.KEY}` a server's values name. */
|
|
137
147
|
export function optionsNamed(values) {
|
|
138
148
|
return [...new Set(values.flatMap((value) => [...value.matchAll(/\$\{user_config\.([A-Za-z_][A-Za-z0-9_]*)\}/g)].map((match) => match[1])))];
|
|
@@ -64,6 +64,12 @@ export interface ExtensionContribution {
|
|
|
64
64
|
* no `readOnlyTools`. A list in the manifest wins: its servers aren't here.
|
|
65
65
|
*/
|
|
66
66
|
readOnlyHintServers?: string[];
|
|
67
|
+
/**
|
|
68
|
+
* Its MCP servers that are remote (#61), by session name: the SDK fixes their URL and headers
|
|
69
|
+
* when the session opens, so a changed option takes opening it again, and they may need the
|
|
70
|
+
* person to sign in.
|
|
71
|
+
*/
|
|
72
|
+
remoteServers?: string[];
|
|
67
73
|
/** Its MCP tools that ask the person themselves: interactive mode's step gate doesn't ask first. */
|
|
68
74
|
selfAskingTools?: string[];
|
|
69
75
|
/** How the chat shows its tools, by full name, in the kit's language: its line and how it counts in a folded group. */
|
|
@@ -135,6 +141,13 @@ export declare function describeCapability(capability: ExtensionCapability, lang
|
|
|
135
141
|
* one is missing.
|
|
136
142
|
*/
|
|
137
143
|
export declare function capabilitiesProblem(pluginRoot: string, capabilities: readonly ExtensionCapability[]): Promise<string | undefined>;
|
|
144
|
+
/**
|
|
145
|
+
* A manifest's `metadata["agent-kit"].description` (#67): its description in other languages than
|
|
146
|
+
* English, which stays the top-level `description` Claude Code reads. Only languages the kit speaks.
|
|
147
|
+
*/
|
|
148
|
+
export declare function parseDescriptions(raw: unknown): Partial<Record<Language, string>>;
|
|
149
|
+
/** An extension's description in `language`: its own there, else the English one. */
|
|
150
|
+
export declare function describeInLanguage(english: string, descriptions: Partial<Record<Language, string>> | undefined, language: Language): string;
|
|
138
151
|
/** An extension's manifest, as the kit reads it. */
|
|
139
152
|
export interface ExtensionManifest {
|
|
140
153
|
name: string;
|
package/dist/core/extensions.js
CHANGED
|
@@ -49,6 +49,19 @@ export async function capabilitiesProblem(pluginRoot, capabilities) {
|
|
|
49
49
|
}
|
|
50
50
|
return undefined;
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* A manifest's `metadata["agent-kit"].description` (#67): its description in other languages than
|
|
54
|
+
* English, which stays the top-level `description` Claude Code reads. Only languages the kit speaks.
|
|
55
|
+
*/
|
|
56
|
+
export function parseDescriptions(raw) {
|
|
57
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
58
|
+
return {};
|
|
59
|
+
return Object.fromEntries(Object.entries(raw).filter(([language, text]) => SUPPORTED_LANGUAGES.includes(language) && typeof text === "string" && text.length > 0));
|
|
60
|
+
}
|
|
61
|
+
/** An extension's description in `language`: its own there, else the English one. */
|
|
62
|
+
export function describeInLanguage(english, descriptions, language) {
|
|
63
|
+
return descriptions?.[language] ?? english;
|
|
64
|
+
}
|
|
52
65
|
/** Reads an extension's manifest from its plugin. */
|
|
53
66
|
export function readExtensionManifest(pluginRoot) {
|
|
54
67
|
const file = path.join(pluginRoot, ".claude-plugin", "plugin.json");
|
|
@@ -26,12 +26,34 @@ export interface ExtensionDirs {
|
|
|
26
26
|
/** One of the two scopes an extension can be installed in: the agent's or the project's. */
|
|
27
27
|
export type ExtensionScope = keyof ExtensionDirs;
|
|
28
28
|
/** One of a plugin's MCP servers, as its `.mcp.json` (or `plugin.json`'s `mcpServers`) declares it. */
|
|
29
|
-
|
|
29
|
+
/** A server the kit starts on this computer, through its launcher. */
|
|
30
|
+
export interface LocalServer {
|
|
30
31
|
command: string;
|
|
31
32
|
args?: string[];
|
|
32
33
|
/** Its variables: a value may take the agent's with `${VAR}`, and its folders with `${CLAUDE_PLUGIN_ROOT}` and `${CLAUDE_PLUGIN_DATA}`. */
|
|
33
34
|
env?: Record<string, string>;
|
|
34
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* A remote server (#61), over HTTP or SSE: nothing runs here. `${user_config.KEY}` and `${VAR}` in
|
|
38
|
+
* its URL and headers take their values when the session opens.
|
|
39
|
+
*/
|
|
40
|
+
export interface RemoteServer {
|
|
41
|
+
type: "http" | "sse";
|
|
42
|
+
url: string;
|
|
43
|
+
headers?: Record<string, string>;
|
|
44
|
+
}
|
|
45
|
+
/** One of an extension's MCP servers, as its `.mcp.json` declares it. */
|
|
46
|
+
export type PluginServer = LocalServer | RemoteServer;
|
|
47
|
+
/** Whether a server is remote (#61). */
|
|
48
|
+
export declare function isRemoteServer(server: PluginServer): server is RemoteServer;
|
|
49
|
+
/**
|
|
50
|
+
* The name a session gives an extension's remote server: its own, prefixed with the extension's.
|
|
51
|
+
* The SDK's CLI keeps OAuth tokens by server name (confirmed empirically, CLI 2.1.300): a server
|
|
52
|
+
* named like one in the runner's own Claude Code would sign in with the runner's token.
|
|
53
|
+
*/
|
|
54
|
+
export declare function remoteServerName(extension: string, server: string): string;
|
|
55
|
+
/** The hosts an extension's remote servers talk to, for the person to read before installing it. */
|
|
56
|
+
export declare function remoteHosts(manifest: Pick<ExternalManifest, "servers">): string[];
|
|
35
57
|
/** How the chat shows one of its tools in one language: `{field}` takes the call's input. */
|
|
36
58
|
export interface ExternalToolLabel {
|
|
37
59
|
label: string;
|
|
@@ -49,7 +71,10 @@ export interface ExtensionAuthor {
|
|
|
49
71
|
*/
|
|
50
72
|
export interface ExternalManifest {
|
|
51
73
|
name: string;
|
|
74
|
+
/** Its description in English: `plugin.json`'s, which Claude Code reads. */
|
|
52
75
|
description: string;
|
|
76
|
+
/** Its description in other languages (#67), from `metadata["agent-kit"].description`. */
|
|
77
|
+
descriptions: Partial<Record<Language, string>>;
|
|
53
78
|
version?: string;
|
|
54
79
|
author?: ExtensionAuthor;
|
|
55
80
|
homepage?: string;
|
|
@@ -227,6 +252,7 @@ export declare function addExtension(source: string, scopeDir: string, options?:
|
|
|
227
252
|
replaced: boolean;
|
|
228
253
|
commit?: string;
|
|
229
254
|
missing: string[];
|
|
255
|
+
remote: string[];
|
|
230
256
|
}>;
|
|
231
257
|
/**
|
|
232
258
|
* Removes an extension from a scope (its files and its lock entry; a linked one's folder is left
|