@falkenslab/agent-kit 0.20.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 +37 -7
- 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 +63 -4
- 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 +9 -1
- package/dist/core/extensions.js +2 -1
- package/dist/core/externalExtensions.d.ts +116 -13
- package/dist/core/externalExtensions.js +280 -34
- 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 +30 -5
- package/dist/core/liveExtensions.js +78 -5
- 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 +44 -20
- 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 +13 -3
- package/dist/tui/extensionCommand.js +214 -37
- 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,139 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const TYPES = new Set(["string", "number", "boolean", "directory", "file"]);
|
|
4
|
+
const FIELDS = new Set(["type", "title", "description", "required", "default", "sensitive", "options", "multiple", "min", "max"]);
|
|
5
|
+
/**
|
|
6
|
+
* A manifest's `userConfig`, checked as Claude Code checks it (an option with an unknown field
|
|
7
|
+
* makes the plugin fail to load there): throws saying what's wrong.
|
|
8
|
+
*/
|
|
9
|
+
export function parseUserConfig(raw) {
|
|
10
|
+
if (raw === undefined)
|
|
11
|
+
return {};
|
|
12
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
13
|
+
throw new Error("its userConfig isn't an object");
|
|
14
|
+
const options = {};
|
|
15
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
16
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
17
|
+
throw new Error(`its userConfig option "${key}" isn't a valid name (letters, digits and _)`);
|
|
18
|
+
if (!value || typeof value !== "object")
|
|
19
|
+
throw new Error(`its userConfig option "${key}" isn't an object`);
|
|
20
|
+
const option = value;
|
|
21
|
+
const unknown = Object.keys(option).filter((field) => !FIELDS.has(field));
|
|
22
|
+
if (unknown.length)
|
|
23
|
+
throw new Error(`its userConfig option "${key}" has unknown fields (${unknown.join(", ")})`);
|
|
24
|
+
if (typeof option.type !== "string" || !TYPES.has(option.type))
|
|
25
|
+
throw new Error(`its userConfig option "${key}" has no valid type (string, number, boolean, directory or file)`);
|
|
26
|
+
if (typeof option.description !== "string")
|
|
27
|
+
throw new Error(`its userConfig option "${key}" has no description`);
|
|
28
|
+
options[key] = { ...option, title: typeof option.title === "string" ? option.title : key };
|
|
29
|
+
}
|
|
30
|
+
return options;
|
|
31
|
+
}
|
|
32
|
+
/** A typed value for `option` from what the person typed; throws saying why it isn't one. */
|
|
33
|
+
export function parseOptionValue(option, typed) {
|
|
34
|
+
const one = (text) => {
|
|
35
|
+
const value = text.trim();
|
|
36
|
+
if (option.type === "number") {
|
|
37
|
+
const number = Number(value);
|
|
38
|
+
if (!value || Number.isNaN(number))
|
|
39
|
+
throw new Error(`"${value}" isn't a number`);
|
|
40
|
+
if (option.min !== undefined && number < option.min)
|
|
41
|
+
throw new Error(`${number} is below ${option.min}`);
|
|
42
|
+
if (option.max !== undefined && number > option.max)
|
|
43
|
+
throw new Error(`${number} is above ${option.max}`);
|
|
44
|
+
if (option.options && !option.options.includes(number))
|
|
45
|
+
throw new Error(`${number} isn't one of ${option.options.join(", ")}`);
|
|
46
|
+
return number;
|
|
47
|
+
}
|
|
48
|
+
if (option.type === "boolean") {
|
|
49
|
+
if (/^(true|yes|y|1|on)$/i.test(value))
|
|
50
|
+
return true;
|
|
51
|
+
if (/^(false|no|n|0|off)$/i.test(value))
|
|
52
|
+
return false;
|
|
53
|
+
throw new Error(`"${value}" isn't true or false`);
|
|
54
|
+
}
|
|
55
|
+
if (option.options && !option.options.map(String).includes(value))
|
|
56
|
+
throw new Error(`"${value}" isn't one of ${option.options.join(", ")}`);
|
|
57
|
+
return option.type === "directory" || option.type === "file" ? path.resolve(value) : value;
|
|
58
|
+
};
|
|
59
|
+
if (option.multiple)
|
|
60
|
+
return typed.split(",").filter((part) => part.trim()).map((part) => String(one(part)));
|
|
61
|
+
return one(typed);
|
|
62
|
+
}
|
|
63
|
+
/** A value as it goes into a server's argument or variable: a list comma-separated. */
|
|
64
|
+
export function optionText(value) {
|
|
65
|
+
return value === undefined ? "" : Array.isArray(value) ? value.join(",") : String(value);
|
|
66
|
+
}
|
|
67
|
+
/** Where a scope keeps an extension's sensitive values: outside its plugin, its lock and its data. */
|
|
68
|
+
export function extensionSecretsPath(scopeDir, name) {
|
|
69
|
+
return path.join(scopeDir, ".secrets", `${name}.json`);
|
|
70
|
+
}
|
|
71
|
+
/** The folder of a scope's sensitive values, which the session's file tools never reach. */
|
|
72
|
+
export function secretsDir(scopeDir) {
|
|
73
|
+
return path.join(scopeDir, ".secrets");
|
|
74
|
+
}
|
|
75
|
+
async function readJson(file) {
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(await readFile(file, "utf8"));
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (error.code === "ENOENT")
|
|
81
|
+
return {};
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** The values an installed extension's options have now: what the person set (the lock's and the secrets'), else their defaults. */
|
|
86
|
+
export async function readOptionValues(scopeDir, name, declared, stored = {}) {
|
|
87
|
+
const secrets = (await readJson(extensionSecretsPath(scopeDir, name)));
|
|
88
|
+
const values = {};
|
|
89
|
+
for (const [key, option] of Object.entries(declared)) {
|
|
90
|
+
const value = option.sensitive ? secrets[key] : stored[key];
|
|
91
|
+
if (value !== undefined)
|
|
92
|
+
values[key] = value;
|
|
93
|
+
else if (option.default !== undefined)
|
|
94
|
+
values[key] = option.default;
|
|
95
|
+
}
|
|
96
|
+
return values;
|
|
97
|
+
}
|
|
98
|
+
/** The required options without a value. */
|
|
99
|
+
export function missingOptions(declared, values) {
|
|
100
|
+
return Object.entries(declared)
|
|
101
|
+
.filter(([key, option]) => option.required && (values[key] === undefined || values[key] === ""))
|
|
102
|
+
.map(([key]) => key);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Sets (or with `undefined`, clears) some of an extension's option values: the sensitive ones in
|
|
106
|
+
* its secrets file, the others returned for its lock entry (the caller writes the lock).
|
|
107
|
+
*/
|
|
108
|
+
export async function storeOptionValues(scopeDir, name, declared, stored, changes) {
|
|
109
|
+
const next = { ...stored };
|
|
110
|
+
const file = extensionSecretsPath(scopeDir, name);
|
|
111
|
+
const secrets = (await readJson(file));
|
|
112
|
+
let secretsChanged = false;
|
|
113
|
+
for (const [key, value] of Object.entries(changes)) {
|
|
114
|
+
const option = declared[key];
|
|
115
|
+
if (!option)
|
|
116
|
+
throw new Error(`${name} has no option "${key}"`);
|
|
117
|
+
const target = option.sensitive ? secrets : next;
|
|
118
|
+
if (value === undefined)
|
|
119
|
+
delete target[key];
|
|
120
|
+
else
|
|
121
|
+
target[key] = value;
|
|
122
|
+
if (option.sensitive)
|
|
123
|
+
secretsChanged = true;
|
|
124
|
+
}
|
|
125
|
+
if (secretsChanged) {
|
|
126
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
127
|
+
await writeFile(file, `${JSON.stringify(secrets, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
128
|
+
await chmod(file, 0o600).catch(() => undefined);
|
|
129
|
+
}
|
|
130
|
+
return next;
|
|
131
|
+
}
|
|
132
|
+
/** Removes an extension's sensitive values. */
|
|
133
|
+
export async function removeSecrets(scopeDir, name) {
|
|
134
|
+
await rm(extensionSecretsPath(scopeDir, name), { force: true });
|
|
135
|
+
}
|
|
136
|
+
/** The `${user_config.KEY}` a server's values name. */
|
|
137
|
+
export function optionsNamed(values) {
|
|
138
|
+
return [...new Set(values.flatMap((value) => [...value.matchAll(/\$\{user_config\.([A-Za-z_][A-Za-z0-9_]*)\}/g)].map((match) => match[1])))];
|
|
139
|
+
}
|
|
@@ -4,7 +4,7 @@ import type { ToolLabels } from "./toolLabels.js";
|
|
|
4
4
|
import type { SessionFacts } from "./sessionFacts.js";
|
|
5
5
|
/**
|
|
6
6
|
* Extensions (ADR-025): what an agent is besides the kit's core. An extension is a Claude Code
|
|
7
|
-
* plugin with the kit's data in its manifest (`.claude-plugin/plugin.json`,
|
|
7
|
+
* plugin with the kit's data in its manifest (`.claude-plugin/plugin.json`, under `metadata["agent-kit"]`:
|
|
8
8
|
* the capabilities it `provides` and `requires`), plus, for an internal one, the code that
|
|
9
9
|
* says what it brings to a session. An agent enables the ones it wants (`AgentSpec.extensions`),
|
|
10
10
|
* each made with its own options (the kit's through their factories, `knowledge({ dir })`…);
|
|
@@ -57,6 +57,12 @@ export interface ExtensionContribution {
|
|
|
57
57
|
}[];
|
|
58
58
|
/** Its MCP tools that only read: plan mode lets them through. */
|
|
59
59
|
readOnlyTools?: string[];
|
|
60
|
+
/**
|
|
61
|
+
* Its MCP servers whose tools that only read plan mode learns from the servers themselves
|
|
62
|
+
* (`readOnlyHint`), asked while the session runs: an installed extension whose manifest lists
|
|
63
|
+
* no `readOnlyTools`. A list in the manifest wins: its servers aren't here.
|
|
64
|
+
*/
|
|
65
|
+
readOnlyHintServers?: string[];
|
|
60
66
|
/** Its MCP tools that ask the person themselves: interactive mode's step gate doesn't ask first. */
|
|
61
67
|
selfAskingTools?: string[];
|
|
62
68
|
/** How the chat shows its tools, by full name, in the kit's language: its line and how it counts in a folded group. */
|
|
@@ -82,6 +88,8 @@ export interface Extension {
|
|
|
82
88
|
plugin: string;
|
|
83
89
|
/** Installed rather than shipped in a package (#37): its subagents get no `Bash`, and no plugin hook runs. */
|
|
84
90
|
external?: boolean;
|
|
91
|
+
/** An installed one's scope folder, where its lock is (its plugin is elsewhere when it's linked). */
|
|
92
|
+
installedIn?: string;
|
|
85
93
|
/** Why it can't run in this session (e.g. "needs a folder (`dir`)"), or `undefined` when it can. */
|
|
86
94
|
missing?(context: ExtensionContext): string | undefined;
|
|
87
95
|
/** What it brings to the session, once it's active. */
|
package/dist/core/extensions.js
CHANGED
|
@@ -12,7 +12,8 @@ export function readExtensionManifest(pluginRoot) {
|
|
|
12
12
|
const raw = JSON.parse(readFileSync(file, "utf8"));
|
|
13
13
|
if (!raw.name)
|
|
14
14
|
throw new Error(`${file} has no "name".`);
|
|
15
|
-
|
|
15
|
+
const kit = raw.metadata?.["agent-kit"];
|
|
16
|
+
return { name: raw.name, description: raw.description ?? "", provides: kit?.provides ?? [], requires: kit?.requires ?? [] };
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
19
|
* Which of the `extensions` run in this session, in the order given: those the session lacks
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import type { Extension } from "./extensions.js";
|
|
2
2
|
import type { Language } from "./language.js";
|
|
3
3
|
import { type ToolPhrase } from "./messages/index.js";
|
|
4
|
+
import { type ExtensionOption, type OptionValue } from "./extensionOptions.js";
|
|
5
|
+
import { type CachedPackage } from "./npxPackages.js";
|
|
4
6
|
/**
|
|
5
7
|
* External extensions (ADR-025, #37): installed into an agent, for all its projects (the
|
|
6
8
|
* agent's scope) or for one (the project's, which wins), from a folder or a git repository.
|
|
7
9
|
* Each scope is a folder of extensions, one per subfolder, with a lock (`extensions.lock.json`)
|
|
8
10
|
* that records where each came from, a SHA-256 over its files and whether it's enabled. An
|
|
9
11
|
* extension is a Claude Code plugin as it is (#38): its MCP servers come from its `.mcp.json`,
|
|
10
|
-
* and the kit starts each with Node through a launcher that leaves it only the system's
|
|
11
|
-
* variables and its own `env`. The `"agent-kit"`
|
|
12
|
-
* what the kit's gates and chat can use (capabilities, read-only tools, labels, help).
|
|
12
|
+
* and the kit starts each with Node (an `npx` one from its package cache, #54) through a launcher that leaves it only the system's
|
|
13
|
+
* variables and its own `env`. The `metadata["agent-kit"]` of its `plugin.json` is optional: it adds
|
|
14
|
+
* what the kit's gates and chat can use (capabilities, read-only tools, labels, help). Each has a
|
|
15
|
+
* data folder of its own (`${CLAUDE_PLUGIN_DATA}`, as Claude Code gives a plugin), kept across
|
|
16
|
+
* sessions and updates; one being developed is linked rather than copied (#52).
|
|
13
17
|
*/
|
|
14
18
|
/** The two folders an agent installs extensions into; either may be left out. */
|
|
15
19
|
export interface ExtensionDirs {
|
|
@@ -24,7 +28,7 @@ export type ExtensionScope = keyof ExtensionDirs;
|
|
|
24
28
|
export interface PluginServer {
|
|
25
29
|
command: string;
|
|
26
30
|
args?: string[];
|
|
27
|
-
/** Its variables: a value may take the agent's with `${VAR}`. */
|
|
31
|
+
/** Its variables: a value may take the agent's with `${VAR}`, and its folders with `${CLAUDE_PLUGIN_ROOT}` and `${CLAUDE_PLUGIN_DATA}`. */
|
|
28
32
|
env?: Record<string, string>;
|
|
29
33
|
}
|
|
30
34
|
/** How the chat shows one of its tools in one language: `{field}` takes the call's input. */
|
|
@@ -40,7 +44,7 @@ export interface ExtensionAuthor {
|
|
|
40
44
|
}
|
|
41
45
|
/**
|
|
42
46
|
* An external extension's manifest: its `plugin.json`, the plugin's own metadata (Claude Code's
|
|
43
|
-
* fields, which its validator checks) and the kit's
|
|
47
|
+
* fields, which its validator checks) and the kit's data (`metadata["agent-kit"]`, which it doesn't read).
|
|
44
48
|
*/
|
|
45
49
|
export interface ExternalManifest {
|
|
46
50
|
name: string;
|
|
@@ -59,33 +63,91 @@ export interface ExternalManifest {
|
|
|
59
63
|
needs: string[];
|
|
60
64
|
/** Its MCP servers, by name (`mcp__<name>__<tool>`), from the plugin's `.mcp.json` or `plugin.json`. */
|
|
61
65
|
servers: Record<string, PluginServer>;
|
|
62
|
-
/**
|
|
63
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Its tools that only read (short names, of any of its servers): plan mode lets them through.
|
|
68
|
+
* Without the key, plan mode asks its servers which (their tools' `readOnlyHint`); with it, the
|
|
69
|
+
* list is the whole answer, even empty.
|
|
70
|
+
*/
|
|
71
|
+
readOnlyTools?: string[];
|
|
64
72
|
/** Its tools' chat labels, by short name (of any of its servers) and language (English when the kit's isn't there). */
|
|
65
73
|
labels: Record<string, Partial<Record<Language, ExternalToolLabel>>>;
|
|
66
74
|
/** What it says about itself in a session, for the agent to tell the person (`SessionFacts`). */
|
|
67
75
|
help?: string;
|
|
76
|
+
/** Its options (`userConfig`, as Claude Code declares them, #55): its servers take them as `${user_config.KEY}`. */
|
|
77
|
+
userConfig: Record<string, ExtensionOption>;
|
|
78
|
+
/** How a catalog shows it (#59): its `logo` (square) and `banner` (wide), paths inside its folder, as its manifest gives them. */
|
|
79
|
+
catalog?: {
|
|
80
|
+
logo?: string;
|
|
81
|
+
banner?: string;
|
|
82
|
+
};
|
|
68
83
|
}
|
|
69
84
|
/** One extension in a scope's lock. */
|
|
70
85
|
export interface LockEntry {
|
|
71
|
-
/** Where it came from: a folder, or a git URL (with `#ref` when given). */
|
|
86
|
+
/** Where it came from: a folder, or a git URL (with `#ref` when given). A linked one runs from there. */
|
|
72
87
|
source: string;
|
|
88
|
+
/**
|
|
89
|
+
* Linked rather than copied (`extension add --link`), to develop it: it runs from its folder,
|
|
90
|
+
* whose files aren't checked, so a rebuild takes effect when its server starts again.
|
|
91
|
+
*/
|
|
92
|
+
linked?: boolean;
|
|
73
93
|
/** The commit it was cloned at, for a git source. */
|
|
74
94
|
commit?: string;
|
|
95
|
+
/** The folder inside its source it was installed from (a git repository's subfolder). */
|
|
96
|
+
subdir?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Its code is in the agent's store (`<agent scope>/.store/<name>/<hash>/`), not in its scope's
|
|
99
|
+
* folder (#57): a project's scope keeps only its lock, so a workspace shared with others carries
|
|
100
|
+
* what it uses and at which version, never the code itself (`restoreExtensions()` gets it).
|
|
101
|
+
*/
|
|
102
|
+
stored?: boolean;
|
|
75
103
|
/** The marketplace it was installed from (`<name>@<marketplace>`), if any. */
|
|
76
104
|
marketplace?: string;
|
|
77
|
-
/** SHA-256 over its files, checked when it loads. */
|
|
105
|
+
/** SHA-256 over its files, checked when it loads; empty for a linked one. */
|
|
78
106
|
sha256: string;
|
|
107
|
+
/** Its options' non-sensitive values (#55); the sensitive ones are in the scope's `.secrets/`. */
|
|
108
|
+
options?: Record<string, OptionValue>;
|
|
109
|
+
/** The npm packages its servers run with npx (#54), by `<package>@<version>`: in the agent's package cache, checked when it loads. */
|
|
110
|
+
packages?: Record<string, CachedPackage>;
|
|
79
111
|
enabled: boolean;
|
|
80
112
|
/** When it was installed, ISO 8601 in UTC. */
|
|
81
113
|
installed: string;
|
|
82
114
|
}
|
|
83
115
|
export declare const LOCK_FILE = "extensions.lock.json";
|
|
116
|
+
/**
|
|
117
|
+
* An installed extension's data folder: `${CLAUDE_PLUGIN_DATA}` in its servers' arguments and
|
|
118
|
+
* variables (a browser profile, a cache), in its scope, outside its plugin and its hash, so it's
|
|
119
|
+
* kept across sessions and updates. Created when a session starts it; removing the extension
|
|
120
|
+
* asks before deleting it.
|
|
121
|
+
*/
|
|
122
|
+
export declare function extensionDataDir(scopeDir: string, name: string): string;
|
|
123
|
+
/**
|
|
124
|
+
* Where a scope keeps what's the person's, not the workspace's: its extensions' data and secrets
|
|
125
|
+
* (#57). The agent's scope, its own folder; a project's, a folder of its own in the agent's
|
|
126
|
+
* (`<agent scope>/.projects/<project>-<hash>/`), so nothing private travels with a project. An
|
|
127
|
+
* agent without a scope of its own keeps it in the project's.
|
|
128
|
+
*/
|
|
129
|
+
export declare function scopePrivateDir(dirs: ExtensionDirs, scope: ExtensionScope): string | undefined;
|
|
130
|
+
/** The agent's store of extensions' code, by content (#57): where a project's scope keeps its extensions' code. */
|
|
131
|
+
export declare function codeStoreDir(dirs: ExtensionDirs): string | undefined;
|
|
132
|
+
/** Where a stored extension's code is: by its name and its files' hash. */
|
|
133
|
+
export declare function storedCodeDir(store: string, name: string, sha256: string): string;
|
|
134
|
+
/**
|
|
135
|
+
* How an install into `scope` is done: its npx packages into the agent's cache, and a project's
|
|
136
|
+
* code into the agent's store when the agent has a scope of its own.
|
|
137
|
+
*/
|
|
138
|
+
export declare function installTarget(dirs: ExtensionDirs, scope: ExtensionScope): {
|
|
139
|
+
packages?: string;
|
|
140
|
+
store?: string;
|
|
141
|
+
};
|
|
84
142
|
/** A scope's lock: its extensions by name. */
|
|
85
143
|
export declare function readLock(scopeDir: string): Promise<Record<string, LockEntry>>;
|
|
86
144
|
/** Reads an extension's manifest, checking what the kit needs from it. */
|
|
87
145
|
export declare function readExternalManifest(dir: string): Promise<ExternalManifest>;
|
|
88
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* SHA-256 over an extension's files (their paths and contents, in order), `.git` aside. A text
|
|
148
|
+
* file's line endings don't count (CRLF is taken as LF): git checks the same commit out with CRLF
|
|
149
|
+
* on one computer and LF on another, and a workspace's lock must match on both (#57).
|
|
150
|
+
*/
|
|
89
151
|
export declare function hashExtension(dir: string): Promise<string>;
|
|
90
152
|
/**
|
|
91
153
|
* Whether `version` (x.y.z) is in `range`: comparators separated by spaces, all of which must
|
|
@@ -96,7 +158,14 @@ export declare function inKitRange(version: string, range: string): boolean;
|
|
|
96
158
|
export interface InstalledExtension {
|
|
97
159
|
name: string;
|
|
98
160
|
scope: ExtensionScope;
|
|
161
|
+
/** Its scope's folder, where its lock is. */
|
|
162
|
+
scopeDir: string;
|
|
163
|
+
/** Its plugin: in its scope's folder, or where it is when it's linked. */
|
|
99
164
|
dir: string;
|
|
165
|
+
/** Its data folder (`${CLAUDE_PLUGIN_DATA}`), which may not exist yet: in its scope's private folder. */
|
|
166
|
+
dataDir: string;
|
|
167
|
+
/** Its scope's private folder (`scopePrivateDir()`): its data and secrets. */
|
|
168
|
+
privateDir: string;
|
|
100
169
|
lock: LockEntry;
|
|
101
170
|
/** Also installed in the project's scope, which wins: this one isn't used. */
|
|
102
171
|
shadowed: boolean;
|
|
@@ -114,6 +183,7 @@ export declare function loadExternalExtensions(dirs: ExtensionDirs): Promise<{
|
|
|
114
183
|
name: string;
|
|
115
184
|
reason: string;
|
|
116
185
|
}[];
|
|
186
|
+
untrusted: InstalledExtension[];
|
|
117
187
|
}>;
|
|
118
188
|
/** Why an installed extension can't load, or its manifest when it can. */
|
|
119
189
|
/** Whether a program is on the `PATH`, as a shell would find it (with `PATHEXT`'s extensions on Windows). */
|
|
@@ -125,19 +195,52 @@ export declare function isGitSource(source: string): boolean;
|
|
|
125
195
|
* a commit, and `subdir` for one inside it): copies its files (never runs a script), checks its
|
|
126
196
|
* manifest, and locks it, enabled, with the marketplace it came from when it did (`origin`, for a
|
|
127
197
|
* temporary folder, is what the lock says it came from). An extension with the same name in that
|
|
128
|
-
* scope is replaced.
|
|
198
|
+
* scope is replaced; its data folder stays. With `link`, a folder is registered where it is
|
|
199
|
+
* instead, to develop it: not copied, and its files not checked. The packages its npx servers
|
|
200
|
+
* run are fetched into `packages` (#54), the agent's package cache (`packageCacheDir()`; by
|
|
201
|
+
* default the scope's).
|
|
129
202
|
*/
|
|
130
203
|
export declare function addExtension(source: string, scopeDir: string, options?: {
|
|
131
204
|
subdir?: string;
|
|
132
205
|
sha?: string;
|
|
133
206
|
marketplace?: string;
|
|
134
207
|
origin?: string;
|
|
208
|
+
link?: boolean;
|
|
209
|
+
packages?: string;
|
|
210
|
+
store?: string;
|
|
135
211
|
}): Promise<{
|
|
136
212
|
name: string;
|
|
137
213
|
replaced: boolean;
|
|
138
214
|
commit?: string;
|
|
139
215
|
}>;
|
|
140
|
-
/**
|
|
141
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Removes an extension from a scope (its files and its lock entry; a linked one's folder is left
|
|
218
|
+
* where it is), and its data folder with `deleteData`; false when it wasn't there.
|
|
219
|
+
*/
|
|
220
|
+
export declare function removeExtension(scopeDir: string, name: string, options?: {
|
|
221
|
+
deleteData?: boolean;
|
|
222
|
+
privateDir?: string;
|
|
223
|
+
}): Promise<boolean>;
|
|
224
|
+
/** The size in bytes of the files under `dir` (0 when it doesn't exist). */
|
|
225
|
+
export declare function folderSize(dir: string): Promise<number>;
|
|
142
226
|
/** Enables or disables an installed extension in a scope; false when it isn't there. */
|
|
143
227
|
export declare function setExtensionEnabled(scopeDir: string, name: string, enabled: boolean): Promise<boolean>;
|
|
228
|
+
/**
|
|
229
|
+
* Sets an installed extension's options (#55), each from what the person typed (`undefined`
|
|
230
|
+
* clears it), checked against its `userConfig`: sensitive ones into the scope's secrets, the rest
|
|
231
|
+
* into its lock. A running session's servers take them when they start again.
|
|
232
|
+
*/
|
|
233
|
+
export declare function setExtensionOptions(where: string | InstalledExtension, name: string, typed: Record<string, string | undefined>): Promise<void>;
|
|
234
|
+
/** One of an installed extension's options and its value now (a sensitive one's value is never given: only whether it's set). */
|
|
235
|
+
export interface ExtensionOptionState {
|
|
236
|
+
key: string;
|
|
237
|
+
option: ExtensionOption;
|
|
238
|
+
/** Its value, when it has one and isn't sensitive. */
|
|
239
|
+
value?: OptionValue;
|
|
240
|
+
/** Whether it has a value (its own or its default). */
|
|
241
|
+
set: boolean;
|
|
242
|
+
/** Its value is its default, not one the person gave. */
|
|
243
|
+
isDefault: boolean;
|
|
244
|
+
}
|
|
245
|
+
/** An installed extension's options, as they are now. */
|
|
246
|
+
export declare function extensionOptionStates(installed: InstalledExtension): Promise<ExtensionOptionState[]>;
|