@standardagents/code-plugin-sdk 1.0.0-alpha.6-surfaces.0 → 1.0.0-alpha.8-title.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 +19 -2
- package/REFERENCE.md +26 -1
- package/package.json +1 -1
- package/src/index.d.ts +15 -3
- package/src/manifest.mjs +2 -0
- package/src/runtime.mjs +35 -19
- 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.7-theme.0" },
|
|
15
|
+
"devDependencies": { "@standardagents/code-plugin-sdk": "^1.0.0-alpha.7-theme.0" },
|
|
16
16
|
"standardPlugin": {
|
|
17
17
|
"apiVersion": 1,
|
|
18
18
|
"id": "example-status",
|
|
@@ -198,3 +198,20 @@ can install a published prerelease by its exact version.
|
|
|
198
198
|
## License
|
|
199
199
|
|
|
200
200
|
MIT. See `LICENSE`.
|
|
201
|
+
|
|
202
|
+
Sidebar card titles use muted, regular-weight text. The host prefixes a plugin
|
|
203
|
+
identity icon only on the viewer's Nerd Font symbol tier. Other tiers render
|
|
204
|
+
the title alone. This applies to bordered and borderless cards. Empty titles
|
|
205
|
+
remain empty; no author-supplied icon or font detection is required.
|
|
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
|
+
Nerd Font identity icons apply around the author-supplied spans.
|
package/REFERENCE.md
CHANGED
|
@@ -31,7 +31,14 @@ Contribution declarations contain a stable ID, surface kind, anchor, title,
|
|
|
31
31
|
merge mode, width, menu position, palette group, chord, URL pattern, and
|
|
32
32
|
action ID as appropriate.
|
|
33
33
|
|
|
34
|
-
A `card` declaration draws a
|
|
34
|
+
A `card` declaration draws a sidebar card. Its optional `border` boolean
|
|
35
|
+
defaults to `true`. Set `border: false` for a borderless group: the host
|
|
36
|
+
retains the author title, content padding, focus indication, and activation
|
|
37
|
+
behavior, and removes the outline and bottom border row. This preference
|
|
38
|
+
is declared by each plugin on its own cards; other surface kinds reject it.
|
|
39
|
+
The nested view node named `card` is a separate content element.
|
|
40
|
+
|
|
41
|
+
`validateManifest`
|
|
35
42
|
refuses a card whose anchor is not `plugins`. Two optional fields control
|
|
36
43
|
panels:
|
|
37
44
|
|
|
@@ -509,3 +516,21 @@ It records traces, surface replacement, subscriptions, and resources without
|
|
|
509
516
|
starting subprocesses. `harness.surface(id, entity?)` returns the current
|
|
510
517
|
content of a published contribution, including view and canvas content, or
|
|
511
518
|
`undefined` after the contribution clears.
|
|
519
|
+
|
|
520
|
+
### Canvas theme colors
|
|
521
|
+
|
|
522
|
+
`CanvasSpec.themeColors` maps up to 32 canvas-local ANSI indexes (0–255) to
|
|
523
|
+
recipes. Each recipe has `source` (optional ANSI index 0–15, omitted for the
|
|
524
|
+
default foreground), `mix` (optional second ANSI index mixed equally), and
|
|
525
|
+
`opacity` (0–1, blended against the viewer background). Both foreground and
|
|
526
|
+
background SGR references to that index resolve through the same recipe.
|
|
527
|
+
For example, `{ 16: { source: 4, opacity: 0.72 }, 20: { opacity: 0.4 } }`
|
|
528
|
+
provides muted terminal blue at index 16 and a subtle foreground at index 20.
|
|
529
|
+
|
|
530
|
+
Resolution occurs in the viewer for sidebar, pane, slot, column, and popover
|
|
531
|
+
canvases. Remote producers do not read or bake another machine's colors.
|
|
532
|
+
Recipes remain unchanged across terminal theme updates. Unavailable source
|
|
533
|
+
colors fall back to the named ANSI index or default foreground; unavailable
|
|
534
|
+
background colors leave opacity unapplied. OSC queries remain unsupported
|
|
535
|
+
in plugin canvas streams. This capability is public and identical for all
|
|
536
|
+
plugins.
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type Json = null | boolean | number | string | Json[] | { [key: string]: Json };
|
|
2
2
|
export type Capability = 'surfaces' | 'events' | 'hooks' | 'panes' | 'projects' |
|
|
3
3
|
'notifications' | 'url' | 'fetch' | 'secrets' | 'webhook';
|
|
4
|
-
/** `card` draws a
|
|
4
|
+
/** `card` draws a sidebar card under the `plugins` anchor, with an optional border. */
|
|
5
5
|
export type SurfaceKind = 'section' | 'card' | 'slot' | 'badge' | 'panel' | 'overlay' |
|
|
6
6
|
'menu' | 'command' | 'key' | 'link';
|
|
7
7
|
/** How a panel opens. Absent means `popover`. */
|
|
@@ -14,6 +14,8 @@ export interface ContributionDeclaration {
|
|
|
14
14
|
id: string;
|
|
15
15
|
kind: SurfaceKind;
|
|
16
16
|
anchor: Anchor;
|
|
17
|
+
/** Sidebar card border. Defaults to true; false retains the title and content padding. */
|
|
18
|
+
border?: boolean;
|
|
17
19
|
title?: string;
|
|
18
20
|
merge?: 'by-machine' | 'by-identity';
|
|
19
21
|
width?: 'full' | 'half';
|
|
@@ -84,7 +86,17 @@ export type TextContent = { kind: 'text'; lines: TextSpan[][] };
|
|
|
84
86
|
export type BadgeContent = { kind: 'badge'; spans: TextSpan[]; actionId?: string };
|
|
85
87
|
export type NativeContent = RowsContent | TextContent | BadgeContent;
|
|
86
88
|
/** `columns` is also the intrinsic width of a card, column, or plugin pane that shows the canvas. */
|
|
89
|
+
export interface CanvasThemeColor {
|
|
90
|
+
/** ANSI palette index 0..15; omitted uses the viewer's default foreground. */
|
|
91
|
+
source?: number;
|
|
92
|
+
/** Optional second ANSI palette index, mixed equally with source. */
|
|
93
|
+
mix?: number;
|
|
94
|
+
/** Blend against the viewer background, from 0 to 1. */
|
|
95
|
+
opacity: number;
|
|
96
|
+
}
|
|
87
97
|
export interface CanvasSpec {
|
|
98
|
+
/** Up to 32 canvas-local indexed color recipes. Keys are ANSI indexes 0..255. */
|
|
99
|
+
themeColors?: Record<number, CanvasThemeColor>;
|
|
88
100
|
columns: number;
|
|
89
101
|
rows: number;
|
|
90
102
|
transparent?: boolean;
|
|
@@ -145,7 +157,7 @@ export interface ButtonNode { type: 'button'; label: string; action: ViewAction
|
|
|
145
157
|
export type ViewNode = StackNode | RowNode | DividerNode | TextNode | BadgeNode | DotNode | ProgressNode |
|
|
146
158
|
SegmentsNode | CardNode | StatNode | KvNode | TabsNode | SelectNode | TableNode | LogNode | ButtonNode;
|
|
147
159
|
/** A host-rendered view tree. Cards, panels, and sections accept it. */
|
|
148
|
-
export interface ViewContent { kind: 'view'; root: ViewNode }
|
|
160
|
+
export interface ViewContent { kind: 'view'; root: ViewNode; /** Passive styled title for card views. */ title?: TextSpan[] }
|
|
149
161
|
|
|
150
162
|
/** Slots and overlays accept rows, text, or canvas. */
|
|
151
163
|
export type DrawnContent = RowsContent | TextContent | CanvasContent;
|
|
@@ -398,7 +410,7 @@ export function validateView(root: unknown, options?: { kind?: SurfaceKind; mani
|
|
|
398
410
|
type Options<T> = Omit<T, 'type' | 'children'>;
|
|
399
411
|
/** Optional builders. Each returns the plain protocol JSON for one node; hand-written JSON is equivalent. */
|
|
400
412
|
export const ui: {
|
|
401
|
-
view(root: ViewNode): ViewContent;
|
|
413
|
+
view(root: ViewNode, options?: { title?: TextSpan[] }): ViewContent;
|
|
402
414
|
action(actionId: string, options?: { value?: string; opens?: string }): ViewAction;
|
|
403
415
|
span(text: string, options?: Omit<ViewSpan, 'text'>): ViewSpan;
|
|
404
416
|
stack(children: ViewNode[], options?: Options<StackNode>): StackNode;
|
package/src/manifest.mjs
CHANGED
|
@@ -76,6 +76,8 @@ 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.border === undefined || (typeof declaration.border === 'boolean' && declaration.kind === 'card'),
|
|
80
|
+
'invalid_manifest', 'Only sidebar cards declare a boolean border preference')
|
|
79
81
|
for (const [key, choices] of Object.entries({ merge: ['by-machine', 'by-identity'], width: ['full', 'half'],
|
|
80
82
|
position: ['top', 'after-open', 'before-danger', 'bottom'], presentation: PRESENTATIONS })) {
|
|
81
83
|
ensure(declaration[key] === undefined || choices.includes(declaration[key]), 'invalid_manifest', `Invalid contribution ${key}`)
|
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,14 +91,30 @@ 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
|
-
const { columns, rows, shade = 0, hover } = content.canvas ?? {}
|
|
103
|
+
const { columns, rows, shade = 0, hover, themeColors } = content.canvas ?? {}
|
|
98
104
|
ensure(Number.isSafeInteger(columns) && columns > 0 && columns <= LIMITS.canvasColumns &&
|
|
99
105
|
Number.isSafeInteger(rows) && rows > 0 && rows <= LIMITS.canvasRows &&
|
|
100
106
|
Number.isFinite(shade) && shade >= 0 && shade <= 1, 'invalid_payload', 'Invalid canvas dimensions or shade')
|
|
101
|
-
if (
|
|
107
|
+
if (themeColors !== undefined) {
|
|
108
|
+
ensure(object(themeColors) && Object.keys(themeColors).length <= 32, 'invalid_payload', 'Invalid canvas theme colors')
|
|
109
|
+
for (const [index, recipe] of Object.entries(themeColors)) {
|
|
110
|
+
ensure(/^(0|[1-9][0-9]{0,2})$/.test(index) && Number(index) <= 255 && object(recipe) &&
|
|
111
|
+
Object.keys(recipe).every(key=>['source','mix','opacity'].includes(key)) &&
|
|
112
|
+
['source','mix'].every(key=>recipe[key] === undefined || (Number.isInteger(recipe[key]) && recipe[key]>=0 && recipe[key]<16)) &&
|
|
113
|
+
Number.isFinite(recipe.opacity) && recipe.opacity>=0 && recipe.opacity<=1,
|
|
114
|
+
'invalid_payload', 'Invalid canvas theme color recipe')
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (hover !== undefined) validatePassiveText(hover)
|
|
102
118
|
}
|
|
103
119
|
}
|
|
104
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 }),
|