@silo-code/sdk 0.6.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.
Files changed (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/dist/context-keys.d.ts +19 -0
  4. package/dist/context-keys.d.ts.map +1 -0
  5. package/dist/context-keys.js +2 -0
  6. package/dist/context-keys.js.map +1 -0
  7. package/dist/dnd-service.d.ts +140 -0
  8. package/dist/dnd-service.d.ts.map +1 -0
  9. package/dist/dnd-service.js +17 -0
  10. package/dist/dnd-service.js.map +1 -0
  11. package/dist/domain-types.d.ts +237 -0
  12. package/dist/domain-types.d.ts.map +1 -0
  13. package/dist/domain-types.js +11 -0
  14. package/dist/domain-types.js.map +1 -0
  15. package/dist/editor-service.d.ts +175 -0
  16. package/dist/editor-service.d.ts.map +1 -0
  17. package/dist/editor-service.js +2 -0
  18. package/dist/editor-service.js.map +1 -0
  19. package/dist/extension-storage.d.ts +26 -0
  20. package/dist/extension-storage.d.ts.map +1 -0
  21. package/dist/extension-storage.js +2 -0
  22. package/dist/extension-storage.js.map +1 -0
  23. package/dist/file-service.d.ts +84 -0
  24. package/dist/file-service.d.ts.map +1 -0
  25. package/dist/file-service.js +2 -0
  26. package/dist/file-service.js.map +1 -0
  27. package/dist/index.d.ts +32 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +22 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/layout-service.d.ts +46 -0
  32. package/dist/layout-service.d.ts.map +1 -0
  33. package/dist/layout-service.js +2 -0
  34. package/dist/layout-service.js.map +1 -0
  35. package/dist/permissions.d.ts +41 -0
  36. package/dist/permissions.d.ts.map +1 -0
  37. package/dist/permissions.js +40 -0
  38. package/dist/permissions.js.map +1 -0
  39. package/dist/process-service.d.ts +132 -0
  40. package/dist/process-service.d.ts.map +1 -0
  41. package/dist/process-service.js +2 -0
  42. package/dist/process-service.js.map +1 -0
  43. package/dist/terminal-service.d.ts +38 -0
  44. package/dist/terminal-service.d.ts.map +1 -0
  45. package/dist/terminal-service.js +2 -0
  46. package/dist/terminal-service.js.map +1 -0
  47. package/dist/theme-service.d.ts +87 -0
  48. package/dist/theme-service.d.ts.map +1 -0
  49. package/dist/theme-service.js +2 -0
  50. package/dist/theme-service.js.map +1 -0
  51. package/dist/types.d.ts +495 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +2 -0
  54. package/dist/types.js.map +1 -0
  55. package/dist/ui-service.d.ts +469 -0
  56. package/dist/ui-service.d.ts.map +1 -0
  57. package/dist/ui-service.js +2 -0
  58. package/dist/ui-service.js.map +1 -0
  59. package/dist/use-focus-group.d.ts +202 -0
  60. package/dist/use-focus-group.d.ts.map +1 -0
  61. package/dist/use-focus-group.js +236 -0
  62. package/dist/use-focus-group.js.map +1 -0
  63. package/dist/use-service-state.d.ts +36 -0
  64. package/dist/use-service-state.d.ts.map +1 -0
  65. package/dist/use-service-state.js +25 -0
  66. package/dist/use-service-state.js.map +1 -0
  67. package/dist/workspace-service.d.ts +72 -0
  68. package/dist/workspace-service.d.ts.map +1 -0
  69. package/dist/workspace-service.js +2 -0
  70. package/dist/workspace-service.js.map +1 -0
  71. package/package.json +54 -0
  72. package/src/context-keys.ts +18 -0
  73. package/src/dnd-service.ts +151 -0
  74. package/src/domain-types.ts +252 -0
  75. package/src/editor-service.ts +196 -0
  76. package/src/extension-storage.ts +25 -0
  77. package/src/file-service.ts +90 -0
  78. package/src/index.ts +151 -0
  79. package/src/layout-service.ts +49 -0
  80. package/src/permissions.ts +55 -0
  81. package/src/process-service.ts +143 -0
  82. package/src/terminal-service.ts +41 -0
  83. package/src/theme-service.ts +102 -0
  84. package/src/types.ts +513 -0
  85. package/src/ui-service.ts +487 -0
  86. package/src/use-focus-group.test.ts +168 -0
  87. package/src/use-focus-group.ts +382 -0
  88. package/src/use-service-state.ts +43 -0
  89. package/src/workspace-service.ts +76 -0
package/src/types.ts ADDED
@@ -0,0 +1,513 @@
1
+ /**
2
+ * The Silo extension API — types only.
3
+ *
4
+ * Everything an extension author can see at compile time lives here (plus the
5
+ * service interfaces in the sibling `*-service` files, which this module
6
+ * references). The host injects the runtime implementation at activation time
7
+ * via {@link ExtensionContext}; extensions compile *against* these types and
8
+ * never import the host. This is the VS Code (`vscode.d.ts`) / Obsidian
9
+ * (`obsidian-api`) model.
10
+ *
11
+ * Start with {@link Extension} (the unit you export) and
12
+ * {@link ExtensionContext} (the `ctx` the host hands you).
13
+ *
14
+ * @remarks Every symbol exported here is part of the public, permanently
15
+ * supported surface. Keep it minimal and deliberate — each addition is a
16
+ * forever breaking-change liability.
17
+ *
18
+ * @packageDocumentation
19
+ */
20
+ import type React from "react";
21
+ import type { IDockviewPanelProps } from "dockview";
22
+ import type { ContextKeys } from "./context-keys";
23
+ import type { WorkspaceService } from "./workspace-service";
24
+ import type { EditorService } from "./editor-service";
25
+ import type { LayoutService } from "./layout-service";
26
+ import type { ProcessService } from "./process-service";
27
+ import type { TerminalService } from "./terminal-service";
28
+ import type { FileService } from "./file-service";
29
+ import type { ThemeService, ThemePreset } from "./theme-service";
30
+ import type { DndService } from "./dnd-service";
31
+ import type { UiService } from "./ui-service";
32
+ import type { ExtensionStorage } from "./extension-storage";
33
+
34
+ /**
35
+ * The teardown handle returned by every `register*` call on
36
+ * {@link ExtensionContext}. Calling {@link Disposable.dispose | dispose}
37
+ * removes the contribution. Disposables are also collected on
38
+ * {@link ExtensionContext.subscriptions} so the host can tear an extension
39
+ * down wholesale.
40
+ *
41
+ * @category Core Types
42
+ * @public
43
+ */
44
+ export interface Disposable {
45
+ dispose(): void;
46
+ }
47
+
48
+ /**
49
+ * The dockview panel API handed to a {@link DockPanelKind} component — used to
50
+ * drive the panel's own tab (title, close, focus). Re-exported from `dockview`
51
+ * so extensions don't take a direct dependency on it.
52
+ *
53
+ * @category Core Types
54
+ * @public
55
+ */
56
+ export type DockPanelApi = IDockviewPanelProps["api"];
57
+
58
+ /**
59
+ * Props passed to an {@link Editor} component. An editor renders the contents of
60
+ * one editor tab (a presenter for a file type — distinct from
61
+ * {@link ExtensionContext.editors}, which is the document model).
62
+ *
63
+ * @category Registration
64
+ * @public
65
+ */
66
+ export interface EditorProps {
67
+ /** Stable id of the editor tab this editor instance is rendering. */
68
+ editorId: string;
69
+ /** Absolute path of the file, or `null` for an untitled buffer. */
70
+ filePath: string | null;
71
+ /** Handle to the surrounding dock panel (title, close, focus). */
72
+ dockApi: DockPanelApi;
73
+ }
74
+
75
+ /**
76
+ * Optional declarations about what an {@link Editor} can do, used by the host to
77
+ * decide routing (e.g. whether the editor can own an untitled buffer).
78
+ *
79
+ * @category Registration
80
+ * @public
81
+ */
82
+ export interface EditorCapabilities {
83
+ /**
84
+ * The editor only displays, it can't edit or save (e.g. the image viewer, a
85
+ * future PDF/hex preview). Defaults to false — editors are editable unless
86
+ * they opt out. Mirrors VS Code's read-only custom-editor variant.
87
+ */
88
+ readonly?: boolean;
89
+ /** The editor can render a never-saved (untitled) buffer. Defaults to false. */
90
+ handlesUntitled?: boolean;
91
+ }
92
+
93
+ /**
94
+ * Contributes a presenter for a file type — everything that opens in the editor
95
+ * area is an `Editor` (a read-write text editor, a read-only image viewer, …).
96
+ * The host picks one per file by calling each registered editor's
97
+ * {@link Editor.match} (highest {@link Editor.priority} wins) and mounting its
98
+ * {@link Editor.component}. Registered via
99
+ * {@link ExtensionContext.registerEditor}; not to be confused with
100
+ * {@link ExtensionContext.editors} (the document/tab model).
101
+ *
102
+ * @category Registration
103
+ * @public
104
+ */
105
+ export interface Editor {
106
+ /** Unique id, conventionally namespaced (e.g. `"core.text-editor"`). */
107
+ id: string;
108
+ /**
109
+ * Human-facing name of this *view* of a file, e.g. `"Text"` or `"Preview"`.
110
+ * Surfaces in the breadcrumb view-switcher and the explorer "Open With" menu
111
+ * when more than one editor matches a file. Defaults to {@link Editor.id}
112
+ * where a label is needed but none is given.
113
+ */
114
+ label?: string;
115
+ /** Returns true if this editor should handle the given path (`null` = untitled). */
116
+ match: (path: string | null) => boolean;
117
+ /** The React component rendered for matched tabs. */
118
+ component: React.ComponentType<EditorProps>;
119
+ /**
120
+ * Higher wins when multiple editors match the same file. Defaults to 0. On a
121
+ * tie the first-registered editor wins — so a second editor for a type can
122
+ * deliberately be an *alternate* (not the default) by matching the
123
+ * incumbent's priority rather than exceeding it. Users still pick any matching
124
+ * view per-tab via "Open With" / the view-switcher
125
+ * ({@link OpenFileOptions.viewType}).
126
+ */
127
+ priority?: number;
128
+ /** Optional routing hints — see {@link EditorCapabilities}. */
129
+ capabilities?: EditorCapabilities;
130
+ }
131
+
132
+ /**
133
+ * Describes how to bootstrap a new, never-saved buffer for a file type. Its
134
+ * mere presence on a FileType marks the type as creatable — it shows up in the
135
+ * "New File" surfaces (the tab-group + menu, the empty-workspace watermark).
136
+ *
137
+ * @category Registration
138
+ * @public
139
+ */
140
+ export interface NewFileTemplate {
141
+ /** Base name (no extension) for the untitled buffer. Defaults to "Untitled". */
142
+ defaultName?: string;
143
+ }
144
+
145
+ /**
146
+ * Declarative metadata about a file extension — the open-ended counterpart to
147
+ * Viewer (which is purely a renderer). A single source of truth that "New File"
148
+ * surfaces (and, later, tab/explorer icons) can enumerate. Registering a
149
+ * FileType does not register a viewer; the two are matched independently by
150
+ * extension at dispatch time.
151
+ *
152
+ * @category Registration
153
+ * @public
154
+ */
155
+ export interface FileType {
156
+ /** Unique id, conventionally namespaced. */
157
+ id: string;
158
+ /** Human label, e.g. "Foo File". Used to build "New {label}…" entries. */
159
+ label: string;
160
+ /** Extensions this type owns — leading dot, lowercase. e.g. [".foo"]. */
161
+ extensions: string[];
162
+ /** When present, the type can be created from "New File" surfaces. */
163
+ newFile?: NewFileTemplate;
164
+ }
165
+
166
+ /**
167
+ * A named, invokable action. Register with
168
+ * {@link ExtensionContext.registerCommand} and trigger from menu items,
169
+ * keybindings, status items, or {@link ExtensionContext.executeCommand}.
170
+ *
171
+ * @category Registration
172
+ * @public
173
+ */
174
+ export interface Command {
175
+ /** Unique id, conventionally `area.verb` (e.g. `"view.toggleLeftPanel"`). */
176
+ id: string;
177
+ /** Human-readable label (shown where the command surfaces in UI). */
178
+ label: string;
179
+ /** The action. Runs synchronously; do async work inside if needed. */
180
+ run: () => void;
181
+ }
182
+
183
+ /**
184
+ * The top-level application menus a {@link MenuItemContribution} can target.
185
+ *
186
+ * @category Registration
187
+ * @public
188
+ */
189
+ export type MenuId = "file" | "edit" | "view" | "window";
190
+
191
+ /**
192
+ * Places a command into one of the application menus.
193
+ *
194
+ * @category Registration
195
+ * @public
196
+ */
197
+ export interface MenuItemContribution {
198
+ /** Unique id for this menu entry. */
199
+ id: string;
200
+ /** Which application menu to place the item in. */
201
+ menu: MenuId;
202
+ /** Id of the {@link Command} this item invokes. */
203
+ command: string;
204
+ /** Override label; defaults to the command's label. */
205
+ label?: string;
206
+ /** Accelerator shown next to the item (display only; bind via {@link Keybinding}). */
207
+ accelerator?: string;
208
+ /**
209
+ * Group key used to bucket items in the same submenu. Items in different
210
+ * groups are visually separated by a separator. Group names are sorted
211
+ * lexically — convention is to prefix with a digit ("1_new", "2_save").
212
+ * Defaults to "9_default" so unspecified items land at the bottom.
213
+ */
214
+ group?: string;
215
+ /** Sort order within a group. Defaults to 0. */
216
+ order?: number;
217
+ /**
218
+ * Optional predicate against current context keys. Items whose `when`
219
+ * returns false stay visible in the app menu but are disabled (macOS
220
+ * native pattern — items can't appear/disappear without rebuilding the
221
+ * whole menu). Items without `when` are always enabled.
222
+ */
223
+ when?: (ctx: ContextKeys) => boolean;
224
+ }
225
+
226
+ /**
227
+ * Binds a keyboard shortcut to a {@link Command}.
228
+ *
229
+ * @category Registration
230
+ * @public
231
+ */
232
+ export interface Keybinding {
233
+ /** Unique id for this binding. */
234
+ id: string;
235
+ /**
236
+ * Shortcut spec like "cmd+s", "ctrl+shift+s", "cmd+1", "cmd+shift+=".
237
+ * Parsed against KeyboardEvent.code for layout-stable letter/digit keys
238
+ * plus a small alias table for symbol keys (= → Equal, - → Minus, etc.).
239
+ */
240
+ key: string;
241
+ /** Id of the {@link Command} to invoke. */
242
+ command: string;
243
+ /** Optional predicate against context keys; the binding is inert when false. */
244
+ when?: (ctx: ContextKeys) => boolean;
245
+ }
246
+
247
+ /**
248
+ * Props passed to a {@link SidePanel} component.
249
+ *
250
+ * @category Registration
251
+ * @public
252
+ */
253
+ export interface SidePanelProps {
254
+ /** True when this side panel is currently visible / selected in its column. */
255
+ active: boolean;
256
+ /**
257
+ * Namespaced, persisted key/value storage scoped to this panel id.
258
+ * Use for restoring UI state (scroll positions, selections, expanded
259
+ * sections, etc.) across reloads.
260
+ */
261
+ storage: ExtensionStorage;
262
+ /**
263
+ * True once the persisted app state has finished loading from disk.
264
+ * Panels should defer restoring values from `storage` until this is true
265
+ * (or subscribe to `storage` and re-read when it flips).
266
+ */
267
+ hydrated: boolean;
268
+ }
269
+
270
+ /**
271
+ * A panel mounted in the left or right side column (e.g. file explorer, git).
272
+ *
273
+ * @category Registration
274
+ * @public
275
+ */
276
+ export interface SidePanel {
277
+ /** Unique id, conventionally namespaced. */
278
+ id: string;
279
+ /** Which side column to mount in. */
280
+ location: "left" | "right";
281
+ /** Title shown in the column's panel switcher. */
282
+ title: string;
283
+ /** The React component; receives {@link SidePanelProps}. */
284
+ component: React.ComponentType<SidePanelProps>;
285
+ /** Sort order within the side column. Defaults to 0. */
286
+ order?: number;
287
+ /**
288
+ * If true, defer mounting the component until the first time this panel
289
+ * becomes active. Once mounted, stays mounted (panel can use `active`
290
+ * to pause expensive work when hidden).
291
+ */
292
+ lazyMount?: boolean;
293
+ }
294
+
295
+ /**
296
+ * Registers a kind of dock panel (a tab that can live in the center dock area,
297
+ * e.g. the terminal). Workspaces open panels of registered kinds by id.
298
+ *
299
+ * @category Registration
300
+ * @public
301
+ */
302
+ export interface DockPanelKind {
303
+ /** Unique id for this panel kind. */
304
+ id: string;
305
+ /** The React component; receives the raw dockview panel props. */
306
+ component: React.ComponentType<IDockviewPanelProps>;
307
+ }
308
+
309
+ /**
310
+ * A widget in the status bar (the strip along the bottom of the window).
311
+ *
312
+ * @category Registration
313
+ * @public
314
+ */
315
+ export interface StatusItem {
316
+ /** Unique id for this status item. */
317
+ id: string;
318
+ /** Which end of the status bar this item sits at. */
319
+ alignment: "left" | "right";
320
+ /** Sort order within its alignment group. Lower sorts first. Defaults to 0. */
321
+ priority?: number;
322
+ /** The React component (renders its own content; no props). */
323
+ component: React.ComponentType;
324
+ }
325
+
326
+ /**
327
+ * A page in the Settings dialog, listed in the left rail.
328
+ *
329
+ * @category Registration
330
+ * @public
331
+ */
332
+ export interface SettingsPage {
333
+ /** Unique id for this settings page. */
334
+ id: string;
335
+ /** Label shown in the Settings left rail. */
336
+ title: string;
337
+ /**
338
+ * Optional grouping key for the left rail (groups sorted lexically, separated
339
+ * by a divider). Honored only for `core.*` pages; a page contributed by any
340
+ * non-core extension is always grouped under **Extensions**, so `group` is
341
+ * ignored for those.
342
+ */
343
+ group?: string;
344
+ /** Sort order within the group. Lower sorts first. Defaults to 0. */
345
+ order?: number;
346
+ /** Renders the right-hand pane when this page is selected. */
347
+ component: React.ComponentType;
348
+ }
349
+
350
+ /**
351
+ * The object handed to {@link Extension.activate}. It is the *only* sanctioned
352
+ * way an extension touches the running app: register contributions, invoke
353
+ * commands, and read/drive state through the typed consumer services. Every
354
+ * `register*` call returns a {@link Disposable} and is also tracked on
355
+ * {@link ExtensionContext.subscriptions}.
356
+ *
357
+ * @category Extension Contract
358
+ * @public
359
+ */
360
+ export interface ExtensionContext {
361
+ /** The activating extension's id (its {@link Extension.id}). */
362
+ readonly extensionId: string;
363
+ /** Disposables tracked for this extension; the host disposes them on teardown. */
364
+ readonly subscriptions: Disposable[];
365
+ /** Register an {@link Editor} (a presenter for a file type's editor tab). */
366
+ registerEditor(editor: Editor): Disposable;
367
+ /** Register a {@link FileType} (declarative file metadata). */
368
+ registerFileType(type: FileType): Disposable;
369
+ /** Register a {@link Command} (a named, invokable action). */
370
+ registerCommand(cmd: Command): Disposable;
371
+ /** Register a {@link MenuItemContribution} (place a command in a menu). */
372
+ registerMenuItem(item: MenuItemContribution): Disposable;
373
+ /** Register a {@link Keybinding} (bind a shortcut to a command). */
374
+ registerKeybinding(binding: Keybinding): Disposable;
375
+ /** Register a {@link SidePanel} (a left/right column panel). */
376
+ registerSidePanel(panel: SidePanel): Disposable;
377
+ /** Register a {@link DockPanelKind} (a center-dock tab kind). */
378
+ registerDockPanelKind(kind: DockPanelKind): Disposable;
379
+ /** Register a {@link StatusItem} (a status-bar widget). */
380
+ registerStatusItem(item: StatusItem): Disposable;
381
+ /** Register a {@link SettingsPage} (a page in the Settings dialog). */
382
+ registerSettingsPage(page: SettingsPage): Disposable;
383
+ /** Register a {@link ThemePreset} (a selectable theme in the picker). */
384
+ registerThemePreset(preset: ThemePreset): Disposable;
385
+ /**
386
+ * Invoke a registered command by id — including commands contributed by
387
+ * other extensions. The minimal "operate" primitive; pairs with the typed
388
+ * services for read access.
389
+ */
390
+ executeCommand(id: string): void;
391
+ /**
392
+ * Consumer API for driving workspace state — create, rename, reorder,
393
+ * activate, soft close/reopen, and hard delete. Subscribe to a frozen
394
+ * state for read access without depending on Valtio.
395
+ */
396
+ readonly workspaces: WorkspaceService;
397
+ /**
398
+ * The editor & document domain — open files into editor tabs, drive the
399
+ * active editor (save / close), and register editor save handlers. Opening
400
+ * editors lives here, not on {@link ExtensionContext.workspaces}.
401
+ */
402
+ readonly editors: EditorService;
403
+ /**
404
+ * Consumer API for app layout — side-panel collapse state. Read via
405
+ * getState/useServiceState/subscribe; drive via toggleSidePanel /
406
+ * setSidePanelCollapsed.
407
+ */
408
+ readonly layout: LayoutService;
409
+ /**
410
+ * Persistent process / PTY sessions that **survive app restarts** — the core
411
+ * primitive under the terminal (and future task runners, REPLs). Spawn or
412
+ * re-attach a session and drive it via the returned `ProcessSession`.
413
+ */
414
+ readonly process: ProcessService;
415
+ /**
416
+ * Consumer API for the terminal domain — open a terminal tab in a workspace
417
+ * (`create`) or reap a workspace's terminals (`closeWorkspace`). The terminal
418
+ * is a core feature (a built-in DockKind like the editor); its tabs render
419
+ * from the workspace's records, and PTY sessions live on
420
+ * {@link ExtensionContext.process}.
421
+ */
422
+ readonly terminals: TerminalService;
423
+ /**
424
+ * Host-mediated filesystem access — read / write / list / watch, all routed
425
+ * through the host rather than raw Tauri. The single privileged chokepoint
426
+ * for the filesystem; watcher lifecycle is host-owned (see {@link FileService}).
427
+ */
428
+ readonly files: FileService;
429
+ /**
430
+ * Consumer API for the theme domain — read the merged preset set + active
431
+ * theme, switch themes, and manage custom themes. Read via getState /
432
+ * subscribe; contribute a new preset via
433
+ * {@link ExtensionContext.registerThemePreset}.
434
+ */
435
+ readonly theme: ThemeService;
436
+ /**
437
+ * Drag-and-drop — be a drag source ({@link DndService.beginDrag}) and a drop
438
+ * target ({@link DndService.registerDropTarget}), with typed payloads
439
+ * ({@link DND_MIME}) that interoperate across extensions. The host owns the
440
+ * drag affordance and the modifier-mode resolution.
441
+ */
442
+ readonly dnd: DndService;
443
+ /**
444
+ * User-interaction — the only sanctioned way to talk to the user (the host
445
+ * renders the chrome). Native file/folder pickers ({@link UiService.pickFolder},
446
+ * {@link UiService.pickFile}, {@link UiService.savePath}) and transient toast
447
+ * notifications ({@link UiService.notify}). Mirrors VS Code's `window.show*`.
448
+ */
449
+ readonly ui: UiService;
450
+ /**
451
+ * Resolve a handle to another extension in order to consume the API it
452
+ * published (the value its {@link Extension.activate} returned). This is how
453
+ * features that live *outside* core — git, terminal, themes — expose
454
+ * capabilities to other extensions.
455
+ *
456
+ * Returns `undefined` if no extension with that id is known. Even when known,
457
+ * the handle's {@link ExtensionHandle.api | api} is `undefined` until that
458
+ * extension has activated — so **always handle absence**; the provider may be
459
+ * disabled or activate after you. Call this at use time, not in `activate`.
460
+ *
461
+ * @typeParam API - the provider's published API type (import its types package).
462
+ */
463
+ getExtension<API = unknown>(id: string): ExtensionHandle<API> | undefined;
464
+ }
465
+
466
+ /**
467
+ * A handle to another extension, obtained via
468
+ * {@link ExtensionContext.getExtension}. Lets one extension consume another's
469
+ * published API while tolerating its absence.
470
+ *
471
+ * @category Extension Contract
472
+ * @public
473
+ */
474
+ export interface ExtensionHandle<API = unknown> {
475
+ /** The resolved extension's id. */
476
+ readonly id: string;
477
+ /** True once that extension has activated. */
478
+ readonly active: boolean;
479
+ /** Its published API (what its `activate` returned), or `undefined` if it
480
+ * hasn't activated or published nothing. */
481
+ readonly api: API | undefined;
482
+ }
483
+
484
+ /**
485
+ * The shape of a Silo extension: a stable id plus an
486
+ * {@link Extension.activate | activate} function the host calls once, passing
487
+ * the {@link ExtensionContext}. This is the unit the host loads — built-ins
488
+ * today, external packages later.
489
+ *
490
+ * @typeParam API - the type of the API this extension publishes, if any (the
491
+ * return type of {@link Extension.activate}). Defaults to `unknown`; omit it for
492
+ * extensions that publish nothing.
493
+ *
494
+ * @category Extension Contract
495
+ * @public
496
+ */
497
+ export interface Extension<API = unknown> {
498
+ /**
499
+ * Unique extension id, conventionally namespaced: `core.*` for Silo's core
500
+ * feature set, `silo.*` for its optional bundled features, `<vendor>.*` for
501
+ * third parties (e.g. `"core.editor"`, `"silo.git"`, `"acme.foo"`).
502
+ */
503
+ id: string;
504
+ /**
505
+ * Called once by the host; register contributions against `ctx` here.
506
+ * **Optionally return an API object** to publish it for other extensions to
507
+ * consume via {@link ExtensionContext.getExtension}. Return nothing if the
508
+ * extension publishes no API.
509
+ */
510
+ activate(ctx: ExtensionContext): API | void;
511
+ /** Optional cleanup hook (reserved for dynamic load/unload). */
512
+ deactivate?(): void;
513
+ }