@openvcs/sdk 0.2.11 → 0.2.13

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.
@@ -0,0 +1,129 @@
1
+ /** Describes horizontal alignment hints for modal content. */
2
+ export type ModalContentAlign = 'left' | 'centered' | 'right';
3
+ /** Describes visual emphasis for modal buttons. */
4
+ export type ModalButtonVariant = 'default' | 'primary' | 'danger';
5
+ /** Describes the text input kinds supported by plugin modals. */
6
+ export type ModalInputKind = 'text' | 'search' | 'password' | 'url' | 'number';
7
+ /** Describes one button option rendered inside a modal. */
8
+ export interface ModalButtonDefinition {
9
+ /** Stores the action id triggered when the button is pressed. */
10
+ id: string;
11
+ /** Stores the button label. */
12
+ content: string;
13
+ /** Stores the optional tooltip text. */
14
+ title?: string;
15
+ /** Stores the visual button variant. */
16
+ variant?: ModalButtonVariant;
17
+ /** Stores the button alignment hint. */
18
+ align?: ModalContentAlign;
19
+ /** Stores a static payload merged into the action payload. */
20
+ payload?: Record<string, unknown>;
21
+ }
22
+ /** Describes one text block rendered inside a modal. */
23
+ export interface ModalTextDefinition {
24
+ /** Always stores `text`. */
25
+ type: 'text';
26
+ /** Stores the block text. */
27
+ content: string;
28
+ /** Stores an optional tooltip. */
29
+ title?: string;
30
+ /** Stores the alignment hint. */
31
+ align?: ModalContentAlign;
32
+ }
33
+ /** Describes one separator rendered inside a modal. */
34
+ export interface ModalSeparatorDefinition {
35
+ /** Always stores `separator`. */
36
+ type: 'separator';
37
+ }
38
+ /** Describes one button rendered inside a modal body. */
39
+ export interface ModalButtonItemDefinition extends ModalButtonDefinition {
40
+ /** Always stores `button`. */
41
+ type: 'button';
42
+ }
43
+ /** Describes one text input rendered inside a modal. */
44
+ export interface ModalInputDefinition {
45
+ /** Always stores `input`. */
46
+ type: 'input';
47
+ /** Stores the input field id. */
48
+ id: string;
49
+ /** Stores the label text. */
50
+ label: string;
51
+ /** Stores the input kind. */
52
+ kind?: ModalInputKind;
53
+ /** Stores the default value. */
54
+ value?: string;
55
+ /** Stores the placeholder text. */
56
+ placeholder?: string;
57
+ /** Stores whether the field is required. */
58
+ required?: boolean;
59
+ /** Stores the alignment hint. */
60
+ align?: ModalContentAlign;
61
+ }
62
+ /** Describes one select option rendered inside a modal. */
63
+ export interface ModalSelectOptionDefinition {
64
+ /** Stores the option label. */
65
+ label: string;
66
+ /** Stores the option value. */
67
+ value: string;
68
+ /** Stores whether the option is selected by default. */
69
+ selected?: boolean;
70
+ }
71
+ /** Describes one select field rendered inside a modal. */
72
+ export interface ModalSelectDefinition {
73
+ /** Always stores `select`. */
74
+ type: 'select';
75
+ /** Stores the field id. */
76
+ id: string;
77
+ /** Stores the label text. */
78
+ label: string;
79
+ /** Stores the available options. */
80
+ options: ModalSelectOptionDefinition[];
81
+ /** Stores the default value. */
82
+ value?: string;
83
+ /** Stores the alignment hint. */
84
+ align?: ModalContentAlign;
85
+ }
86
+ /** Describes one row-level action rendered in a list item. */
87
+ export interface ModalListActionDefinition extends ModalButtonDefinition {
88
+ /** Always stores `button`. */
89
+ type: 'button';
90
+ }
91
+ /** Describes one list row rendered inside a modal list. */
92
+ export interface ModalListRowDefinition {
93
+ /** Stores the row id. */
94
+ id: string;
95
+ /** Stores the row title. */
96
+ title: string;
97
+ /** Stores a short status label. */
98
+ status?: string;
99
+ /** Stores a secondary metadata label. */
100
+ meta?: string;
101
+ /** Stores longer descriptive text. */
102
+ description?: string;
103
+ /** Stores the row actions. */
104
+ actions?: ModalListActionDefinition[];
105
+ }
106
+ /** Describes one list block rendered inside a modal. */
107
+ export interface ModalListDefinition {
108
+ /** Always stores `list`. */
109
+ type: 'list';
110
+ /** Stores the list id. */
111
+ id: string;
112
+ /** Stores the list label. */
113
+ label?: string;
114
+ /** Stores the empty-state text. */
115
+ emptyText?: string;
116
+ /** Stores the alignment hint. */
117
+ align?: ModalContentAlign;
118
+ /** Stores the list rows. */
119
+ items: ModalListRowDefinition[];
120
+ }
121
+ /** Describes one plugin modal content item. */
122
+ export type PluginModalContentItem = ModalTextDefinition | ModalSeparatorDefinition | ModalButtonItemDefinition | ModalInputDefinition | ModalSelectDefinition | ModalListDefinition;
123
+ /** Describes the structured payload used to render a plugin modal. */
124
+ export interface PluginModalDefinition {
125
+ /** Stores the modal title. */
126
+ title: string;
127
+ /** Stores the ordered list of modal content items. */
128
+ content: PluginModalContentItem[];
129
+ }
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ // Copyright © 2025-2026 OpenVCS Contributors
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -23,6 +23,8 @@ export type PluginSettingsValue = Record<string, unknown>;
23
23
  export interface PluginHandleActionParams extends RequestParams {
24
24
  /** Stores the action id selected by the user. */
25
25
  action_id?: string;
26
+ /** Stores an optional payload supplied by the triggering UI. */
27
+ payload?: Record<string, unknown>;
26
28
  }
27
29
  /** Describes the params shape for plugin settings callbacks. */
28
30
  export interface PluginSettingsValuesParams extends RequestParams {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openvcs/sdk",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "OpenVCS SDK CLI for plugin scaffolding and .ovcsp tar.gz packaging",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "homepage": "https://openvcs.app/",
@@ -22,6 +22,7 @@ export { createDefaultPluginDelegates, createRuntimeDispatcher } from './dispatc
22
22
  export { isPluginFailure, pluginError } from './errors';
23
23
  export { createPluginRuntime } from './factory';
24
24
  export { createHost } from './host';
25
+ export { ModalBuilder } from './modal';
25
26
  export {
26
27
  bootstrapPluginModule,
27
28
  createRegisteredPluginRuntime,
@@ -11,22 +11,10 @@ import type {
11
11
  import type { PluginRuntimeContext } from './contracts.js';
12
12
 
13
13
  type MenubarMenuOptions = { before?: string; after?: string };
14
-
15
14
  type MenuEntryKind = 'button' | 'text' | 'separator';
16
15
 
17
16
  type OpenVCSGlobal = typeof globalThis & {
18
17
  OpenVCS?: {
19
- menus?: {
20
- get(menuId: string): unknown;
21
- getOrCreate(menuId: string, label: string): unknown;
22
- create(menuId: string, label: string, options?: MenubarMenuOptions): unknown;
23
- addMenuItem(menuId: string, item: MenubarItem): void;
24
- addMenuSeparator(menuId: string, beforeAction?: string): void;
25
- remove(menuId: string): void;
26
- hide(menuId: string): void;
27
- show(menuId: string): void;
28
- };
29
- registerAction(id: string, handler: (...args: unknown[]) => unknown): void;
30
18
  invoke<T = unknown>(cmd: string, args?: unknown): Promise<T>;
31
19
  notify(msg: string): void;
32
20
  };
@@ -66,36 +54,40 @@ interface SerializedMenuDefinition {
66
54
  const menus = new Map<string, StoredMenuState>();
67
55
  const menuOrder: string[] = [];
68
56
  const actionHandlers = new Map<string, (...args: unknown[]) => unknown>();
69
- let nextSyntheticId = 0;
57
+ let syntheticId = 0;
70
58
 
71
- /** Returns the OpenVCS global, when the host exposes one. */
59
+ /** Returns the host-side OpenVCS helper, when the environment provides one. */
72
60
  function getOpenVCS() {
73
61
  return (globalThis as OpenVCSGlobal).OpenVCS;
74
62
  }
75
63
 
76
- /** Normalizes menu ids for stable map lookups. */
64
+ /** Normalizes a menu id for stable map lookup. */
77
65
  function normalizeMenuId(menuId: string): string {
78
66
  return String(menuId || '').trim();
79
67
  }
80
68
 
81
- /** Allocates a stable synthetic id for a generated menu item. */
69
+ /** Allocates a stable synthetic id for generated menu entries. */
82
70
  function allocateSyntheticId(prefix: string): string {
83
- nextSyntheticId += 1;
84
- return `${prefix}-${nextSyntheticId}`;
71
+ syntheticId += 1;
72
+ return `${prefix}-${syntheticId}`;
85
73
  }
86
74
 
87
- /** Returns a stored menu state or null if it does not exist. */
75
+ /** Returns the stored menu state for one id, if present. */
88
76
  function getStoredMenu(menuId: string): StoredMenuState | null {
89
77
  return menus.get(normalizeMenuId(menuId)) || null;
90
78
  }
91
79
 
92
- /** Inserts a menu id into the ordering list. */
80
+ /** Removes one menu id from the ordering list. */
81
+ function removeMenuId(menuId: string): void {
82
+ const id = normalizeMenuId(menuId);
83
+ const index = menuOrder.indexOf(id);
84
+ if (index >= 0) menuOrder.splice(index, 1);
85
+ }
86
+
87
+ /** Inserts one menu id into the ordering list. */
93
88
  function placeMenuId(menuId: string, options?: MenubarMenuOptions): void {
94
89
  const id = normalizeMenuId(menuId);
95
- const existingIndex = menuOrder.indexOf(id);
96
- if (existingIndex >= 0) {
97
- menuOrder.splice(existingIndex, 1);
98
- }
90
+ removeMenuId(id);
99
91
 
100
92
  const beforeId = normalizeMenuId(options?.before || '');
101
93
  const afterId = normalizeMenuId(options?.after || '');
@@ -119,8 +111,12 @@ function placeMenuId(menuId: string, options?: MenubarMenuOptions): void {
119
111
  menuOrder.push(id);
120
112
  }
121
113
 
122
- /** Ensures a menu record exists for the provided id. */
123
- function ensureStoredMenu(menuId: string, label: string, options?: MenubarMenuOptions): StoredMenuState {
114
+ /** Ensures a menu record exists for one id. */
115
+ function ensureStoredMenu(
116
+ menuId: string,
117
+ label: string,
118
+ options?: MenubarMenuOptions,
119
+ ): StoredMenuState {
124
120
  const id = normalizeMenuId(menuId);
125
121
  const safeLabel = String(label || '').trim() || id;
126
122
  let menu = menus.get(id);
@@ -128,38 +124,35 @@ function ensureStoredMenu(menuId: string, label: string, options?: MenubarMenuOp
128
124
  if (!menu) {
129
125
  menu = { id, label: safeLabel, items: [] };
130
126
  menus.set(id, menu);
131
- placeMenuId(id, options);
132
- return menu;
127
+ } else {
128
+ menu.label = safeLabel;
133
129
  }
134
130
 
135
- menu.label = safeLabel;
136
131
  placeMenuId(id, options);
137
132
  return menu;
138
133
  }
139
134
 
140
- /** Finds one menu item by action id. */
141
- function findMenuItem(menu: StoredMenuState, actionId: string): StoredMenuItem | null {
135
+ /** Finds a stored item by action id. */
136
+ function findStoredItem(menu: StoredMenuState, actionId: string): StoredMenuItem | null {
142
137
  const id = normalizeMenuId(actionId);
143
138
  return menu.items.find((item) => item.action === id) || null;
144
139
  }
145
140
 
146
- /** Inserts a menu item relative to before/after anchors when provided. */
141
+ /** Inserts a menu item at the requested position. */
147
142
  function insertMenuItem(menu: StoredMenuState, item: StoredMenuItem, before?: string, after?: string): void {
148
143
  const beforeId = normalizeMenuId(before || '');
149
144
  const afterId = normalizeMenuId(after || '');
150
145
 
151
- const insertAt = (index: number) => {
152
- const existingIndex = menu.items.findIndex((entry) => entry.id === item.id || entry.action === item.action);
153
- if (existingIndex >= 0) {
154
- menu.items.splice(existingIndex, 1);
155
- }
156
- menu.items.splice(index, 0, item);
146
+ const removeExisting = () => {
147
+ const index = menu.items.findIndex((entry) => entry.id === item.id || entry.action === item.action);
148
+ if (index >= 0) menu.items.splice(index, 1);
157
149
  };
158
150
 
159
151
  if (beforeId) {
160
152
  const beforeIndex = menu.items.findIndex((entry) => entry.action === beforeId);
161
153
  if (beforeIndex >= 0) {
162
- insertAt(beforeIndex);
154
+ removeExisting();
155
+ menu.items.splice(beforeIndex, 0, item);
163
156
  return;
164
157
  }
165
158
  }
@@ -167,15 +160,17 @@ function insertMenuItem(menu: StoredMenuState, item: StoredMenuItem, before?: st
167
160
  if (afterId) {
168
161
  const afterIndex = menu.items.findIndex((entry) => entry.action === afterId);
169
162
  if (afterIndex >= 0) {
170
- insertAt(afterIndex + 1);
163
+ removeExisting();
164
+ menu.items.splice(afterIndex + 1, 0, item);
171
165
  return;
172
166
  }
173
167
  }
174
168
 
175
- insertAt(menu.items.length);
169
+ removeExisting();
170
+ menu.items.push(item);
176
171
  }
177
172
 
178
- /** Converts one stored menu item into a serializable plugin payload element. */
173
+ /** Converts one stored item into a serializable menu payload element. */
179
174
  function serializeMenuItem(item: StoredMenuItem): SerializedMenuItem | null {
180
175
  if (item.hidden) return null;
181
176
 
@@ -202,7 +197,7 @@ function serializeMenuItem(item: StoredMenuItem): SerializedMenuItem | null {
202
197
  };
203
198
  }
204
199
 
205
- /** Serializes the current menu registry for the host runtime. */
200
+ /** Serializes the local registry into plugin menu payloads. */
206
201
  function serializeMenus(): SerializedMenuDefinition[] {
207
202
  return menuOrder
208
203
  .map((menuId, index) => {
@@ -214,13 +209,13 @@ function serializeMenus(): SerializedMenuDefinition[] {
214
209
  order: index + 1,
215
210
  elements: menu.items
216
211
  .map((item) => serializeMenuItem(item))
217
- .filter((item): item is SerializedMenuItem => Boolean(item)),
212
+ .filter((item): item is SerializedMenuItem => Boolean(item)),
218
213
  };
219
214
  })
220
215
  .filter((menu): menu is SerializedMenuDefinition => Boolean(menu));
221
216
  }
222
217
 
223
- /** Runs one registered action handler by id. */
218
+ /** Runs a registered action handler by id. */
224
219
  export async function runRegisteredAction(actionId: string, ...args: unknown[]): Promise<boolean> {
225
220
  const id = String(actionId || '').trim();
226
221
  if (!id) return false;
@@ -241,80 +236,52 @@ export interface MenuHandle {
241
236
  showItem(actionId: string): void;
242
237
  }
243
238
 
244
- /** Creates a stable, mutation-based handle for one menu id. */
239
+ /** Creates a stable handle for one stored menu. */
245
240
  function createMenuHandle(menuId: string): MenuHandle {
246
241
  const id = normalizeMenuId(menuId);
247
242
 
248
243
  return {
249
244
  id,
250
245
  addItem(item: MenubarItem) {
251
- const menu = getStoredMenu(this.id) || ensureStoredMenu(this.id, this.id);
252
246
  const label = String(item?.label || '').trim();
253
247
  const action = String(item?.action || '').trim();
254
248
  if (!label || !action) return;
255
249
 
256
- const entry: StoredMenuItem = {
250
+ const menu = getStoredMenu(this.id) || ensureStoredMenu(this.id, this.id);
251
+ insertMenuItem(menu, {
257
252
  kind: 'button',
258
253
  id: action,
259
254
  label,
260
255
  title: item.title,
261
256
  action,
262
- };
263
- insertMenuItem(menu, entry, item.before, item.after);
264
-
265
- const openvcs = getOpenVCS();
266
- if (!openvcs?.menus) return;
267
- openvcs.menus.getOrCreate(menu.id, menu.label);
268
- openvcs.menus.addMenuItem(menu.id, item);
257
+ }, item.before, item.after);
269
258
  },
270
259
  addSeparator(beforeAction?: string) {
271
260
  const menu = getStoredMenu(this.id) || ensureStoredMenu(this.id, this.id);
272
- const entry: StoredMenuItem = {
261
+ insertMenuItem(menu, {
273
262
  kind: 'separator',
274
263
  id: allocateSyntheticId(`${menu.id}-separator`),
275
264
  label: 'Separator',
276
265
  content: '—',
277
- };
278
- insertMenuItem(menu, entry, beforeAction);
279
-
280
- const openvcs = getOpenVCS();
281
- if (!openvcs?.menus) return;
282
- openvcs.menus.getOrCreate(menu.id, menu.label);
283
- openvcs.menus.addMenuSeparator(menu.id, beforeAction);
266
+ }, beforeAction);
284
267
  },
285
268
  removeItem(actionId: string) {
286
269
  const menu = getStoredMenu(this.id);
287
270
  if (!menu) return;
288
271
  const idToRemove = normalizeMenuId(actionId);
289
272
  menu.items = menu.items.filter((item) => item.action !== idToRemove);
290
-
291
- const openvcs = getOpenVCS();
292
- if (openvcs?.menus) {
293
- const handle = openvcs.menus.get(this.id) as MenuHandle | null;
294
- handle?.removeItem(idToRemove);
295
- }
296
273
  },
297
274
  hideItem(actionId: string) {
298
275
  const menu = getStoredMenu(this.id);
299
276
  if (!menu) return;
300
- const item = findMenuItem(menu, actionId);
277
+ const item = findStoredItem(menu, actionId);
301
278
  if (item) item.hidden = true;
302
-
303
- const openvcs = getOpenVCS();
304
- if (!openvcs?.menus) return;
305
- const handle = openvcs.menus.get(this.id) as MenuHandle | null;
306
- handle?.hideItem(actionId);
307
279
  },
308
280
  showItem(actionId: string) {
309
281
  const menu = getStoredMenu(this.id);
310
282
  if (!menu) return;
311
- const item = findMenuItem(menu, actionId);
283
+ const item = findStoredItem(menu, actionId);
312
284
  if (item) item.hidden = false;
313
-
314
- const openvcs = getOpenVCS();
315
- if (!openvcs?.menus) return;
316
- const handle = openvcs.menus.get(this.id) as MenuHandle | null;
317
- handle?.showItem(actionId);
318
285
  },
319
286
  };
320
287
  }
@@ -326,59 +293,45 @@ export function getMenu(menuId: string): MenuHandle | null {
326
293
  return createMenuHandle(stored.id);
327
294
  }
328
295
 
329
- /** Returns a menu by id, creating it when necessary. */
296
+ /** Returns a menu by id, creating it if needed. */
330
297
  export function getOrCreateMenu(menuId: string, label: string): MenuHandle | null {
331
298
  const stored = ensureStoredMenu(menuId, label);
332
299
  return createMenuHandle(stored.id);
333
300
  }
334
301
 
335
- /** Creates a menu at a specific insertion point. */
302
+ /** Creates a menu at a specific position. */
336
303
  export function createMenu(menuId: string, label: string, options?: MenubarMenuOptions): MenuHandle | null {
337
304
  const stored = ensureStoredMenu(menuId, label, options);
338
305
  return createMenuHandle(stored.id);
339
306
  }
340
307
 
341
- /** Adds one action item to a menu. */
308
+ /** Adds one item to a menu. */
342
309
  export function addMenuItem(menuId: string, item: MenubarItem): void {
343
- const menu = getStoredMenu(menuId) || ensureStoredMenu(menuId, menuId);
344
- const handle = createMenuHandle(menu.id);
345
- handle.addItem(item);
310
+ createMenuHandle(menuId).addItem(item);
346
311
  }
347
312
 
348
- /** Adds one separator item to a menu. */
313
+ /** Adds one separator to a menu. */
349
314
  export function addMenuSeparator(menuId: string, beforeAction?: string): void {
350
- const menu = getStoredMenu(menuId) || ensureStoredMenu(menuId, menuId);
351
- const handle = createMenuHandle(menu.id);
352
- handle.addSeparator(beforeAction);
315
+ createMenuHandle(menuId).addSeparator(beforeAction);
353
316
  }
354
317
 
355
- /** Removes one menu entirely. */
318
+ /** Removes one menu from the registry. */
356
319
  export function removeMenu(menuId: string): void {
357
320
  const id = normalizeMenuId(menuId);
358
321
  menus.delete(id);
359
- const index = menuOrder.indexOf(id);
360
- if (index >= 0) menuOrder.splice(index, 1);
361
-
362
- const openvcs = getOpenVCS();
363
- openvcs?.menus?.remove(id);
322
+ removeMenuId(id);
364
323
  }
365
324
 
366
- /** Hides one menu from the host UI. */
325
+ /** Hides one menu from the registry. */
367
326
  export function hideMenu(menuId: string): void {
368
327
  const menu = getStoredMenu(menuId);
369
328
  if (menu) menu.hidden = true;
370
-
371
- const openvcs = getOpenVCS();
372
- openvcs?.menus?.hide(normalizeMenuId(menuId));
373
329
  }
374
330
 
375
- /** Shows one previously hidden menu. */
331
+ /** Shows one menu from the registry. */
376
332
  export function showMenu(menuId: string): void {
377
333
  const menu = getStoredMenu(menuId);
378
334
  if (menu) menu.hidden = false;
379
-
380
- const openvcs = getOpenVCS();
381
- openvcs?.menus?.show(normalizeMenuId(menuId));
382
335
  }
383
336
 
384
337
  /** Registers an action handler by id. */
@@ -386,27 +339,24 @@ export function registerAction(id: string, handler: (...args: unknown[]) => unkn
386
339
  const key = String(id || '').trim();
387
340
  if (!key) return;
388
341
  actionHandlers.set(key, handler);
389
-
390
- const openvcs = getOpenVCS();
391
- openvcs?.registerAction(key, handler);
392
342
  }
393
343
 
394
- /** Invokes a host command when the host exposes a direct invoke helper. */
344
+ /** Invokes a host command when a host helper is available. */
395
345
  export function invoke<T = unknown>(cmd: string, args?: unknown): Promise<T> {
396
346
  const openvcs = getOpenVCS();
397
347
  if (!openvcs) {
398
- return Promise.reject(new Error('OpenVCS not available'));
348
+ return Promise.reject(new Error('OpenVCS host is not available in this runtime'));
399
349
  }
400
350
  return openvcs.invoke(cmd, args);
401
351
  }
402
352
 
403
- /** Emits a user notification when the host exposes one. */
353
+ /** Emits a notification when the host helper is available. */
404
354
  export function notify(msg: string): void {
405
355
  const openvcs = getOpenVCS();
406
356
  openvcs?.notify(msg);
407
357
  }
408
358
 
409
- /** Builds the SDK plugin delegates contributed by the menu registry. */
359
+ /** Builds SDK delegates from the local menu/action registries. */
410
360
  export function createMenuPluginDelegates(): PluginDelegates<PluginRuntimeContext> {
411
361
  return {
412
362
  async 'plugin.get_menus'(): Promise<PluginMenuDefinition[]> {
@@ -414,8 +364,9 @@ export function createMenuPluginDelegates(): PluginDelegates<PluginRuntimeContex
414
364
  },
415
365
  async 'plugin.handle_action'(params: PluginHandleActionParams): Promise<null> {
416
366
  const actionId = String(params?.action_id || '').trim();
417
- if (!actionId) return null;
418
- await runRegisteredAction(actionId);
367
+ if (actionId) {
368
+ await runRegisteredAction(actionId, params?.payload);
369
+ }
419
370
  return null;
420
371
  },
421
372
  };