@capybara-hq/plugin-sdk 0.0.0-stage → 0.1.0-rc.1
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/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +27 -2
- package/dist/agent-template.d.ts +47 -0
- package/dist/agent-template.js +123 -0
- package/dist/bootstrap.d.ts +67 -0
- package/dist/bootstrap.js +109 -0
- package/dist/cards.d.ts +64 -0
- package/dist/cards.js +217 -0
- package/dist/channel.d.ts +230 -0
- package/dist/channel.js +26 -0
- package/dist/connector-auth.d.ts +64 -0
- package/dist/connector-auth.js +96 -0
- package/dist/connector.d.ts +154 -0
- package/dist/connector.js +1 -0
- package/dist/ctx.d.ts +176 -0
- package/dist/ctx.js +1 -0
- package/dist/define-extension.d.ts +41 -0
- package/dist/define-extension.js +85 -0
- package/dist/directory.d.ts +29 -0
- package/dist/directory.js +1 -0
- package/dist/extension-point.d.ts +29 -0
- package/dist/extension-point.js +36 -0
- package/dist/file-preview.d.ts +64 -0
- package/dist/file-preview.js +72 -0
- package/dist/frontmatter.d.ts +12 -0
- package/dist/frontmatter.js +36 -0
- package/dist/hooks.d.ts +195 -0
- package/dist/hooks.js +14 -0
- package/dist/identity.d.ts +44 -0
- package/dist/identity.js +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +28 -0
- package/dist/marketplace.d.ts +99 -0
- package/dist/marketplace.js +256 -0
- package/dist/model-params.d.ts +82 -0
- package/dist/model-params.js +222 -0
- package/dist/model.d.ts +227 -0
- package/dist/model.js +152 -0
- package/dist/permissions.d.ts +67 -0
- package/dist/permissions.js +271 -0
- package/dist/plugin-manifest.d.ts +141 -0
- package/dist/plugin-manifest.js +322 -0
- package/dist/plugin-package.d.ts +139 -0
- package/dist/plugin-package.js +448 -0
- package/dist/sandbox.d.ts +152 -0
- package/dist/sandbox.js +96 -0
- package/dist/secret.d.ts +5 -0
- package/dist/secret.js +8 -0
- package/dist/semver-range.d.ts +6 -0
- package/dist/semver-range.js +12 -0
- package/dist/skill-invocation.d.ts +83 -0
- package/dist/skill-invocation.js +201 -0
- package/dist/skill-keys.d.ts +9 -0
- package/dist/skill-keys.js +16 -0
- package/dist/skill.d.ts +77 -0
- package/dist/skill.js +244 -0
- package/dist/storage.d.ts +96 -0
- package/dist/storage.js +9 -0
- package/dist/tool-key.d.ts +28 -0
- package/dist/tool-key.js +40 -0
- package/dist/tool.d.ts +154 -0
- package/dist/tool.js +218 -0
- package/dist/ui.d.ts +265 -0
- package/dist/ui.js +358 -0
- package/dist/url.d.ts +2 -0
- package/dist/url.js +12 -0
- package/package.json +40 -3
- package/schemas/bootstrap.v1.schema.json +125 -0
- package/schemas/connector-template.v1.schema.json +116 -0
- package/schemas/marketplace.v1.schema.json +270 -0
- package/schemas/plugin.v1.schema.json +330 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { POLICY_HOOKS } from "./hooks.js";
|
|
2
|
+
import { UI_ICONS } from "./ui.js";
|
|
3
|
+
/** hook 挂载点(封闭列表,见总设计 2.4)。 */
|
|
4
|
+
export const HOOK_MOUNT_POINTS = [
|
|
5
|
+
"session.start",
|
|
6
|
+
"session.end",
|
|
7
|
+
"agent.preStep",
|
|
8
|
+
"model.request",
|
|
9
|
+
"model.response",
|
|
10
|
+
"tool.preExecute",
|
|
11
|
+
"tool.postExecute",
|
|
12
|
+
"usage.report",
|
|
13
|
+
];
|
|
14
|
+
const HOOKS = new Set(HOOK_MOUNT_POINTS);
|
|
15
|
+
/** A platform API not declared in the manifest was used. */
|
|
16
|
+
export class ExtensionPermissionError extends Error {
|
|
17
|
+
constructor(plugin, what) {
|
|
18
|
+
super(`extension ${plugin}: not allowed; declare "${what}" in the extension manifest`);
|
|
19
|
+
this.name = "ExtensionPermissionError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** The consumed extension point is unavailable (not installed, not enabled, or not enabled in that org). */
|
|
23
|
+
export class ExtensionPointUnavailableError extends Error {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "ExtensionPointUnavailableError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const KEYS = new Set([
|
|
30
|
+
"secrets",
|
|
31
|
+
"http",
|
|
32
|
+
"tools",
|
|
33
|
+
"hooks",
|
|
34
|
+
"policyHooks",
|
|
35
|
+
"models",
|
|
36
|
+
"storage",
|
|
37
|
+
"network",
|
|
38
|
+
"ui",
|
|
39
|
+
]);
|
|
40
|
+
const POLICY = new Set(POLICY_HOOKS);
|
|
41
|
+
const METHODS = new Set(["GET", "POST", "PUT", "DELETE"]);
|
|
42
|
+
const DOMAIN_RE = /^(?=.{1,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i;
|
|
43
|
+
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
44
|
+
function strList(label, v, errs) {
|
|
45
|
+
if (!Array.isArray(v)) {
|
|
46
|
+
errs.push(`${label} must be an array`);
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const out = [];
|
|
50
|
+
for (const x of v) {
|
|
51
|
+
if (typeof x !== "string" || x === "")
|
|
52
|
+
errs.push(`${label} entries must be non-empty strings`);
|
|
53
|
+
else if (out.includes(x))
|
|
54
|
+
errs.push(`${label} has duplicate entry "${x}"`);
|
|
55
|
+
else
|
|
56
|
+
out.push(x);
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
/** 校验权限清单,返回错误列表(空表示合法)。 */
|
|
61
|
+
export function validatePermissions(plugin, p) {
|
|
62
|
+
if (p === undefined)
|
|
63
|
+
return [];
|
|
64
|
+
if (!isObj(p))
|
|
65
|
+
return ["permissions must be an object"];
|
|
66
|
+
const errs = [];
|
|
67
|
+
for (const k of Object.keys(p)) {
|
|
68
|
+
if (!KEYS.has(k))
|
|
69
|
+
errs.push(`unknown permissions key "${k}"`);
|
|
70
|
+
}
|
|
71
|
+
for (const k of ["secrets", "storage"]) {
|
|
72
|
+
if (p[k] !== undefined && typeof p[k] !== "boolean") {
|
|
73
|
+
errs.push(`permissions.${k} must be a boolean`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (p.tools !== undefined)
|
|
77
|
+
strList("permissions.tools", p.tools, errs);
|
|
78
|
+
if (p.hooks !== undefined) {
|
|
79
|
+
for (const h of strList("permissions.hooks", p.hooks, errs)) {
|
|
80
|
+
if (!HOOKS.has(h)) {
|
|
81
|
+
errs.push(`permissions.hooks has unknown hook "${h}" (allowed: ${HOOK_MOUNT_POINTS.join(", ")})`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (p.policyHooks !== undefined) {
|
|
86
|
+
const declared = Array.isArray(p.hooks) ? p.hooks : [];
|
|
87
|
+
for (const h of strList("permissions.policyHooks", p.policyHooks, errs)) {
|
|
88
|
+
if (!POLICY.has(h)) {
|
|
89
|
+
errs.push(`permissions.policyHooks has "${h}", which takes no policy hooks (allowed: ${POLICY_HOOKS.join(", ")})`);
|
|
90
|
+
}
|
|
91
|
+
else if (!declared.includes(h)) {
|
|
92
|
+
errs.push(`permissions.policyHooks has "${h}", which is missing from permissions.hooks`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (p.network !== undefined) {
|
|
97
|
+
for (const d of strList("permissions.network", p.network, errs)) {
|
|
98
|
+
if (!DOMAIN_RE.test(d))
|
|
99
|
+
errs.push(`permissions.network entry "${d}" is not a valid domain`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (p.http !== undefined) {
|
|
103
|
+
if (!Array.isArray(p.http))
|
|
104
|
+
errs.push("permissions.http must be an array");
|
|
105
|
+
else {
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
for (const r of p.http) {
|
|
108
|
+
if (!isObj(r) ||
|
|
109
|
+
typeof r.method !== "string" ||
|
|
110
|
+
!METHODS.has(r.method) ||
|
|
111
|
+
typeof r.path !== "string" ||
|
|
112
|
+
!r.path.startsWith("/") ||
|
|
113
|
+
(r.public !== undefined && typeof r.public !== "boolean")) {
|
|
114
|
+
errs.push("permissions.http entries need method GET/POST/PUT/DELETE and a path starting with /");
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const key = `${r.method} ${r.path}`;
|
|
118
|
+
if (seen.has(key))
|
|
119
|
+
errs.push(`permissions.http has duplicate route "${key}"`);
|
|
120
|
+
seen.add(key);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (p.ui !== undefined) {
|
|
125
|
+
if (!isObj(p.ui))
|
|
126
|
+
errs.push("permissions.ui must be an object");
|
|
127
|
+
else {
|
|
128
|
+
for (const k of Object.keys(p.ui)) {
|
|
129
|
+
if (k !== "views" && k !== "embeds")
|
|
130
|
+
errs.push(`unknown permissions.ui key "${k}"`);
|
|
131
|
+
}
|
|
132
|
+
const views = p.ui.views === undefined ? [] : strList("permissions.ui.views", p.ui.views, errs);
|
|
133
|
+
const embeds = p.ui.embeds === undefined ? [] : strList("permissions.ui.embeds", p.ui.embeds, errs);
|
|
134
|
+
for (const id of views) {
|
|
135
|
+
if (embeds.includes(id))
|
|
136
|
+
errs.push(`permissions.ui id "${id}" is in both views and embeds`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (p.models !== undefined) {
|
|
141
|
+
if (!Array.isArray(p.models))
|
|
142
|
+
errs.push("permissions.models must be an array");
|
|
143
|
+
else {
|
|
144
|
+
const seen = new Set();
|
|
145
|
+
for (const m of p.models) {
|
|
146
|
+
const label = isObj(m) && isObj(m.label) ? m.label : undefined;
|
|
147
|
+
if (!isObj(m) ||
|
|
148
|
+
typeof m.purpose !== "string" ||
|
|
149
|
+
(m.kind !== "chat" && m.kind !== "embedding") ||
|
|
150
|
+
!label ||
|
|
151
|
+
typeof label.zh !== "string" ||
|
|
152
|
+
label.zh === "" ||
|
|
153
|
+
typeof label.en !== "string" ||
|
|
154
|
+
label.en === "") {
|
|
155
|
+
errs.push("permissions.models entries need purpose, kind chat/embedding and label zh/en");
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (!m.purpose.startsWith(`${plugin}.`) || m.purpose.length === plugin.length + 1) {
|
|
159
|
+
errs.push(`permissions.models purpose "${m.purpose}" must start with "${plugin}."`);
|
|
160
|
+
}
|
|
161
|
+
if (seen.has(m.purpose))
|
|
162
|
+
errs.push(`permissions.models has duplicate purpose "${m.purpose}"`);
|
|
163
|
+
seen.add(m.purpose);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return errs;
|
|
168
|
+
}
|
|
169
|
+
const UI_ID_RE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
170
|
+
const UI_TITLE_MAX = 100;
|
|
171
|
+
const UI_PLACES = [
|
|
172
|
+
"conversation",
|
|
173
|
+
"panel",
|
|
174
|
+
"section",
|
|
175
|
+
"inline",
|
|
176
|
+
"page",
|
|
177
|
+
"file-preview",
|
|
178
|
+
];
|
|
179
|
+
const UI_EMBED_PREFIX = "/ui/";
|
|
180
|
+
/**
|
|
181
|
+
* 校验清单 `ui` 并与权限清单交叉核对,返回错误列表(空表示合法):
|
|
182
|
+
* 每个贡献的 id 恰好出现在 `permissions.ui.views`(kind view)或 `permissions.ui.embeds`(kind embed)里,
|
|
183
|
+
* `permissions.ui` 里也不能有清单 `ui` 没有的 id;embed 的 path 以 `/ui/` 开头并在 `permissions.http` 中声明为 GET。
|
|
184
|
+
* 假定 `permissions` 已经过 {@link validatePermissions}。
|
|
185
|
+
*/
|
|
186
|
+
export function validateUiContributions(ui, permissions) {
|
|
187
|
+
const perms = isObj(permissions) ? permissions : {};
|
|
188
|
+
const pui = isObj(perms.ui) ? perms.ui : {};
|
|
189
|
+
const listed = (k) => Array.isArray(pui[k])
|
|
190
|
+
? pui[k].filter((x) => typeof x === "string")
|
|
191
|
+
: [];
|
|
192
|
+
const views = listed("views");
|
|
193
|
+
const embeds = listed("embeds");
|
|
194
|
+
const gets = new Set((Array.isArray(perms.http) ? perms.http : [])
|
|
195
|
+
.filter((r) => isObj(r) && r.method === "GET")
|
|
196
|
+
.map((r) => r.path));
|
|
197
|
+
const errs = [];
|
|
198
|
+
const contributions = [];
|
|
199
|
+
if (ui !== undefined) {
|
|
200
|
+
if (!Array.isArray(ui))
|
|
201
|
+
errs.push("ui must be an array");
|
|
202
|
+
else
|
|
203
|
+
contributions.push(...ui);
|
|
204
|
+
}
|
|
205
|
+
const seen = new Map();
|
|
206
|
+
const aliases = [];
|
|
207
|
+
contributions.forEach((c, i) => {
|
|
208
|
+
const at = `ui[${i}]`;
|
|
209
|
+
if (!isObj(c)) {
|
|
210
|
+
errs.push(`${at} must be an object`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (typeof c.id !== "string" || !UI_ID_RE.test(c.id)) {
|
|
214
|
+
errs.push(`${at}.id must match ${UI_ID_RE}`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const id = c.id;
|
|
218
|
+
const title = isObj(c.title) ? c.title : undefined;
|
|
219
|
+
if (!title ||
|
|
220
|
+
[title.zh, title.en].some((t) => typeof t !== "string" || t === "" || t.length > UI_TITLE_MAX))
|
|
221
|
+
errs.push(`ui "${id}" title needs non-empty zh and en of at most ${UI_TITLE_MAX} characters`);
|
|
222
|
+
if (!UI_PLACES.includes(c.place))
|
|
223
|
+
errs.push(`ui "${id}" place must be one of ${UI_PLACES.join(", ")}`);
|
|
224
|
+
if (c.kind !== "view" && c.kind !== "embed") {
|
|
225
|
+
errs.push(`ui "${id}" kind must be view or embed`);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (c.icon !== undefined && !UI_ICONS.includes(c.icon))
|
|
229
|
+
errs.push(`ui "${id}" icon must be one of ${UI_ICONS.join(", ")}`);
|
|
230
|
+
if (c.aliasOf !== undefined)
|
|
231
|
+
aliases.push({ id, kind: c.kind, target: c.aliasOf });
|
|
232
|
+
if (seen.has(id))
|
|
233
|
+
errs.push(`ui has duplicate id "${id}"`);
|
|
234
|
+
seen.set(id, c.kind);
|
|
235
|
+
if (c.kind === "view") {
|
|
236
|
+
if (c.place === "file-preview")
|
|
237
|
+
errs.push(`ui "${id}" place file-preview is only allowed for kind embed`);
|
|
238
|
+
if (c.path !== undefined)
|
|
239
|
+
errs.push(`ui "${id}" path is only allowed for kind embed`);
|
|
240
|
+
if (!views.includes(id))
|
|
241
|
+
errs.push(`ui "${id}" (view) must be listed in permissions.ui.views`);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (!embeds.includes(id))
|
|
245
|
+
errs.push(`ui "${id}" (embed) must be listed in permissions.ui.embeds`);
|
|
246
|
+
if (typeof c.path !== "string" || !c.path.startsWith(UI_EMBED_PREFIX)) {
|
|
247
|
+
errs.push(`ui "${id}" (embed) path must start with "${UI_EMBED_PREFIX}"`);
|
|
248
|
+
}
|
|
249
|
+
else if (!gets.has(c.path)) {
|
|
250
|
+
errs.push(`ui "${id}" (embed) path must be declared in permissions.http as "GET ${c.path}"`);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
const aliasIds = new Set(aliases.map((a) => a.id));
|
|
254
|
+
for (const a of aliases) {
|
|
255
|
+
if (typeof a.target !== "string" ||
|
|
256
|
+
a.target === a.id ||
|
|
257
|
+
aliasIds.has(a.target) ||
|
|
258
|
+
seen.get(a.target) !== a.kind)
|
|
259
|
+
errs.push(`ui "${a.id}" aliasOf must name another, non-alias ${a.kind} contribution`);
|
|
260
|
+
}
|
|
261
|
+
for (const [k, kind, ids] of [
|
|
262
|
+
["views", "view", views],
|
|
263
|
+
["embeds", "embed", embeds],
|
|
264
|
+
]) {
|
|
265
|
+
for (const id of ids) {
|
|
266
|
+
if (seen.get(id) !== kind)
|
|
267
|
+
errs.push(`permissions.ui.${k} entry "${id}" has no matching ${kind} contribution in ui`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return errs;
|
|
271
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type ConnectorTemplateSpec, type LocalizedName, type StaticExtensionManifest } from "./plugin-package.js";
|
|
3
|
+
/**
|
|
4
|
+
* The plugin manifest `.capybara-plugin/plugin.json` (package ecosystem spec §4.1). A plugin is a
|
|
5
|
+
* bundle of at most one extension, skills and connector templates, packed as an npm-pack tgz:
|
|
6
|
+
*
|
|
7
|
+
* ```
|
|
8
|
+
* package/package.json npm metadata; version equals plugin.json version
|
|
9
|
+
* package/.capybara-plugin/plugin.json this manifest (the packer writes it into every tgz)
|
|
10
|
+
* package/skills/<name>/SKILL.md skills
|
|
11
|
+
* package/connectors/<id>.json connector templates ({@link ConnectorTemplateFile})
|
|
12
|
+
* package/agents/<name>.md agent templates (parsed by `parseAgentTemplate`)
|
|
13
|
+
* package/dist/index.mjs extension entry (only with an extension)
|
|
14
|
+
* package/migrations/*.sql, package/ui/**
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Paths start with `./` and stay inside the plugin root. Omitting `skills` / `connectorTemplates`
|
|
18
|
+
* / `agents` means "scan `skills/*\/SKILL.md` / `connectors/*.json` / `agents/*.md`" (done by the
|
|
19
|
+
* kernel when it reads the package). `extension` is written by the packer, never by hand.
|
|
20
|
+
*
|
|
21
|
+
* The committed JSON Schema (`schemas/plugin.v1.schema.json`) is generated from
|
|
22
|
+
* {@link PluginManifestSchema} by `pnpm gen:package-schemas` and drift-checked by
|
|
23
|
+
* `check:package-schemas`. Rules JSON Schema cannot express (path safety and nesting, uniqueness,
|
|
24
|
+
* no self-dependency, extension name and version equal to the plugin's, the extension manifest
|
|
25
|
+
* rules of defineExtension) are decided only by {@link parsePluginManifest}. Unknown keys are
|
|
26
|
+
* rejected (the manifest is written by the author; a typo should fail, not be ignored).
|
|
27
|
+
*/
|
|
28
|
+
/** Path of the manifest inside the plugin root (and under `package/` in a tgz). */
|
|
29
|
+
export declare const PLUGIN_MANIFEST_FILE = ".capybara-plugin/plugin.json";
|
|
30
|
+
/** Manifest format version. */
|
|
31
|
+
export declare const PLUGIN_MANIFEST_SCHEMA_VERSION = 1;
|
|
32
|
+
/** Limits of the plugin manifest. */
|
|
33
|
+
export declare const PLUGIN_MANIFEST_LIMITS: {
|
|
34
|
+
/** Characters of the `plugin.json` text (larger text is not parsed). */
|
|
35
|
+
readonly manifestChars: number;
|
|
36
|
+
readonly dependencies: 20;
|
|
37
|
+
readonly skills: 50;
|
|
38
|
+
readonly connectorTemplates: 20;
|
|
39
|
+
/** Agent templates ({@link AGENT_TEMPLATE_LIMITS}`.perPlugin`). */
|
|
40
|
+
readonly agents: 64;
|
|
41
|
+
readonly descriptionChars: 1024;
|
|
42
|
+
readonly publisherChars: 100;
|
|
43
|
+
readonly displayNameChars: 100;
|
|
44
|
+
readonly pathChars: 512;
|
|
45
|
+
};
|
|
46
|
+
/** A plugin this one depends on: a plugin name and a semver range. */
|
|
47
|
+
export interface PluginDependency {
|
|
48
|
+
/** Catalog item name (`^[a-z0-9][a-z0-9-]{0,63}$`). */
|
|
49
|
+
name: string;
|
|
50
|
+
/** semver range. */
|
|
51
|
+
version: string;
|
|
52
|
+
}
|
|
53
|
+
/** A skill of the plugin: a `./…` directory holding SKILL.md. */
|
|
54
|
+
export interface PluginSkillRef {
|
|
55
|
+
path: string;
|
|
56
|
+
/** Suggested as pinned when an agent selects the skill (spec §9.1). */
|
|
57
|
+
pinned?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/** A connector template of the plugin: a `./…/<id>.json` file ({@link ConnectorTemplateFile}). */
|
|
60
|
+
export interface PluginConnectorTemplateRef {
|
|
61
|
+
path: string;
|
|
62
|
+
}
|
|
63
|
+
/** An agent template of the plugin: a `./…/<name>.md` file (see `parseAgentTemplate`). */
|
|
64
|
+
export interface PluginAgentRef {
|
|
65
|
+
path: string;
|
|
66
|
+
}
|
|
67
|
+
/** The plugin's extension, written by the packer. */
|
|
68
|
+
export interface PluginExtensionSection {
|
|
69
|
+
/** `./dist/index.mjs`. */
|
|
70
|
+
entry: string;
|
|
71
|
+
/** The static extension manifest (checked against the module's at load: `manifest_mismatch`). */
|
|
72
|
+
manifest: StaticExtensionManifest;
|
|
73
|
+
/** `./migrations`. */
|
|
74
|
+
migrations?: string;
|
|
75
|
+
/** `./ui`. */
|
|
76
|
+
ui?: string;
|
|
77
|
+
}
|
|
78
|
+
/** `.capybara-plugin/plugin.json`. */
|
|
79
|
+
export interface PluginManifest {
|
|
80
|
+
$schema?: string;
|
|
81
|
+
schemaVersion: 1;
|
|
82
|
+
/** Catalog item name; with an extension also an extension name, equal to `extension.manifest.name`. */
|
|
83
|
+
name: string;
|
|
84
|
+
/** Exact semver version. */
|
|
85
|
+
version: string;
|
|
86
|
+
publisher: string;
|
|
87
|
+
/** ≤ 1024 characters. */
|
|
88
|
+
description: string;
|
|
89
|
+
displayName?: LocalizedName;
|
|
90
|
+
/** `./…` .svg / .png inside the plugin. */
|
|
91
|
+
icon?: string;
|
|
92
|
+
license?: string;
|
|
93
|
+
/** https URL. */
|
|
94
|
+
homepage?: string;
|
|
95
|
+
/** No self-dependency, no duplicates. */
|
|
96
|
+
dependencies?: PluginDependency[];
|
|
97
|
+
/** Omitted = scan `skills/*\/SKILL.md`. */
|
|
98
|
+
skills?: PluginSkillRef[];
|
|
99
|
+
/** Omitted = scan `connectors/*.json`. */
|
|
100
|
+
connectorTemplates?: PluginConnectorTemplateRef[];
|
|
101
|
+
/** Agent templates; omitted = scan `agents/*.md`. */
|
|
102
|
+
agents?: PluginAgentRef[];
|
|
103
|
+
extension?: PluginExtensionSection;
|
|
104
|
+
}
|
|
105
|
+
/** A connector template file `connectors/<id>.json`; the template id is the file's base name. */
|
|
106
|
+
export interface ConnectorTemplateFile {
|
|
107
|
+
displayName: LocalizedName;
|
|
108
|
+
description?: string;
|
|
109
|
+
/** Safe path relative to the plugin root (an optional leading `./`), .svg / .png. */
|
|
110
|
+
icon?: string;
|
|
111
|
+
/** Validated by {@link validateConnectorTemplate}. */
|
|
112
|
+
template: ConnectorTemplateSpec;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The zod schema of `plugin.json` (strict objects: unknown keys rejected, `$schema` allowed).
|
|
116
|
+
* The JSON Schema is generated with `z.toJSONSchema(PluginManifestSchema, { io: "input" })`.
|
|
117
|
+
*/
|
|
118
|
+
export declare const PluginManifestSchema: z.ZodType<PluginManifest>;
|
|
119
|
+
/**
|
|
120
|
+
* The zod schema of a connector template file (strict). The JSON Schema is generated with
|
|
121
|
+
* `z.toJSONSchema(ConnectorTemplateFileSchema, { io: "input" })`.
|
|
122
|
+
*/
|
|
123
|
+
export declare const ConnectorTemplateFileSchema: z.ZodType<ConnectorTemplateFile>;
|
|
124
|
+
/**
|
|
125
|
+
* Parses and validates `plugin.json`: either the parsed JSON value or the file text (text larger
|
|
126
|
+
* than {@link PLUGIN_MANIFEST_LIMITS}`.manifestChars` is not parsed). Invalid input returns
|
|
127
|
+
* `{ errors }` (`<path>: <reason>`, English, at most 50); never throws. Whether the listed paths
|
|
128
|
+
* exist, and the "contains something" rule after scanning, are checked by the kernel when it reads
|
|
129
|
+
* the package.
|
|
130
|
+
*/
|
|
131
|
+
export declare function parsePluginManifest(json: unknown): PluginManifest | {
|
|
132
|
+
errors: string[];
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Parses and validates a connector template file (`connectors/<id>.json`): the parsed JSON value
|
|
136
|
+
* or the file text. Template problems come from {@link validateConnectorTemplate} (paths relative
|
|
137
|
+
* to the template root, values never echoed). Returns `{ errors }` (at most 50); never throws.
|
|
138
|
+
*/
|
|
139
|
+
export declare function parseConnectorTemplateFile(json: unknown): ConnectorTemplateFile | {
|
|
140
|
+
errors: string[];
|
|
141
|
+
};
|