@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 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.6-surfaces.0" },
15
- "devDependencies": { "@standardagents/code-plugin-sdk": "^1.0.0-alpha.6-surfaces.0" },
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 boxed card in the sidebar. `validateManifest`
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@standardagents/code-plugin-sdk",
3
- "version": "1.0.0-alpha.6-surfaces.0",
3
+ "version": "1.0.0-alpha.8-title.0",
4
4
  "type": "module",
5
5
  "description": "Standard Code plugin authoring SDK",
6
6
  "license": "MIT",
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 boxed card in the sidebar under the `plugins` anchor. */
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 validateCanvasHover(value) {
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', 'Invalid canvas hover text')
18
- ensure(Buffer.byteLength(value) <= LIMITS.canvasHoverBytes,
19
- 'payload_too_large', `Canvas hover text exceeds ${LIMITS.canvasHoverBytes} bytes`)
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', 'Invalid canvas hover spans')
23
- ensure(value.length > 0, 'invalid_payload', 'Canvas hover spans must contain text')
24
- ensure(value.length <= 32, 'payload_too_large', 'Canvas hover spans exceed 32 spans')
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', 'Invalid canvas hover span')
27
+ ensure(object(span), 'invalid_payload', `Invalid ${label} span`)
28
28
  ensure(typeof span.text === 'string' && !HOVER_FORBIDDEN.test(span.text),
29
- 'invalid_payload', 'Invalid canvas hover span text')
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 canvas hover span ${field}`)
33
+ !HOVER_FORBIDDEN.test(span[field]), 'invalid_payload', `Invalid ${label} span ${field}`)
34
34
  ensure(Buffer.byteLength(span[field]) <= 64,
35
- 'payload_too_large', `Canvas hover span ${field} exceeds 64 bytes`)
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 canvas hover span ${field}`)
40
+ 'invalid_payload', `Invalid ${label} span ${field}`)
41
41
  }
42
- ensure(span.actionId === undefined, 'invalid_payload', 'Canvas hover spans cannot contain actionId')
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', 'Invalid canvas hover text')
47
- ensure(Buffer.byteLength(text) <= LIMITS.canvasHoverBytes,
48
- 'payload_too_large', `Canvas hover text exceeds ${LIMITS.canvasHoverBytes} bytes`)
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') validateView(content.root, { kind: declaration.kind, manifest })
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 (hover !== undefined) validateCanvasHover(hover)
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 }),