@capybara-hq/plugin-sdk 0.0.0-stage → 0.1.0-rc.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/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 +194 -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 +27 -0
- package/dist/index.js +27 -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.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
package/dist/ui.js
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { validateInputForm } from "./tool.js";
|
|
2
|
+
import { isHttpUrl } from "./url.js";
|
|
3
|
+
/** A place with the old `conversation` mapped to `panel`. */
|
|
4
|
+
export function normalizePlace(place) {
|
|
5
|
+
return place === "conversation" ? "panel" : place;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Icons a contribution may use (design system icon names; `puzzle` is the default and is drawn as
|
|
9
|
+
* the design `extension` icon). The title-bar button of a panel shows it.
|
|
10
|
+
*/
|
|
11
|
+
export const UI_ICONS = [
|
|
12
|
+
"list-checks",
|
|
13
|
+
"layout-grid",
|
|
14
|
+
"file",
|
|
15
|
+
"chart",
|
|
16
|
+
"calendar",
|
|
17
|
+
"users",
|
|
18
|
+
"bot",
|
|
19
|
+
"book",
|
|
20
|
+
"message-square",
|
|
21
|
+
"puzzle",
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* 视图上限({@link validateUiView} 统一检查):
|
|
25
|
+
* - `blocks`:块数;
|
|
26
|
+
* - `tableRows`:表格行数,也用作 keyValue 项数、表格列数、图表点数、按钮数的上限;
|
|
27
|
+
* - `checklistItems`:清单项数;
|
|
28
|
+
* - `textChars`:整个视图所有文本(含表单 JSON)的字符总数;
|
|
29
|
+
* - `payloadBytes`:单个按钮 `payload` 的 JSON 序列化后 UTF-8 字节数。
|
|
30
|
+
*/
|
|
31
|
+
export const UI_LIMITS = {
|
|
32
|
+
blocks: 50,
|
|
33
|
+
tableRows: 200,
|
|
34
|
+
checklistItems: 100,
|
|
35
|
+
textChars: 20000,
|
|
36
|
+
payloadBytes: 2000,
|
|
37
|
+
};
|
|
38
|
+
const BADGE_MAX = 32;
|
|
39
|
+
/** Longest item / block `id` and open target `item`. */
|
|
40
|
+
const ITEM_ID_MAX = 200;
|
|
41
|
+
/** A ui contribution id (same rule as the manifest's). */
|
|
42
|
+
const OPEN_ID_RE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
43
|
+
const CHECK_STATUSES = ["pending", "in_progress", "completed"];
|
|
44
|
+
const CHART_KINDS = ["bar", "line"];
|
|
45
|
+
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
46
|
+
const MD_INLINE_LINK = /\]\(\s*<?([^\s)>]*)/g;
|
|
47
|
+
const MD_REF_DEF = /^ {0,3}\[[^\]\n]+\]:[ \t]*<?([^\s>]*)/gm;
|
|
48
|
+
const MD_AUTOLINK = /<([a-zA-Z][a-zA-Z0-9+.-]{1,31}:[^<>\s]*)>/g;
|
|
49
|
+
/** Markdown 里的链接 / 图片目标(行内、引用定义、尖括号自动链接)。 */
|
|
50
|
+
function markdownLinks(md) {
|
|
51
|
+
const out = [];
|
|
52
|
+
for (const re of [MD_INLINE_LINK, MD_REF_DEF, MD_AUTOLINK]) {
|
|
53
|
+
for (const m of md.matchAll(re))
|
|
54
|
+
out.push(m[1] ?? "");
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
/** JSON 序列化后的 UTF-8 字节数;不可序列化返回 undefined。 */
|
|
59
|
+
function jsonBytes(v) {
|
|
60
|
+
try {
|
|
61
|
+
const s = JSON.stringify(v);
|
|
62
|
+
return typeof s === "string" ? new TextEncoder().encode(s).length : undefined;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 校验插件返回的视图,返回问题列表(空 = 合法)。链接与图片只允许 http(s) 绝对地址;
|
|
70
|
+
* Markdown 的检查是纵深防御,网页渲染时仍须只认 http(s) 链接、不渲染原生 HTML。
|
|
71
|
+
* 按钮与表单的 `actionId` 在整个视图内唯一(动作回调靠它区分来源)。
|
|
72
|
+
*/
|
|
73
|
+
export function validateUiView(v) {
|
|
74
|
+
if (!isObj(v))
|
|
75
|
+
return ["view must be an object"];
|
|
76
|
+
const out = [];
|
|
77
|
+
let chars = 0;
|
|
78
|
+
const text = (at, s, nonEmpty = false) => {
|
|
79
|
+
if (typeof s !== "string" || (nonEmpty && s === "")) {
|
|
80
|
+
out.push(`${at} must be a ${nonEmpty ? "non-empty " : ""}string`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
chars += s.length;
|
|
84
|
+
};
|
|
85
|
+
const actionIds = new Set();
|
|
86
|
+
const actionId = (at, id) => {
|
|
87
|
+
text(at, id, true);
|
|
88
|
+
if (typeof id !== "string" || id === "")
|
|
89
|
+
return;
|
|
90
|
+
if (actionIds.has(id))
|
|
91
|
+
out.push(`${at} is a duplicate actionId "${id}" in this view`);
|
|
92
|
+
actionIds.add(id);
|
|
93
|
+
};
|
|
94
|
+
const openTarget = (at, o) => {
|
|
95
|
+
if (!isObj(o)) {
|
|
96
|
+
out.push(`${at} must be an object`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (typeof o.contribution !== "string" || !OPEN_ID_RE.test(o.contribution))
|
|
100
|
+
out.push(`${at}.contribution must be a ui contribution id`);
|
|
101
|
+
else
|
|
102
|
+
chars += o.contribution.length;
|
|
103
|
+
if (o.item !== undefined) {
|
|
104
|
+
if (typeof o.item !== "string" || o.item === "" || o.item.length > ITEM_ID_MAX)
|
|
105
|
+
out.push(`${at}.item must be a non-empty string of at most ${ITEM_ID_MAX} characters`);
|
|
106
|
+
else
|
|
107
|
+
chars += o.item.length;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const anchor = (at, it) => {
|
|
111
|
+
if (it.id !== undefined) {
|
|
112
|
+
if (typeof it.id !== "string" || it.id === "" || it.id.length > ITEM_ID_MAX)
|
|
113
|
+
out.push(`${at}.id must be a non-empty string of at most ${ITEM_ID_MAX} characters`);
|
|
114
|
+
else
|
|
115
|
+
chars += it.id.length;
|
|
116
|
+
}
|
|
117
|
+
if (it.open !== undefined)
|
|
118
|
+
openTarget(`${at}.open`, it.open);
|
|
119
|
+
};
|
|
120
|
+
const list = (at, xs, max) => {
|
|
121
|
+
if (!Array.isArray(xs)) {
|
|
122
|
+
out.push(`${at} must be an array`);
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
if (xs.length > max)
|
|
126
|
+
out.push(`${at} must have at most ${max} entries`);
|
|
127
|
+
return xs;
|
|
128
|
+
};
|
|
129
|
+
if (v.badge !== undefined) {
|
|
130
|
+
if (typeof v.badge !== "string" || v.badge.length > BADGE_MAX)
|
|
131
|
+
out.push(`badge must be a string of at most ${BADGE_MAX} characters`);
|
|
132
|
+
else
|
|
133
|
+
chars += v.badge.length;
|
|
134
|
+
}
|
|
135
|
+
if (v.open !== undefined)
|
|
136
|
+
openTarget("open", v.open);
|
|
137
|
+
const blocks = list("blocks", v.blocks, UI_LIMITS.blocks);
|
|
138
|
+
blocks.forEach((b, i) => {
|
|
139
|
+
const at = `blocks[${i}]`;
|
|
140
|
+
if (!isObj(b)) {
|
|
141
|
+
out.push(`${at} must be an object`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
switch (b.type) {
|
|
145
|
+
case "heading":
|
|
146
|
+
text(`${at}.text`, b.text, true);
|
|
147
|
+
return;
|
|
148
|
+
case "text": {
|
|
149
|
+
text(`${at}.markdown`, b.markdown);
|
|
150
|
+
if (typeof b.markdown === "string") {
|
|
151
|
+
for (const link of markdownLinks(b.markdown)) {
|
|
152
|
+
if (!isHttpUrl(link))
|
|
153
|
+
out.push(`${at}.markdown has a link that is not an absolute http(s) URL`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
case "checklist":
|
|
159
|
+
list(`${at}.items`, b.items, UI_LIMITS.checklistItems).forEach((it, j) => {
|
|
160
|
+
const iat = `${at}.items[${j}]`;
|
|
161
|
+
if (!isObj(it)) {
|
|
162
|
+
out.push(`${iat} must be an object`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
text(`${iat}.text`, it.text, true);
|
|
166
|
+
if (!CHECK_STATUSES.includes(it.status))
|
|
167
|
+
out.push(`${iat}.status must be one of ${CHECK_STATUSES.join(", ")}`);
|
|
168
|
+
anchor(iat, it);
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
case "keyValue":
|
|
172
|
+
list(`${at}.items`, b.items, UI_LIMITS.tableRows).forEach((it, j) => {
|
|
173
|
+
const iat = `${at}.items[${j}]`;
|
|
174
|
+
if (!isObj(it)) {
|
|
175
|
+
out.push(`${iat} must be an object`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
text(`${iat}.label`, it.label);
|
|
179
|
+
text(`${iat}.value`, it.value);
|
|
180
|
+
anchor(iat, it);
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
case "table": {
|
|
184
|
+
const keys = new Set();
|
|
185
|
+
list(`${at}.columns`, b.columns, UI_LIMITS.tableRows).forEach((c, j) => {
|
|
186
|
+
const cat = `${at}.columns[${j}]`;
|
|
187
|
+
if (!isObj(c) || typeof c.key !== "string" || c.key === "") {
|
|
188
|
+
out.push(`${cat}.key must be a non-empty string`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (keys.has(c.key))
|
|
192
|
+
out.push(`${cat}.key "${c.key}" is a duplicate`);
|
|
193
|
+
keys.add(c.key);
|
|
194
|
+
text(`${cat}.key`, c.key);
|
|
195
|
+
text(`${cat}.label`, c.label);
|
|
196
|
+
});
|
|
197
|
+
const rows = list(`${at}.rows`, b.rows, UI_LIMITS.tableRows);
|
|
198
|
+
rows.forEach((r, j) => {
|
|
199
|
+
const rat = `${at}.rows[${j}]`;
|
|
200
|
+
if (!isObj(r)) {
|
|
201
|
+
out.push(`${rat} must be an object`);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
for (const [k, cell] of Object.entries(r)) {
|
|
205
|
+
if (!keys.has(k))
|
|
206
|
+
out.push(`${rat} has unknown column "${k}"`);
|
|
207
|
+
text(`${rat}.${k}`, cell);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
if (b.open !== undefined) {
|
|
211
|
+
const opens = list(`${at}.open`, b.open, rows.length);
|
|
212
|
+
opens.forEach((o, j) => {
|
|
213
|
+
if (o !== null)
|
|
214
|
+
openTarget(`${at}.open[${j}]`, o);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
case "code":
|
|
220
|
+
if (b.language !== undefined)
|
|
221
|
+
text(`${at}.language`, b.language, true);
|
|
222
|
+
text(`${at}.text`, b.text);
|
|
223
|
+
return;
|
|
224
|
+
case "image":
|
|
225
|
+
if (!isHttpUrl(b.url))
|
|
226
|
+
out.push(`${at}.url must be an absolute http(s) URL of at most 2000 characters`);
|
|
227
|
+
else
|
|
228
|
+
chars += b.url.length;
|
|
229
|
+
text(`${at}.alt`, b.alt);
|
|
230
|
+
return;
|
|
231
|
+
case "chart":
|
|
232
|
+
if (!CHART_KINDS.includes(b.kind))
|
|
233
|
+
out.push(`${at}.kind must be one of ${CHART_KINDS.join(", ")}`);
|
|
234
|
+
list(`${at}.series`, b.series, UI_LIMITS.tableRows).forEach((p, j) => {
|
|
235
|
+
const pat = `${at}.series[${j}]`;
|
|
236
|
+
if (!isObj(p)) {
|
|
237
|
+
out.push(`${pat} must be an object`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
text(`${pat}.label`, p.label);
|
|
241
|
+
if (typeof p.value !== "number" || !Number.isFinite(p.value))
|
|
242
|
+
out.push(`${pat}.value must be a finite number`);
|
|
243
|
+
});
|
|
244
|
+
return;
|
|
245
|
+
case "actions": {
|
|
246
|
+
const buttons = list(`${at}.buttons`, b.buttons, UI_LIMITS.tableRows);
|
|
247
|
+
if (Array.isArray(b.buttons) && buttons.length === 0)
|
|
248
|
+
out.push(`${at}.buttons must not be empty`);
|
|
249
|
+
buttons.forEach((btn, j) => {
|
|
250
|
+
const bat = `${at}.buttons[${j}]`;
|
|
251
|
+
if (!isObj(btn)) {
|
|
252
|
+
out.push(`${bat} must be an object`);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
actionId(`${bat}.actionId`, btn.actionId);
|
|
256
|
+
text(`${bat}.label`, btn.label, true);
|
|
257
|
+
if (btn.danger !== undefined && typeof btn.danger !== "boolean")
|
|
258
|
+
out.push(`${bat}.danger must be a boolean`);
|
|
259
|
+
if (btn.payload !== undefined) {
|
|
260
|
+
const n = jsonBytes(btn.payload);
|
|
261
|
+
if (n === undefined)
|
|
262
|
+
out.push(`${bat}.payload must be JSON-serializable`);
|
|
263
|
+
else if (n > UI_LIMITS.payloadBytes)
|
|
264
|
+
out.push(`${bat}.payload must be at most ${UI_LIMITS.payloadBytes} bytes as JSON`);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
case "form": {
|
|
270
|
+
if (!isObj(b.form))
|
|
271
|
+
out.push(`${at}.form must be an object`);
|
|
272
|
+
else {
|
|
273
|
+
for (const p of validateInputForm(b.form))
|
|
274
|
+
out.push(`${at}.form: ${p}`);
|
|
275
|
+
chars += jsonBytes(b.form) === undefined ? 0 : JSON.stringify(b.form).length;
|
|
276
|
+
}
|
|
277
|
+
actionId(`${at}.actionId`, b.actionId);
|
|
278
|
+
text(`${at}.submitLabel`, b.submitLabel, true);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
default:
|
|
282
|
+
out.push(`${at}.type ${JSON.stringify(b.type)} is not a known block type`);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
if (chars > UI_LIMITS.textChars)
|
|
286
|
+
out.push(`view text must be at most ${UI_LIMITS.textChars} characters in total`);
|
|
287
|
+
return out;
|
|
288
|
+
}
|
|
289
|
+
/** 外层页面与嵌入 iframe 之间 `postMessage` 协议的版本标识。 */
|
|
290
|
+
export const EMBED_PROTOCOL = "capybara-embed/1";
|
|
291
|
+
const NAV_MAX = 2000;
|
|
292
|
+
const RESIZE_MAX = 10000;
|
|
293
|
+
const TOAST_MAX = 500;
|
|
294
|
+
// capybara:embed-nav-begin(这一段在 packages/plugin-sdk/src/ui.ts 与 apps/web/src/lib/embed-bridge.ts 里逐字相同,有测试核对)
|
|
295
|
+
// 控制字符与反斜杠:浏览器会把 `/\host` 当成 `//host`,也会剥掉路径里的换行 / 制表符
|
|
296
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: 正是要拒绝控制字符
|
|
297
|
+
const NAV_BAD = /[\u0000-\u001f\u007f\\]/;
|
|
298
|
+
/** 路径部分里编码的 `/`、`\` 与控制字符(任意大小写):解码后会改变路径结构。 */
|
|
299
|
+
const NAV_BAD_ENCODED = /%(?:2f|5c|[01][0-9a-f]|7f)/i;
|
|
300
|
+
/**
|
|
301
|
+
* 站内路径:以单个 `/` 开头,任何位置都没有 `//`,没有控制字符与反斜杠;路径部分没有 `.` / `..` 段
|
|
302
|
+
* (含任意大小写的 `%2e` 编码),也没有编码的 `/`(`%2f`)、`\`(`%5c`)与控制字符(`%00`–`%1f`、`%7f`);
|
|
303
|
+
* 且按 URL 规范化后与原文完全相同、不以 `//` 开头——规范化不会把它变成别的路径,
|
|
304
|
+
* 交给站内路由时不会跳到站外或上层目录。
|
|
305
|
+
*/
|
|
306
|
+
function isInSitePath(p) {
|
|
307
|
+
if (!p.startsWith("/") || p.includes("//") || NAV_BAD.test(p))
|
|
308
|
+
return false;
|
|
309
|
+
const path = p.split(/[?#]/, 1)[0] ?? "";
|
|
310
|
+
if (NAV_BAD_ENCODED.test(path))
|
|
311
|
+
return false;
|
|
312
|
+
for (const seg of path.split("/")) {
|
|
313
|
+
const s = seg.toLowerCase().replaceAll("%2e", ".");
|
|
314
|
+
if (s === "." || s === "..")
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
let u;
|
|
318
|
+
try {
|
|
319
|
+
u = new URL(p, "http://x");
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
return u.pathname + u.search + u.hash === p && !u.pathname.startsWith("//");
|
|
325
|
+
}
|
|
326
|
+
// capybara:embed-nav-end
|
|
327
|
+
/**
|
|
328
|
+
* 解析 iframe 发来的消息:协议、类型、字段都对才返回(只留已知字段的新对象),否则返回 null。
|
|
329
|
+
* 外层页面还须先核对消息来源是这个 iframe。
|
|
330
|
+
*/
|
|
331
|
+
export function parseEmbedChildMessage(m) {
|
|
332
|
+
if (!isObj(m) || m.protocol !== EMBED_PROTOCOL)
|
|
333
|
+
return null;
|
|
334
|
+
const protocol = EMBED_PROTOCOL;
|
|
335
|
+
switch (m.type) {
|
|
336
|
+
case "resize":
|
|
337
|
+
return typeof m.height === "number" &&
|
|
338
|
+
Number.isFinite(m.height) &&
|
|
339
|
+
m.height >= 0 &&
|
|
340
|
+
m.height <= RESIZE_MAX
|
|
341
|
+
? { protocol, type: "resize", height: m.height }
|
|
342
|
+
: null;
|
|
343
|
+
case "navigate": {
|
|
344
|
+
const p = m.path;
|
|
345
|
+
if (typeof p !== "string" || p.length > NAV_MAX)
|
|
346
|
+
return null;
|
|
347
|
+
if (!isInSitePath(p))
|
|
348
|
+
return null;
|
|
349
|
+
return { protocol, type: "navigate", path: p };
|
|
350
|
+
}
|
|
351
|
+
case "toast":
|
|
352
|
+
return typeof m.text === "string" && m.text !== "" && m.text.length <= TOAST_MAX
|
|
353
|
+
? { protocol, type: "toast", text: m.text }
|
|
354
|
+
: null;
|
|
355
|
+
default:
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
}
|
package/dist/url.d.ts
ADDED
package/dist/url.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** 内部工具(index.ts 不导出):是否为 http(s) 绝对地址且长度不超过 max。 */
|
|
2
|
+
export function isHttpUrl(v, max = 2000) {
|
|
3
|
+
if (typeof v !== "string" || v.length > max)
|
|
4
|
+
return false;
|
|
5
|
+
try {
|
|
6
|
+
const u = new URL(v);
|
|
7
|
+
return u.protocol === "https:" || u.protocol === "http:";
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capybara-hq/plugin-sdk",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0-rc.0",
|
|
4
|
+
"description": "Capybara plugin SDK: the types and pure functions plugins are written against",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/capybara-hq/core.git",
|
|
9
|
+
"directory": "packages/plugin-sdk"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/capybara-hq/core/tree/main/packages/plugin-sdk#readme",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"schemas",
|
|
25
|
+
"README.md",
|
|
26
|
+
"NOTICE"
|
|
27
|
+
],
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"kysely": "^0.29.6",
|
|
30
|
+
"semver": "^7.8.5",
|
|
31
|
+
"yaml": "^2.9.1",
|
|
32
|
+
"zod": "^4.6.7"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@microsoft/api-extractor": "7.59.4",
|
|
36
|
+
"@types/semver": "^7.8.0",
|
|
37
|
+
"ajv": "^8.20.0",
|
|
38
|
+
"ajv-formats": "^3.0.1"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsc -b"
|
|
42
|
+
}
|
|
6
43
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://capybarahq.com/schemas/bootstrap.v1.schema.json",
|
|
4
|
+
"title": "Capybara bootstrap manifest packages/kernel/bootstrap.json (schemaVersion 1)",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"$schema": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"maxLength": 2000
|
|
10
|
+
},
|
|
11
|
+
"schemaVersion": {
|
|
12
|
+
"type": "number",
|
|
13
|
+
"const": 1
|
|
14
|
+
},
|
|
15
|
+
"marketplace": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"properties": {
|
|
18
|
+
"url": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"maxLength": 2000,
|
|
21
|
+
"pattern": "^https:\\/\\/\\S+$"
|
|
22
|
+
},
|
|
23
|
+
"commit": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"pattern": "^[0-9a-f]{40}$"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"required": [
|
|
29
|
+
"url",
|
|
30
|
+
"commit"
|
|
31
|
+
],
|
|
32
|
+
"additionalProperties": false
|
|
33
|
+
},
|
|
34
|
+
"plugins": {
|
|
35
|
+
"maxItems": 500,
|
|
36
|
+
"type": "array",
|
|
37
|
+
"items": {
|
|
38
|
+
"oneOf": [
|
|
39
|
+
{
|
|
40
|
+
"type": "object",
|
|
41
|
+
"properties": {
|
|
42
|
+
"name": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"pattern": "^[a-z0-9][a-z0-9-]{0,63}$"
|
|
45
|
+
},
|
|
46
|
+
"version": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"maxLength": 256,
|
|
49
|
+
"pattern": "^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(?:\\+[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*)?$"
|
|
50
|
+
},
|
|
51
|
+
"integrity": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"pattern": "^sha512-[A-Za-z0-9+/]{86}==$"
|
|
54
|
+
},
|
|
55
|
+
"required": {
|
|
56
|
+
"type": "boolean"
|
|
57
|
+
},
|
|
58
|
+
"source": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"const": "npm"
|
|
61
|
+
},
|
|
62
|
+
"npm": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"maxLength": 214,
|
|
65
|
+
"pattern": "^(?:@[a-z0-9][a-z0-9._~-]*\\/)?[a-z0-9][a-z0-9._~-]*$"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"required": [
|
|
69
|
+
"name",
|
|
70
|
+
"version",
|
|
71
|
+
"integrity",
|
|
72
|
+
"required",
|
|
73
|
+
"source",
|
|
74
|
+
"npm"
|
|
75
|
+
],
|
|
76
|
+
"additionalProperties": false
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"type": "object",
|
|
80
|
+
"properties": {
|
|
81
|
+
"name": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"pattern": "^[a-z0-9][a-z0-9-]{0,63}$"
|
|
84
|
+
},
|
|
85
|
+
"version": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"maxLength": 256,
|
|
88
|
+
"pattern": "^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(?:\\+[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*)?$"
|
|
89
|
+
},
|
|
90
|
+
"integrity": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"pattern": "^sha512-[A-Za-z0-9+/]{86}==$"
|
|
93
|
+
},
|
|
94
|
+
"required": {
|
|
95
|
+
"type": "boolean"
|
|
96
|
+
},
|
|
97
|
+
"source": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"const": "workspace"
|
|
100
|
+
},
|
|
101
|
+
"path": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"pattern": "^plugins\\/[a-z0-9][a-z0-9-]{0,63}$"
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"required": [
|
|
107
|
+
"name",
|
|
108
|
+
"version",
|
|
109
|
+
"integrity",
|
|
110
|
+
"required",
|
|
111
|
+
"source",
|
|
112
|
+
"path"
|
|
113
|
+
],
|
|
114
|
+
"additionalProperties": false
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"required": [
|
|
121
|
+
"schemaVersion",
|
|
122
|
+
"plugins"
|
|
123
|
+
],
|
|
124
|
+
"additionalProperties": false
|
|
125
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://capybarahq.com/schemas/connector-template.v1.schema.json",
|
|
4
|
+
"title": "Capybara connector template file connectors/<id>.json",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"displayName": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"zh": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"minLength": 1,
|
|
13
|
+
"maxLength": 100,
|
|
14
|
+
"pattern": "\\S"
|
|
15
|
+
},
|
|
16
|
+
"en": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"minLength": 1,
|
|
19
|
+
"maxLength": 100,
|
|
20
|
+
"pattern": "\\S"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"required": [
|
|
24
|
+
"zh",
|
|
25
|
+
"en"
|
|
26
|
+
],
|
|
27
|
+
"additionalProperties": false
|
|
28
|
+
},
|
|
29
|
+
"description": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"minLength": 1,
|
|
32
|
+
"maxLength": 1024,
|
|
33
|
+
"pattern": "\\S"
|
|
34
|
+
},
|
|
35
|
+
"icon": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"maxLength": 512
|
|
38
|
+
},
|
|
39
|
+
"template": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"properties": {
|
|
42
|
+
"plugin": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"maxLength": 100
|
|
45
|
+
},
|
|
46
|
+
"pluginVersion": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"maxLength": 256
|
|
49
|
+
},
|
|
50
|
+
"connector": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"maxLength": 100
|
|
53
|
+
},
|
|
54
|
+
"endpoint": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"propertyNames": {
|
|
57
|
+
"type": "string"
|
|
58
|
+
},
|
|
59
|
+
"additionalProperties": {}
|
|
60
|
+
},
|
|
61
|
+
"editableEndpoint": {
|
|
62
|
+
"maxItems": 50,
|
|
63
|
+
"type": "array",
|
|
64
|
+
"items": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"maxLength": 1000
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"auth": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"propertyNames": {
|
|
72
|
+
"type": "string"
|
|
73
|
+
},
|
|
74
|
+
"additionalProperties": {}
|
|
75
|
+
},
|
|
76
|
+
"ownership": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"enum": [
|
|
79
|
+
"org",
|
|
80
|
+
"personal"
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
"defaultToolPolicies": {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"propertyNames": {
|
|
86
|
+
"type": "string"
|
|
87
|
+
},
|
|
88
|
+
"additionalProperties": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"enum": [
|
|
91
|
+
"allow",
|
|
92
|
+
"ask",
|
|
93
|
+
"deny"
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"icon": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"maxLength": 512
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"required": [
|
|
103
|
+
"plugin",
|
|
104
|
+
"connector",
|
|
105
|
+
"endpoint",
|
|
106
|
+
"auth"
|
|
107
|
+
],
|
|
108
|
+
"additionalProperties": false
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"required": [
|
|
112
|
+
"displayName",
|
|
113
|
+
"template"
|
|
114
|
+
],
|
|
115
|
+
"additionalProperties": false
|
|
116
|
+
}
|