@falkenslab/agent-kit 0.23.0 → 0.24.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.
@@ -38,7 +38,7 @@ export function describeCapabilities(status) {
38
38
  /** One line for a catalog entry: what installs it, its version, whether it's installed or has an update, what it is. */
39
39
  export function describeEntry(entry) {
40
40
  const state = entry.installed ? (entry.update ? ` ${t().extensionUpdateAvailable(entry.installed.manifest?.version ?? "?")}` : ` ${t().extensionIsInstalled}`) : "";
41
- return `${entry.spec}${entry.version ? ` ${entry.version}` : ""}${state}${entry.plugin.description ? ` ${entry.plugin.description}` : ""}`;
41
+ return `${entry.spec}${entry.version ? ` ${entry.version}` : ""}${state}${entry.description ? ` ${entry.description}` : ""}`;
42
42
  }
43
43
  /** An installed extension's options, one line each: its value, its default, or whether a sensitive one is set (never its value). */
44
44
  export async function describeOptions(extension) {
@@ -22,6 +22,8 @@ export interface CatalogEntry {
22
22
  /** From the agent's own marketplace: installed without a confirmation. */
23
23
  official: boolean;
24
24
  plugin: MarketplacePlugin;
25
+ /** What it is, in the kit's language (#67): its own there, else the marketplace entry's, else its `plugin.json`'s, in English. */
26
+ description: string;
25
27
  /** The version it offers: its `plugin.json`'s when the marketplace has its folder, else the marketplace's entry's. */
26
28
  version?: string;
27
29
  /** Installed (and used: not shadowed), from wherever it came. */
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import { addExtension, 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
  /**
@@ -68,13 +68,14 @@ async function offered(marketplace, plugin) {
68
68
  const version = manifest?.version ?? plugin.version;
69
69
  const capabilities = (manifest?.capabilities ?? []).map((capability) => describeCapability(capability, getLanguage()));
70
70
  const requirements = (manifest?.requirements ?? []).map((requirement) => ({ name: requirement.name, for: requirement.for }));
71
- return { ...(version ? { version } : {}), keywords: manifest?.keywords ?? [], ...(logo ? { logo } : {}), ...(banner ? { banner } : {}), capabilities, requirements };
71
+ const description = describeInLanguage(plugin.description ?? manifest?.description ?? "", manifest?.descriptions, getLanguage());
72
+ return { ...(version ? { version } : {}), keywords: manifest?.keywords ?? [], ...(logo ? { logo } : {}), ...(banner ? { banner } : {}), capabilities, requirements, description };
72
73
  }
73
74
  }
74
75
  catch {
75
76
  // A source the kit doesn't install: the entry's own fields.
76
77
  }
77
- return { ...(plugin.version ? { version: plugin.version } : {}), keywords: [], capabilities: [], requirements: [] };
78
+ return { ...(plugin.version ? { version: plugin.version } : {}), keywords: [], capabilities: [], requirements: [], description: plugin.description ?? "" };
78
79
  }
79
80
  /** Whether `offered` is newer than `installed` (x.y.z); a version that can't be compared isn't. */
80
81
  export function isNewer(offered, installed) {
@@ -96,7 +97,7 @@ export async function extensionCatalog(dirs, options = {}) {
96
97
  if (wanted && !topics.some((topic) => wanted.has(topic)))
97
98
  continue;
98
99
  // 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();
100
+ const text = [plugin.name, plugin.description, look.description, ...topics, ...look.capabilities.flatMap(({ id, label, description }) => [id, label, description])].join(" ").toLowerCase();
100
101
  if (!words.every((word) => text.includes(word)))
101
102
  continue;
102
103
  const version = look.version;
@@ -104,6 +105,7 @@ export async function extensionCatalog(dirs, options = {}) {
104
105
  const fromHere = mine?.lock.marketplace === `${plugin.name}@${marketplace.name}`;
105
106
  entries.push({
106
107
  spec: `${plugin.name}@${marketplace.name}`,
108
+ description: look.description,
107
109
  marketplace: marketplace.name,
108
110
  official: marketplace.official,
109
111
  plugin,
@@ -135,6 +135,13 @@ export declare function describeCapability(capability: ExtensionCapability, lang
135
135
  * one is missing.
136
136
  */
137
137
  export declare function capabilitiesProblem(pluginRoot: string, capabilities: readonly ExtensionCapability[]): Promise<string | undefined>;
138
+ /**
139
+ * A manifest's `metadata["agent-kit"].description` (#67): its description in other languages than
140
+ * English, which stays the top-level `description` Claude Code reads. Only languages the kit speaks.
141
+ */
142
+ export declare function parseDescriptions(raw: unknown): Partial<Record<Language, string>>;
143
+ /** An extension's description in `language`: its own there, else the English one. */
144
+ export declare function describeInLanguage(english: string, descriptions: Partial<Record<Language, string>> | undefined, language: Language): string;
138
145
  /** An extension's manifest, as the kit reads it. */
139
146
  export interface ExtensionManifest {
140
147
  name: string;
@@ -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");
@@ -49,7 +49,10 @@ export interface ExtensionAuthor {
49
49
  */
50
50
  export interface ExternalManifest {
51
51
  name: string;
52
+ /** Its description in English: `plugin.json`'s, which Claude Code reads. */
52
53
  description: string;
54
+ /** Its description in other languages (#67), from `metadata["agent-kit"].description`. */
55
+ descriptions: Partial<Record<Language, string>>;
53
56
  version?: string;
54
57
  author?: ExtensionAuthor;
55
58
  homepage?: string;
@@ -5,7 +5,7 @@ import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { promisify } from "node:util";
8
- import { capabilitiesProblem, parseCapabilities, providedBy } from "./extensions.js";
8
+ import { capabilitiesProblem, parseCapabilities, parseDescriptions, providedBy } from "./extensions.js";
9
9
  import { getLanguage } from "./messages/index.js";
10
10
  import { truncate } from "./toolLabels.js";
11
11
  import { agentKitVersion } from "./version.js";
@@ -97,6 +97,7 @@ export async function readExternalManifest(dir) {
97
97
  return {
98
98
  name,
99
99
  description: typeof raw.description === "string" ? raw.description : "",
100
+ descriptions: parseDescriptions(kit.description),
100
101
  ...text("version"),
101
102
  ...(author && typeof author === "object" && typeof author.name === "string"
102
103
  ? { author: { name: author.name, ...(author.email ? { email: author.email } : {}), ...(author.url ? { url: author.url } : {}) } }
@@ -6,7 +6,7 @@ import { addExtension, extensionOptionStates, installTarget, folderSize, isGitSo
6
6
  import { describe, describeOptions } from "../chat/extensions.js";
7
7
  import { extensionCatalog, knownMarketplaces as catalogMarketplaces, restoreExtensions, updateExtension } from "../core/extensionCatalog.js";
8
8
  import { trustWorkspace } from "../core/workspaceTrust.js";
9
- import { describeCapability } from "../core/extensions.js";
9
+ import { describeCapability, describeInLanguage } from "../core/extensions.js";
10
10
  import { getLanguage } from "../core/messages/index.js";
11
11
  export { chatExtensionsCommand, describe } from "../chat/extensions.js";
12
12
  /**
@@ -402,10 +402,13 @@ async function info(extension) {
402
402
  if (!manifest)
403
403
  return [describe(extension), "Its manifest can't be read."];
404
404
  const author = manifest.author ? [manifest.author.name, manifest.author.email && `<${manifest.author.email}>`, manifest.author.url && `(${manifest.author.url})`].filter(Boolean).join(" ") : undefined;
405
- const readme = path.join(extension.dir, "README.md");
405
+ // Its README in the kit's language when it has one (README.es.md), else the English (#67).
406
+ const language = getLanguage();
407
+ const translated = path.join(extension.dir, `README.${language}.md`);
408
+ const readme = language !== "en" && (await stat(translated).catch(() => null))?.isFile() ? translated : path.join(extension.dir, "README.md");
406
409
  const field = (label, value) => (value ? [`${label.padEnd(13)}${value}`] : []);
407
410
  return [
408
- `${manifest.name}${manifest.version ? ` ${manifest.version}` : ""}: ${manifest.description}`,
411
+ `${manifest.name}${manifest.version ? ` ${manifest.version}` : ""}: ${describeInLanguage(manifest.description, manifest.descriptions, language)}`,
409
412
  ...field("Author", author),
410
413
  ...field("License", manifest.license),
411
414
  ...field("Homepage", manifest.homepage),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@falkenslab/agent-kit",
3
- "version": "0.23.0",
3
+ "version": "0.24.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",
@@ -11,6 +11,12 @@
11
11
  "type": "string",
12
12
  "pattern": "^\\s*(?:(?:>=|<=|>|<|=)?\\s*v?\\d+(?:\\.\\d+){0,2})(?:\\s+(?:>=|<=|>|<|=)?\\s*v?\\d+(?:\\.\\d+){0,2})*\\s*$"
13
13
  },
14
+ "description": {
15
+ "description": "Its description in other languages than English, which stays plugin.json's own description (Claude Code reads that one).",
16
+ "type": "object",
17
+ "propertyNames": { "enum": ["es", "fr", "de"] },
18
+ "additionalProperties": { "type": "string", "minLength": 1 }
19
+ },
14
20
  "provides": { "description": "Capabilities it lets the agent use, by name only. Its capabilities' ids count as provided too.", "type": "array", "items": { "type": "string", "minLength": 1 } },
15
21
  "capabilities": {
16
22
  "description": "What it lets the agent do, for the person: each one's skills and tools together, described per language. Its id is what requires names; neutral ids (lms-grading) let skills depend on what can be done, not on which extension does it.",
@@ -35,7 +35,14 @@
35
35
  "properties": {
36
36
  "name": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" },
37
37
  "version": { "description": "Its plugin.json's, the same as its marketplace entry's.", "type": "string", "minLength": 1 },
38
- "description": { "type": "string" },
38
+ "description": { "description": "In English: its marketplace entry's, else its plugin.json's.", "type": "string" },
39
+ "descriptions": {
40
+ "description": "Its description per language, English among them; only when it has one in another language.",
41
+ "type": "object",
42
+ "propertyNames": { "enum": ["en", "es", "fr", "de"] },
43
+ "required": ["en"],
44
+ "additionalProperties": { "type": "string" }
45
+ },
39
46
  "category": { "type": ["string", "null"] },
40
47
  "topics": {
41
48
  "description": "Its plugin.json's keywords and its entry's tags, keywords and category, lowercased, without repeats.",
@@ -47,6 +54,13 @@
47
54
  "license": { "type": ["string", "null"] },
48
55
  "homepage": { "$ref": "#/$defs/url" },
49
56
  "readme": { "$ref": "#/$defs/url" },
57
+ "readmes": {
58
+ "description": "Its README per language (README.md, README.es.md…), as URLs; only when it has one in another language.",
59
+ "type": "object",
60
+ "propertyNames": { "enum": ["en", "es", "fr", "de"] },
61
+ "required": ["en"],
62
+ "additionalProperties": { "$ref": "#/$defs/url" }
63
+ },
50
64
  "install": {
51
65
  "type": "object",
52
66
  "additionalProperties": false,