@standardagents/code-plugin-sdk 1.0.0-alpha.7-theme.0 → 1.0.0-alpha.9-icons.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/README.md +23 -2
- package/package.json +1 -1
- package/src/index.d.ts +4 -2
- package/src/manifest.mjs +3 -0
- package/src/runtime.mjs +24 -18
- package/src/view.mjs +1 -1
package/README.md
CHANGED
|
@@ -11,8 +11,8 @@ Its `package.json` includes a static `standardPlugin` manifest.
|
|
|
11
11
|
{
|
|
12
12
|
"name": "example-status",
|
|
13
13
|
"type": "module",
|
|
14
|
-
"peerDependencies": { "@standardagents/code-plugin-sdk": "^1.0.0-alpha.
|
|
15
|
-
"devDependencies": { "@standardagents/code-plugin-sdk": "^1.0.0-alpha.
|
|
14
|
+
"peerDependencies": { "@standardagents/code-plugin-sdk": "^1.0.0-alpha.9-icons.0" },
|
|
15
|
+
"devDependencies": { "@standardagents/code-plugin-sdk": "^1.0.0-alpha.9-icons.0" },
|
|
16
16
|
"standardPlugin": {
|
|
17
17
|
"apiVersion": 1,
|
|
18
18
|
"id": "example-status",
|
|
@@ -195,6 +195,27 @@ The Standard Code build workflow publishes this package when the version in
|
|
|
195
195
|
under `latest`. Feature branches publish under their product branch tag. Authors
|
|
196
196
|
can install a published prerelease by its exact version.
|
|
197
197
|
|
|
198
|
+
## Sidebar card titles
|
|
199
|
+
|
|
200
|
+
Sidebar card titles use muted, regular-weight text. A card declaration may
|
|
201
|
+
supply an `icon` containing one Nerd Font private-use glyph, for example
|
|
202
|
+
`"icon": "\uDB81\uDCC5"` (a gauge). The author chooses the card's topic icon.
|
|
203
|
+
The host shows it only on the viewer's Nerd Font tier. Other tiers omit the
|
|
204
|
+
icon and its spacing. Omitted icons have no fallback. This applies to bordered
|
|
205
|
+
and borderless cards. Empty titles remain empty.
|
|
206
|
+
|
|
207
|
+
A card view may replace its declared title through
|
|
208
|
+
`ui.view(root, { title: [{ text: 'Builds ' }, { text: '●', foreground: 'green' }] })`.
|
|
209
|
+
The optional `title` uses native `TextSpan` fields, including `#RRGGBB` and the terminal colors `black`, `red`, `green`,
|
|
210
|
+
`yellow`, `blue`, `magenta`, `cyan`, and `white`;
|
|
211
|
+
`ui.span()` is a body-view builder with a different shape. Titles accept 1–32
|
|
212
|
+
passive spans and at most 512 UTF-8 bytes of combined, nonblank single-line text.
|
|
213
|
+
Actions, control characters, and bidi formatting are rejected. Each replacement
|
|
214
|
+
updates the body and title together. Omitting `title` restores the manifest title.
|
|
215
|
+
This option belongs to card views. Plugins own the meaning and color of any
|
|
216
|
+
status light; the SDK adds no health indicator. Host title styling and supported
|
|
217
|
+
author-selected Nerd Font icons apply around the author-supplied spans.
|
|
218
|
+
|
|
198
219
|
## License
|
|
199
220
|
|
|
200
221
|
MIT. See `LICENSE`.
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface ContributionDeclaration {
|
|
|
16
16
|
anchor: Anchor;
|
|
17
17
|
/** Sidebar card border. Defaults to true; false retains the title and content padding. */
|
|
18
18
|
border?: boolean;
|
|
19
|
+
/** One author-selected Nerd Font glyph; omitted on non-Nerd viewer tiers. */
|
|
20
|
+
icon?: string;
|
|
19
21
|
title?: string;
|
|
20
22
|
merge?: 'by-machine' | 'by-identity';
|
|
21
23
|
width?: 'full' | 'half';
|
|
@@ -157,7 +159,7 @@ export interface ButtonNode { type: 'button'; label: string; action: ViewAction
|
|
|
157
159
|
export type ViewNode = StackNode | RowNode | DividerNode | TextNode | BadgeNode | DotNode | ProgressNode |
|
|
158
160
|
SegmentsNode | CardNode | StatNode | KvNode | TabsNode | SelectNode | TableNode | LogNode | ButtonNode;
|
|
159
161
|
/** A host-rendered view tree. Cards, panels, and sections accept it. */
|
|
160
|
-
export interface ViewContent { kind: 'view'; root: ViewNode }
|
|
162
|
+
export interface ViewContent { kind: 'view'; root: ViewNode; /** Passive styled title for card views. */ title?: TextSpan[] }
|
|
161
163
|
|
|
162
164
|
/** Slots and overlays accept rows, text, or canvas. */
|
|
163
165
|
export type DrawnContent = RowsContent | TextContent | CanvasContent;
|
|
@@ -410,7 +412,7 @@ export function validateView(root: unknown, options?: { kind?: SurfaceKind; mani
|
|
|
410
412
|
type Options<T> = Omit<T, 'type' | 'children'>;
|
|
411
413
|
/** Optional builders. Each returns the plain protocol JSON for one node; hand-written JSON is equivalent. */
|
|
412
414
|
export const ui: {
|
|
413
|
-
view(root: ViewNode): ViewContent;
|
|
415
|
+
view(root: ViewNode, options?: { title?: TextSpan[] }): ViewContent;
|
|
414
416
|
action(actionId: string, options?: { value?: string; opens?: string }): ViewAction;
|
|
415
417
|
span(text: string, options?: Omit<ViewSpan, 'text'>): ViewSpan;
|
|
416
418
|
stack(children: ViewNode[], options?: Options<StackNode>): StackNode;
|
package/src/manifest.mjs
CHANGED
|
@@ -76,6 +76,9 @@ export function validateManifest(value) {
|
|
|
76
76
|
ensure(object(declaration) && identifier(declaration.id) && !ids.has(declaration.id) &&
|
|
77
77
|
SURFACE_KINDS.includes(declaration.kind) && ANCHORS.includes(declaration.anchor), 'invalid_manifest', 'Invalid contribution declaration')
|
|
78
78
|
ids.add(declaration.id)
|
|
79
|
+
ensure(declaration.icon === undefined || (declaration.kind === 'card' && typeof declaration.icon === 'string' &&
|
|
80
|
+
/^[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]$/u.test(declaration.icon)),
|
|
81
|
+
'invalid_manifest', 'Card icon must be one Nerd Font private-use glyph')
|
|
79
82
|
ensure(declaration.border === undefined || (typeof declaration.border === 'boolean' && declaration.kind === 'card'),
|
|
80
83
|
'invalid_manifest', 'Only sidebar cards declare a boolean border preference')
|
|
81
84
|
for (const [key, choices] of Object.entries({ merge: ['by-machine', 'by-identity'], width: ['full', 'half'],
|
package/src/runtime.mjs
CHANGED
|
@@ -11,41 +11,41 @@ function boundedText(value, label, limit = 512) {
|
|
|
11
11
|
return value
|
|
12
12
|
}
|
|
13
13
|
const HOVER_FORBIDDEN = /[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/
|
|
14
|
-
function
|
|
14
|
+
function validatePassiveText(value, limit = LIMITS.canvasHoverBytes, label = 'canvas hover') {
|
|
15
15
|
if (typeof value === 'string') {
|
|
16
16
|
ensure(value.trim().length > 0 && !HOVER_FORBIDDEN.test(value),
|
|
17
|
-
'invalid_payload',
|
|
18
|
-
ensure(Buffer.byteLength(value) <=
|
|
19
|
-
'payload_too_large',
|
|
17
|
+
'invalid_payload', `Invalid ${label} text`)
|
|
18
|
+
ensure(Buffer.byteLength(value) <= limit,
|
|
19
|
+
'payload_too_large', `${label} text exceeds ${limit} bytes`)
|
|
20
20
|
return
|
|
21
21
|
}
|
|
22
|
-
ensure(Array.isArray(value), 'invalid_payload',
|
|
23
|
-
ensure(value.length > 0, 'invalid_payload',
|
|
24
|
-
ensure(value.length <= 32, 'payload_too_large',
|
|
22
|
+
ensure(Array.isArray(value), 'invalid_payload', `Invalid ${label} spans`)
|
|
23
|
+
ensure(value.length > 0, 'invalid_payload', `${label} spans must contain text`)
|
|
24
|
+
ensure(value.length <= 32, 'payload_too_large', `${label} spans exceed 32 spans`)
|
|
25
25
|
let text = ''
|
|
26
26
|
for (const span of value) {
|
|
27
|
-
ensure(object(span), 'invalid_payload',
|
|
27
|
+
ensure(object(span), 'invalid_payload', `Invalid ${label} span`)
|
|
28
28
|
ensure(typeof span.text === 'string' && !HOVER_FORBIDDEN.test(span.text),
|
|
29
|
-
'invalid_payload',
|
|
29
|
+
'invalid_payload', `Invalid ${label} span text`)
|
|
30
30
|
for (const field of ['foreground', 'background']) {
|
|
31
31
|
if (span[field] !== undefined) {
|
|
32
32
|
ensure(typeof span[field] === 'string' && span[field].trim().length > 0 &&
|
|
33
|
-
!HOVER_FORBIDDEN.test(span[field]), 'invalid_payload', `Invalid
|
|
33
|
+
!HOVER_FORBIDDEN.test(span[field]), 'invalid_payload', `Invalid ${label} span ${field}`)
|
|
34
34
|
ensure(Buffer.byteLength(span[field]) <= 64,
|
|
35
|
-
'payload_too_large',
|
|
35
|
+
'payload_too_large', `${label} span ${field} exceeds 64 bytes`)
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
for (const field of ['bold', 'italic', 'underline']) {
|
|
39
39
|
ensure(span[field] === undefined || typeof span[field] === 'boolean',
|
|
40
|
-
'invalid_payload', `Invalid
|
|
40
|
+
'invalid_payload', `Invalid ${label} span ${field}`)
|
|
41
41
|
}
|
|
42
|
-
ensure(span.actionId === undefined, 'invalid_payload',
|
|
42
|
+
ensure(span.actionId === undefined, 'invalid_payload', `${label} spans cannot contain actionId`)
|
|
43
43
|
text += span.text
|
|
44
44
|
}
|
|
45
45
|
ensure(text.trim().length > 0 && !HOVER_FORBIDDEN.test(text),
|
|
46
|
-
'invalid_payload',
|
|
47
|
-
ensure(Buffer.byteLength(text) <=
|
|
48
|
-
'payload_too_large',
|
|
46
|
+
'invalid_payload', `Invalid ${label} text`)
|
|
47
|
+
ensure(Buffer.byteLength(text) <= limit,
|
|
48
|
+
'payload_too_large', `${label} text exceeds ${limit} bytes`)
|
|
49
49
|
}
|
|
50
50
|
function validateEntity(entity) {
|
|
51
51
|
ensure(object(entity) && entityKinds.includes(entity.kind), 'invalid_payload', 'Invalid registration entity')
|
|
@@ -91,7 +91,13 @@ function validateContent(content, declaration, manifest) {
|
|
|
91
91
|
ensure(object(content) && CONTENT_KINDS.includes(content.kind), 'invalid_payload', 'Invalid surface content')
|
|
92
92
|
ensure(acceptsContent(kind, content), 'invalid_payload', `A ${kind} contribution cannot show ${content.kind} content`)
|
|
93
93
|
// The view walk bounds depth before serialization visits the tree.
|
|
94
|
-
if (content.kind === 'view')
|
|
94
|
+
if (content.kind === 'view') {
|
|
95
|
+
validateView(content.root, { kind: declaration.kind, manifest })
|
|
96
|
+
if (content.title !== undefined) {
|
|
97
|
+
ensure(kind === 'card' && Array.isArray(content.title), 'invalid_payload', 'Styled titles belong to card views')
|
|
98
|
+
validatePassiveText(content.title, 512, 'card title')
|
|
99
|
+
}
|
|
100
|
+
}
|
|
95
101
|
jsonBytes(content)
|
|
96
102
|
if (content.kind === 'canvas') {
|
|
97
103
|
const { columns, rows, shade = 0, hover, themeColors } = content.canvas ?? {}
|
|
@@ -108,7 +114,7 @@ function validateContent(content, declaration, manifest) {
|
|
|
108
114
|
'invalid_payload', 'Invalid canvas theme color recipe')
|
|
109
115
|
}
|
|
110
116
|
}
|
|
111
|
-
if (hover !== undefined)
|
|
117
|
+
if (hover !== undefined) validatePassiveText(hover)
|
|
112
118
|
}
|
|
113
119
|
}
|
|
114
120
|
|
package/src/view.mjs
CHANGED
|
@@ -231,7 +231,7 @@ const node = (type, fields) => compact({ type, ...fields })
|
|
|
231
231
|
|
|
232
232
|
/** Optional builders. Each returns the plain protocol JSON for one node. */
|
|
233
233
|
export const ui = Object.freeze({
|
|
234
|
-
view: root => ({ kind: 'view', root }),
|
|
234
|
+
view: (root, { title } = {}) => compact({ kind: 'view', root, title }),
|
|
235
235
|
action: (actionId, { value, opens } = {}) => compact({ actionId, value, opens }),
|
|
236
236
|
span: (value, { tone, weight, mono } = {}) => compact({ text: value, tone, weight, mono }),
|
|
237
237
|
stack: (items, { gap } = {}) => node('stack', { gap, children: items }),
|