@capybara-hq/plugin-sdk 0.1.0-rc.6 → 0.1.0-rc.8
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/agent-docs/connector-templates.md +1 -0
- package/agent-docs/plugin-format.md +17 -2
- package/dist/cards.d.ts +7 -0
- package/dist/cards.js +4 -4
- package/dist/channel.d.ts +20 -1
- package/dist/connector-auth.d.ts +2 -0
- package/dist/connector-auth.js +15 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/marketplace.d.ts +26 -0
- package/dist/marketplace.js +43 -1
- package/dist/plugin-manifest.d.ts +22 -3
- package/dist/plugin-manifest.js +85 -5
- package/dist/setup-guide.d.ts +94 -0
- package/dist/setup-guide.js +135 -0
- package/dist/theme-assets.d.ts +39 -0
- package/dist/theme-assets.js +449 -0
- package/dist/theme-readability.d.ts +37 -0
- package/dist/theme-readability.js +145 -0
- package/dist/theme-tokens.generated.d.ts +275 -0
- package/dist/theme-tokens.generated.js +167 -0
- package/dist/theme.d.ts +152 -0
- package/dist/theme.js +392 -0
- package/package.json +2 -1
- package/schemas/connector-template.v1.schema.json +3 -0
- package/schemas/marketplace.v1.schema.json +14 -0
- package/schemas/plugin.v1.schema.json +155 -0
- package/schemas/theme.v1.schema.json +225 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin setup guides (spec `2026-11-08-plugin-setup-guides-design.md`): setup material a plugin
|
|
3
|
+
* ships (steps and template files such as Slack's app manifest) that the kernel renders with a
|
|
4
|
+
* fixed set of safe, deterministic variables and the web shows where the person needs it. The
|
|
5
|
+
* format lives in `plugin.json` `setupGuides` ({@link PluginSetupGuide}); this module holds the
|
|
6
|
+
* places, their modes and variables, the limits and the pure renderer.
|
|
7
|
+
*
|
|
8
|
+
* Placeholders are `{{name}}` (spaces inside the braces allowed) with `name` matching
|
|
9
|
+
* `[A-Za-z][A-Za-z0-9]*`. There is no other syntax: no conditionals, loops, filters or escapes. A
|
|
10
|
+
* placeholder the place does not define, or one without a value, stays as written.
|
|
11
|
+
*/
|
|
12
|
+
/** Where a guide is shown. `channel-connection`: 新建渠道 and the channel detail page. */
|
|
13
|
+
export declare const SETUP_GUIDE_PLACES: readonly ["channel-connection"];
|
|
14
|
+
export type SetupGuidePlace = (typeof SETUP_GUIDE_PLACES)[number];
|
|
15
|
+
/**
|
|
16
|
+
* The modes of each place: a step or file with `mode` applies to that mode only.
|
|
17
|
+
* `channel-connection`: the connection's `inboundMode`.
|
|
18
|
+
*/
|
|
19
|
+
export declare const SETUP_GUIDE_MODES: Readonly<Record<SetupGuidePlace, readonly string[]>>;
|
|
20
|
+
/**
|
|
21
|
+
* The variables of each place. None is a secret: the kernel fills them from public URLs, names and
|
|
22
|
+
* versions only (`publicUrl`, `webhookUrl`, `signInCallbackUrl`, `connectionName`, `orgName`,
|
|
23
|
+
* `pluginName`, `pluginVersion`, `mode`).
|
|
24
|
+
*/
|
|
25
|
+
export declare const SETUP_GUIDE_VARIABLES: Readonly<Record<SetupGuidePlace, readonly string[]>>;
|
|
26
|
+
/** Languages of a template file: the code block's label and how values are escaped. */
|
|
27
|
+
export declare const SETUP_GUIDE_LANGUAGES: readonly ["yaml", "json", "text"];
|
|
28
|
+
export type SetupGuideLanguage = (typeof SETUP_GUIDE_LANGUAGES)[number];
|
|
29
|
+
/** Limits of setup guides. */
|
|
30
|
+
export declare const SETUP_GUIDE_LIMITS: {
|
|
31
|
+
/** Guides per plugin. */
|
|
32
|
+
readonly guides: 8;
|
|
33
|
+
/** Steps per guide. */
|
|
34
|
+
readonly steps: 20;
|
|
35
|
+
/** Files per guide. */
|
|
36
|
+
readonly files: 8;
|
|
37
|
+
/** Characters of a step (each language). */
|
|
38
|
+
readonly stepChars: 2000;
|
|
39
|
+
/** Characters of a title (each language). */
|
|
40
|
+
readonly titleChars: 100;
|
|
41
|
+
/** Bytes of a template file. */
|
|
42
|
+
readonly fileBytes: number;
|
|
43
|
+
};
|
|
44
|
+
/** File names a template file may have (checked on the path). */
|
|
45
|
+
export declare const SETUP_GUIDE_FILE_RE: RegExp;
|
|
46
|
+
/** Localized text: English, optional Chinese (falls back to English). */
|
|
47
|
+
export interface SetupGuideText {
|
|
48
|
+
en: string;
|
|
49
|
+
zh?: string;
|
|
50
|
+
}
|
|
51
|
+
/** A step: plain text (a backtick pair is shown as inline code), optionally for one mode. */
|
|
52
|
+
export interface SetupGuideStep extends SetupGuideText {
|
|
53
|
+
mode?: string;
|
|
54
|
+
}
|
|
55
|
+
/** A template file of a guide. */
|
|
56
|
+
export interface SetupGuideFile {
|
|
57
|
+
/** `./…` inside the plugin, `.tmpl` / `.yaml` / `.yml` / `.json` / `.txt`. */
|
|
58
|
+
path: string;
|
|
59
|
+
title: SetupGuideText;
|
|
60
|
+
language: SetupGuideLanguage;
|
|
61
|
+
mode?: string;
|
|
62
|
+
}
|
|
63
|
+
/** A setup guide in `plugin.json` `setupGuides`. */
|
|
64
|
+
export interface PluginSetupGuide {
|
|
65
|
+
/** `^[a-z0-9][a-z0-9-]{0,63}$`, unique in the plugin. */
|
|
66
|
+
id: string;
|
|
67
|
+
place: SetupGuidePlace;
|
|
68
|
+
title: SetupGuideText;
|
|
69
|
+
steps?: SetupGuideStep[];
|
|
70
|
+
files?: SetupGuideFile[];
|
|
71
|
+
}
|
|
72
|
+
/** The placeholder names a text uses, each once, in order of first use. */
|
|
73
|
+
export declare function setupGuidePlaceholders(text: string): string[];
|
|
74
|
+
/** The placeholders of a text that the place does not define (the toolchain refuses them). */
|
|
75
|
+
export declare function unknownSetupGuidePlaceholders(text: string, place: SetupGuidePlace): string[];
|
|
76
|
+
/** The modes a guide's steps and files use, in the place's order (empty: no mode switch). */
|
|
77
|
+
export declare function setupGuideModes(guide: PluginSetupGuide): string[];
|
|
78
|
+
/**
|
|
79
|
+
* Renders a guide text: each `{{name}}` with a value in `values` is replaced by it, escaped for
|
|
80
|
+
* `language` (`yaml` / `json`: JSON-string-escaped without quotes, so a placeholder inside double
|
|
81
|
+
* quotes stays one string; `text`: control characters become spaces). A placeholder whose value is
|
|
82
|
+
* null or undefined stays as written and is listed in `unresolved` when `known` holds it; one not
|
|
83
|
+
* in `known` stays as written and is not listed. Never runs code; deterministic.
|
|
84
|
+
*/
|
|
85
|
+
export declare function renderSetupGuideText(text: string, values: Readonly<Record<string, string | null | undefined>>, language: SetupGuideLanguage, known?: readonly string[]): {
|
|
86
|
+
text: string;
|
|
87
|
+
unresolved: string[];
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Whether a guide's template file is usable: present, at most {@link SETUP_GUIDE_LIMITS}`.fileBytes`,
|
|
91
|
+
* valid UTF-8 text without NUL. The problem (English, naming the path), or undefined when it is.
|
|
92
|
+
* Shared by the packer and the kernel's package reader.
|
|
93
|
+
*/
|
|
94
|
+
export declare function setupGuideFileProblem(path: string, data: Uint8Array | undefined): string | undefined;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin setup guides (spec `2026-11-08-plugin-setup-guides-design.md`): setup material a plugin
|
|
3
|
+
* ships (steps and template files such as Slack's app manifest) that the kernel renders with a
|
|
4
|
+
* fixed set of safe, deterministic variables and the web shows where the person needs it. The
|
|
5
|
+
* format lives in `plugin.json` `setupGuides` ({@link PluginSetupGuide}); this module holds the
|
|
6
|
+
* places, their modes and variables, the limits and the pure renderer.
|
|
7
|
+
*
|
|
8
|
+
* Placeholders are `{{name}}` (spaces inside the braces allowed) with `name` matching
|
|
9
|
+
* `[A-Za-z][A-Za-z0-9]*`. There is no other syntax: no conditionals, loops, filters or escapes. A
|
|
10
|
+
* placeholder the place does not define, or one without a value, stays as written.
|
|
11
|
+
*/
|
|
12
|
+
/** Where a guide is shown. `channel-connection`: 新建渠道 and the channel detail page. */
|
|
13
|
+
export const SETUP_GUIDE_PLACES = ["channel-connection"];
|
|
14
|
+
/**
|
|
15
|
+
* The modes of each place: a step or file with `mode` applies to that mode only.
|
|
16
|
+
* `channel-connection`: the connection's `inboundMode`.
|
|
17
|
+
*/
|
|
18
|
+
export const SETUP_GUIDE_MODES = {
|
|
19
|
+
"channel-connection": ["long_connection", "webhook"],
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The variables of each place. None is a secret: the kernel fills them from public URLs, names and
|
|
23
|
+
* versions only (`publicUrl`, `webhookUrl`, `signInCallbackUrl`, `connectionName`, `orgName`,
|
|
24
|
+
* `pluginName`, `pluginVersion`, `mode`).
|
|
25
|
+
*/
|
|
26
|
+
export const SETUP_GUIDE_VARIABLES = {
|
|
27
|
+
"channel-connection": [
|
|
28
|
+
"publicUrl",
|
|
29
|
+
"webhookUrl",
|
|
30
|
+
"signInCallbackUrl",
|
|
31
|
+
"connectionName",
|
|
32
|
+
"orgName",
|
|
33
|
+
"pluginName",
|
|
34
|
+
"pluginVersion",
|
|
35
|
+
"mode",
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
/** Languages of a template file: the code block's label and how values are escaped. */
|
|
39
|
+
export const SETUP_GUIDE_LANGUAGES = ["yaml", "json", "text"];
|
|
40
|
+
/** Limits of setup guides. */
|
|
41
|
+
export const SETUP_GUIDE_LIMITS = {
|
|
42
|
+
/** Guides per plugin. */
|
|
43
|
+
guides: 8,
|
|
44
|
+
/** Steps per guide. */
|
|
45
|
+
steps: 20,
|
|
46
|
+
/** Files per guide. */
|
|
47
|
+
files: 8,
|
|
48
|
+
/** Characters of a step (each language). */
|
|
49
|
+
stepChars: 2000,
|
|
50
|
+
/** Characters of a title (each language). */
|
|
51
|
+
titleChars: 100,
|
|
52
|
+
/** Bytes of a template file. */
|
|
53
|
+
fileBytes: 64 * 1024,
|
|
54
|
+
};
|
|
55
|
+
/** File names a template file may have (checked on the path). */
|
|
56
|
+
export const SETUP_GUIDE_FILE_RE = /\.(?:tmpl|ya?ml|json|txt)$/i;
|
|
57
|
+
const PLACEHOLDER_RE = /\{\{\s*([A-Za-z][A-Za-z0-9]*)\s*\}\}/g;
|
|
58
|
+
/** The placeholder names a text uses, each once, in order of first use. */
|
|
59
|
+
export function setupGuidePlaceholders(text) {
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const m of text.matchAll(PLACEHOLDER_RE)) {
|
|
62
|
+
const name = m[1];
|
|
63
|
+
if (!out.includes(name))
|
|
64
|
+
out.push(name);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
/** The placeholders of a text that the place does not define (the toolchain refuses them). */
|
|
69
|
+
export function unknownSetupGuidePlaceholders(text, place) {
|
|
70
|
+
const known = SETUP_GUIDE_VARIABLES[place];
|
|
71
|
+
return setupGuidePlaceholders(text).filter((n) => !known.includes(n));
|
|
72
|
+
}
|
|
73
|
+
/** The modes a guide's steps and files use, in the place's order (empty: no mode switch). */
|
|
74
|
+
export function setupGuideModes(guide) {
|
|
75
|
+
const used = new Set();
|
|
76
|
+
for (const s of guide.steps ?? [])
|
|
77
|
+
if (s.mode !== undefined)
|
|
78
|
+
used.add(s.mode);
|
|
79
|
+
for (const f of guide.files ?? [])
|
|
80
|
+
if (f.mode !== undefined)
|
|
81
|
+
used.add(f.mode);
|
|
82
|
+
return SETUP_GUIDE_MODES[guide.place].filter((m) => used.has(m));
|
|
83
|
+
}
|
|
84
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: control characters are what is replaced
|
|
85
|
+
const CONTROL_RE = /[\u0000-\u001f\u007f]/g;
|
|
86
|
+
/** A value as inserted into a text of the language (see {@link renderSetupGuideText}). */
|
|
87
|
+
function escapeValue(value, language) {
|
|
88
|
+
if (language === "text")
|
|
89
|
+
return value.replace(CONTROL_RE, " ");
|
|
90
|
+
// JSON string escaping without the quotes: valid inside a JSON string and a YAML double-quoted
|
|
91
|
+
// scalar, whatever the value holds
|
|
92
|
+
return JSON.stringify(value).slice(1, -1);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Renders a guide text: each `{{name}}` with a value in `values` is replaced by it, escaped for
|
|
96
|
+
* `language` (`yaml` / `json`: JSON-string-escaped without quotes, so a placeholder inside double
|
|
97
|
+
* quotes stays one string; `text`: control characters become spaces). A placeholder whose value is
|
|
98
|
+
* null or undefined stays as written and is listed in `unresolved` when `known` holds it; one not
|
|
99
|
+
* in `known` stays as written and is not listed. Never runs code; deterministic.
|
|
100
|
+
*/
|
|
101
|
+
export function renderSetupGuideText(text, values, language, known = Object.keys(values)) {
|
|
102
|
+
const unresolved = [];
|
|
103
|
+
const out = text.replace(PLACEHOLDER_RE, (whole, name) => {
|
|
104
|
+
if (!known.includes(name))
|
|
105
|
+
return whole;
|
|
106
|
+
const v = Object.hasOwn(values, name) ? values[name] : undefined;
|
|
107
|
+
if (v === null || v === undefined) {
|
|
108
|
+
if (!unresolved.includes(name))
|
|
109
|
+
unresolved.push(name);
|
|
110
|
+
return whole;
|
|
111
|
+
}
|
|
112
|
+
return escapeValue(v, language);
|
|
113
|
+
});
|
|
114
|
+
return { text: out, unresolved };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Whether a guide's template file is usable: present, at most {@link SETUP_GUIDE_LIMITS}`.fileBytes`,
|
|
118
|
+
* valid UTF-8 text without NUL. The problem (English, naming the path), or undefined when it is.
|
|
119
|
+
* Shared by the packer and the kernel's package reader.
|
|
120
|
+
*/
|
|
121
|
+
export function setupGuideFileProblem(path, data) {
|
|
122
|
+
if (data === undefined)
|
|
123
|
+
return `${path} not found in the package`;
|
|
124
|
+
if (data.byteLength > SETUP_GUIDE_LIMITS.fileBytes) {
|
|
125
|
+
return `${path} is larger than ${SETUP_GUIDE_LIMITS.fileBytes} bytes`;
|
|
126
|
+
}
|
|
127
|
+
let text;
|
|
128
|
+
try {
|
|
129
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(data);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return `${path} is not valid UTF-8 text`;
|
|
133
|
+
}
|
|
134
|
+
return text.includes("\u0000") ? `${path} is not a text file (it contains NUL)` : undefined;
|
|
135
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type ParsedTheme } from "./theme.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parses a logo SVG (UTF-8, ≤ {@link THEME_LIMITS}`.svgBytes`) with a strict, namespace-aware
|
|
4
|
+
* XML parser and re-serializes it. The root must be an `svg` element in the SVG namespace with a
|
|
5
|
+
* numeric `viewBox`; its `width`, `height` and `class` are dropped. Rejected: a DOCTYPE (and so
|
|
6
|
+
* any entity declaration), processing instructions, a non-UTF-8 encoding, elements outside the
|
|
7
|
+
* SVG allowlist (`script`, `foreignObject`, `style`, …) or in the XHTML namespace, `style` and
|
|
8
|
+
* `on*` attributes, `href` / `xlink:href` not starting with `#`, `javascript:` / `vbscript:` in
|
|
9
|
+
* any value, quotes or a backslash in attribute values, any function other than `url(#id)`, colors
|
|
10
|
+
* (`rgb`, `rgba`, `hsl`, `hsla`) and transforms (`translate`, `rotate`, `scale`, `matrix`,
|
|
11
|
+
* `skewX`, `skewY`), a viewBox longer than 128 characters, and animation of `href`, `style` or event
|
|
12
|
+
* handlers. Comments, the XML declaration and elements / attributes in other namespaces (editor
|
|
13
|
+
* metadata) are dropped; CDATA becomes escaped text. Never throws.
|
|
14
|
+
*/
|
|
15
|
+
export declare function sanitizeThemeSvg(bytes: Uint8Array): {
|
|
16
|
+
svg: string;
|
|
17
|
+
} | {
|
|
18
|
+
error: string;
|
|
19
|
+
};
|
|
20
|
+
/** A woff2 font: the `wOF2` signature and at most {@link THEME_LIMITS}`.fontBytes`. */
|
|
21
|
+
export declare function checkWoff2(bytes: Uint8Array): string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* A PNG logo: the PNG signature, an `IHDR` first chunk with a height of at least
|
|
24
|
+
* {@link THEME_LIMITS}`.pngMinHeight` pixels, and at most {@link THEME_LIMITS}`.pngBytes`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function checkPng(bytes: Uint8Array): string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Resolves and checks the asset files of a parsed theme: each `fonts[].file` ({@link checkWoff2})
|
|
29
|
+
* and `logo.mark` / `logo.favicon` (`.svg` through {@link sanitizeThemeSvg}, `.png` through
|
|
30
|
+
* {@link checkPng}); the favicon must be square (SVG viewBox or PNG width = height). `files` holds the plugin's files keyed by their path without a leading
|
|
31
|
+
* `./`; the returned `assets` (the sanitized SVG bytes, the other files unchanged) use the same
|
|
32
|
+
* keys. Errors are `<field>: <reason>`, English; the total input size of the theme's distinct
|
|
33
|
+
* assets must stay within {@link THEME_LIMITS}`.assetsBytes`. That limit is per plugin, so a
|
|
34
|
+
* caller reading several themes also checks the sum over all of them (distinct paths).
|
|
35
|
+
*/
|
|
36
|
+
export declare function validateThemeAssets(theme: ParsedTheme, files: Map<string, Uint8Array>): {
|
|
37
|
+
assets: Map<string, Uint8Array>;
|
|
38
|
+
errors: string[];
|
|
39
|
+
};
|