@clipbus/plugin-sdk 0.7.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/API.md +641 -0
- package/LICENSE +21 -0
- package/README.md +466 -0
- package/SPECIFICATION.md +355 -0
- package/dist/dom/autoFit.d.ts +15 -0
- package/dist/dom/consolePatch.d.ts +1 -0
- package/dist/dom/index.cjs +211 -0
- package/dist/dom/index.d.cts +6 -0
- package/dist/dom/index.d.ts +6 -0
- package/dist/dom/index.js +188 -0
- package/dist/dom/textInputState.d.ts +1 -0
- package/dist/dom/topicAdapter.d.ts +30 -0
- package/dist/generated/INDEX.runtime.generated.d.ts +6 -0
- package/dist/generated/INDEX.ui.generated.d.ts +4 -0
- package/dist/generated/capabilityClients.generated.d.ts +199 -0
- package/dist/generated/data.generated.d.ts +193 -0
- package/dist/generated/hostClients.generated.d.ts +38 -0
- package/dist/generated/runtime.actionResult.generated.d.ts +28 -0
- package/dist/generated/runtime.definePlugin.generated.d.ts +16 -0
- package/dist/generated/runtime.handlers.generated.d.ts +20 -0
- package/dist/generated/runtime.host.generated.d.ts +34 -0
- package/dist/generated/topicSubscribers.generated.d.ts +32 -0
- package/dist/generated/ui.bootstrap.generated.d.ts +15 -0
- package/dist/generated/ui.clipbus.generated.d.ts +79 -0
- package/dist/generated/wireConstants.generated.d.ts +3 -0
- package/dist/internal/capabilities.d.ts +31 -0
- package/dist/internal/index.cjs +68 -0
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/internalConsole.d.ts +7 -0
- package/dist/internal/ipcBus.d.ts +48 -0
- package/dist/internal/runtimeInvokeClient.d.ts +3 -0
- package/dist/internal/topic.d.ts +20 -0
- package/dist/runtime/defineMessage.d.ts +6 -0
- package/dist/runtime/index.cjs +163 -0
- package/dist/runtime/index.d.cts +4 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +132 -0
- package/dist/shared/defineMessage.d.ts +7 -0
- package/dist/ui/defineMessage.d.ts +7 -0
- package/dist/ui/index.cjs +362 -0
- package/dist/ui/index.d.cts +4 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +339 -0
- package/docs/README.md +34 -0
- package/docs/authoring.md +288 -0
- package/docs/capability-detection.md +105 -0
- package/docs/concepts.md +80 -0
- package/docs/entry.md +137 -0
- package/docs/faq.md +65 -0
- package/docs/item-context.md +186 -0
- package/docs/manifest.md +149 -0
- package/docs/permissions.md +32 -0
- package/docs/rpc.md +84 -0
- package/package.json +76 -0
package/README.md
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
# @clipbus/plugin-sdk
|
|
2
|
+
|
|
3
|
+
Standalone, publishable SDK for Clipbus plugins. Provides typed helpers for the runtime (Node.js) and UI (WebView) sides of a plugin.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install from npm:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @clipbus/plugin-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@clipbus/plugin-sdk": "^0.1.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then import the runtime, UI, or DOM entry point:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
const { definePlugin } = require('@clipbus/plugin-sdk/runtime');
|
|
25
|
+
import { clipbus } from '@clipbus/plugin-sdk/ui';
|
|
26
|
+
import { autoFit } from '@clipbus/plugin-sdk/dom';
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
> Publishing prerequisite: the `clipbus` npm org/scope must exist before the first
|
|
30
|
+
> `npm publish` (a one-time `npmjs.com` operation under a logged-in account).
|
|
31
|
+
> Fallback unscoped name if `@clipbus` is unavailable: `clipbus-plugin-sdk`.
|
|
32
|
+
|
|
33
|
+
## Runtime entry (`@clipbus/plugin-sdk/runtime`)
|
|
34
|
+
|
|
35
|
+
Used in Node.js plugin code to define capabilities and return results.
|
|
36
|
+
|
|
37
|
+
### `definePlugin(definition)`
|
|
38
|
+
|
|
39
|
+
Validates and returns a plugin definition object. Throws if `setup` is not a function.
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
const { definePlugin } = require('@clipbus/plugin-sdk/runtime');
|
|
43
|
+
|
|
44
|
+
module.exports = definePlugin({
|
|
45
|
+
setup(init) {
|
|
46
|
+
return {
|
|
47
|
+
detectors: { 'my-detector': { detect(input, ctx) { /* ... */ } } },
|
|
48
|
+
attachmentRenderers: { 'my-renderer': { resolveAttachment(input, ctx) { /* ... */ } } },
|
|
49
|
+
actions: { 'my-action': { resolveSession(input, ctx) { /* ... */ } } }
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `actionResult.text(value, options?)`
|
|
56
|
+
|
|
57
|
+
Returns a locked `{ result: { resultKind: 'text', text }, userMessage }` shape.
|
|
58
|
+
|
|
59
|
+
| Param | Type | Description |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `value` | `unknown` | Coerced to string. `null`/`undefined` → `""`. |
|
|
62
|
+
| `options.userMessage` | `string?` | Optional message shown to the user. |
|
|
63
|
+
|
|
64
|
+
### `actionResult.none(options?)`
|
|
65
|
+
|
|
66
|
+
Returns `{ result: { resultKind: 'none', text: null }, userMessage }`.
|
|
67
|
+
|
|
68
|
+
### `actionResult.image(value, options?)`
|
|
69
|
+
|
|
70
|
+
Returns a locked `{ result: { resultKind: 'image', imageTempPath, imageFormatHint }, userMessage }` shape. Use this when an action produces an image result written to a temp path allocated via `ctx.host.action.allocateImageTempPath()`.
|
|
71
|
+
|
|
72
|
+
| Param | Type | Description |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `value.imageTempPath` | `string` | Path returned by `allocateImageTempPath`. |
|
|
75
|
+
| `value.imageFormatHint` | `string?` | Optional format hint (e.g. `"png"`, `"jpeg"`). |
|
|
76
|
+
| `options.userMessage` | `string?` | Optional message shown to the user. |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### `messageHandlers` — handle UI → Runtime RPC calls
|
|
81
|
+
|
|
82
|
+
Register handlers that respond to calls made from UI via `clipbus.runtime.invoke`. Each key is a string; the value is an async function receiving `(request, ctx)`.
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
const { definePlugin } = require('@clipbus/plugin-sdk/runtime');
|
|
86
|
+
|
|
87
|
+
module.exports = definePlugin({
|
|
88
|
+
messageHandlers: {
|
|
89
|
+
'generate-image': async (req, ctx) => {
|
|
90
|
+
const { path } = await ctx.host.action.allocateImageTempPath({ formatHint: 'png' });
|
|
91
|
+
// ... write bytes to path ...
|
|
92
|
+
return { imageTempPath: path };
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### `defineMessage(key)` — shared type contract for UI ↔ Runtime RPC
|
|
99
|
+
|
|
100
|
+
Creates a typed contract object. Import from `@clipbus/plugin-sdk/runtime` on the runtime side (gives `.handle()`) or from `@clipbus/plugin-sdk/ui` on the UI side (gives `.invoke()`).
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// shared/contracts.ts (imported by both runtime and UI)
|
|
104
|
+
import { defineMessage } from '@clipbus/plugin-sdk/runtime';
|
|
105
|
+
|
|
106
|
+
export const GenerateImage = defineMessage<
|
|
107
|
+
{ prompt: string },
|
|
108
|
+
{ imageTempPath: string }
|
|
109
|
+
>('generate-image');
|
|
110
|
+
|
|
111
|
+
// runtime/index.ts
|
|
112
|
+
definePlugin({
|
|
113
|
+
messageHandlers: Object.fromEntries([
|
|
114
|
+
GenerateImage.handle(async (req, ctx) => {
|
|
115
|
+
const { path } = await ctx.host.action.allocateImageTempPath({ formatHint: 'png' });
|
|
116
|
+
return { imageTempPath: path };
|
|
117
|
+
}),
|
|
118
|
+
]),
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### Host verbs (`ctx.host.*`) — runtime context only
|
|
125
|
+
|
|
126
|
+
These verbs are available on the `ctx.host` object passed to every runtime handler. All calls go through real Node IPC and return typed responses.
|
|
127
|
+
|
|
128
|
+
#### `ctx.host.item`
|
|
129
|
+
|
|
130
|
+
| Method | Returns | Description |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| `setTags({tags})` | `Promise<{tags: string[]}>` | Replace item tags (requires `setTags` permission) |
|
|
133
|
+
| `addTags({tags})` | `Promise<{tags: string[]}>` | Add tags to item |
|
|
134
|
+
| `removeTags({tags})` | `Promise<{tags: string[]}>` | Remove tags from item |
|
|
135
|
+
| `setPinned({pinned})` | `Promise<{pinned: boolean}>` | Pin or unpin the item |
|
|
136
|
+
| `setAttachments(payload)` | `Promise<{}>` | Replace attachments (requires `setAttachment` permission) |
|
|
137
|
+
| `setSearchExtension(payload)` | `Promise<{}>` | Replace search extension entries |
|
|
138
|
+
| `materializeImagePath()` | `Promise<{path: string}>` | Copies the item's image to a stable temp file. Idempotent across invocation. Only valid for `image`-type items. |
|
|
139
|
+
| `readAttachment({attachmentType, attachmentKey})` | `Promise<{payloadJson?: string}>` | Returns the `payloadJson` string for the named attachment, or `null` if absent. |
|
|
140
|
+
|
|
141
|
+
#### `ctx.host.action`
|
|
142
|
+
|
|
143
|
+
| Method | Returns | Description |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `allocateImageTempPath({formatHint?})` | `Promise<{path: string}>` | Allocates a unique writable path for an image result. Consumed by `actionResult.image({imageTempPath})`. Cleaned up when invocation scope closes. |
|
|
146
|
+
|
|
147
|
+
#### `ctx.host.asset`
|
|
148
|
+
|
|
149
|
+
| Method | Returns | Description |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `registerImage({path})` | `Promise<{url: string}>` | Registers a Node-produced / local image file with the current session and returns an opaque `clipbus-asset://` URL the WebView can render in `<img>`. The host validates the file, copies it into the session owner dir, then mints a session-scoped token. The real path never reaches JS. |
|
|
152
|
+
|
|
153
|
+
#### `ctx.host.clipboard`, `ctx.host.navigation`, `ctx.host.settings`
|
|
154
|
+
|
|
155
|
+
| Method | Returns | Description |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| `clipboard.copyText({text})` | `Promise<void>` | Copy text to system clipboard |
|
|
158
|
+
| `navigation.openUrl({url})` | `Promise<void>` | Open a URL |
|
|
159
|
+
| `navigation.revealInFinder({path})` | `Promise<void>` | Show file path in Finder |
|
|
160
|
+
| `navigation.openFilePath({path})` | `Promise<void>` | Open a file by path with default app |
|
|
161
|
+
| `settings.get({key})` | `Promise<{value: string \| null}>` | Read a single plugin setting |
|
|
162
|
+
| `settings.getAll()` | `Promise<{settings: Record<string, string>}>` | Read all plugin settings |
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## DOM utilities (`@clipbus/plugin-sdk/dom`)
|
|
167
|
+
|
|
168
|
+
Optional sub-package for WebView plugins that need DOM manipulation utilities.
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
import { patchConsole, patchTextInputState, autoFit } from '@clipbus/plugin-sdk/dom';
|
|
172
|
+
|
|
173
|
+
// Patch console and text input state (call once at module load)
|
|
174
|
+
patchConsole();
|
|
175
|
+
patchTextInputState();
|
|
176
|
+
|
|
177
|
+
// Auto-fit WebView height with ResizeObserver
|
|
178
|
+
const dispose = await autoFit({ min: 120, max: 480, target: containerEl });
|
|
179
|
+
dispose(); // stop monitoring
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## UI entry (`@clipbus/plugin-sdk/ui`)
|
|
185
|
+
|
|
186
|
+
Used in WebView plugin code (ES modules, Vite build). Exposes a single `clipbus` object.
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
import { clipbus } from '@clipbus/plugin-sdk/ui';
|
|
190
|
+
|
|
191
|
+
// No async ready() call needed — subscribers can be registered immediately.
|
|
192
|
+
const item = clipbus.item.current(); // may be undefined if bootstrap hasn't arrived yet
|
|
193
|
+
clipbus.item.on(next => { /* always fires once host pushes bootstrap */ });
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### No `clipbus.ready()`
|
|
197
|
+
|
|
198
|
+
The SDK does **not** export `clipbus.ready()`. Observers (`clipbus.<domain>.on(fn)`) are context-neutral: register them at module load and the host's bootstrap push will trigger them. Use `.current()` for a synchronous snapshot; defend against `undefined` with `?.` / `??` / early-return.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### `clipbus.item` — Topic\<PluginClipboardItem\>
|
|
203
|
+
|
|
204
|
+
| Member | Shape | Description |
|
|
205
|
+
|---|---|---|
|
|
206
|
+
| `clipbus.item.current()` | `() → PluginClipboardItem \| undefined` | Read current item snapshot (may be `undefined` before bootstrap arrives) |
|
|
207
|
+
| `clipbus.item.on(fn)` | `(fn) → Unsubscribe` | Subscribe to item changes |
|
|
208
|
+
| `clipbus.item.readAttachment({attachmentType, attachmentKey})` | `Verb → {payloadJson?: string}` | Read an attachment payload for the current item |
|
|
209
|
+
|
|
210
|
+
#### `clipbus.item.attachment` — OptionalTopic\<PluginAttachmentPayload\> (data layer; attachment context)
|
|
211
|
+
|
|
212
|
+
| Member | Shape | Description |
|
|
213
|
+
|---|---|---|
|
|
214
|
+
| `clipbus.item.attachment.current()` | `() → PluginAttachmentPayload \| undefined` | Current attachment payload |
|
|
215
|
+
| `clipbus.item.attachment.on(fn)` | `(fn) → Unsubscribe` | Subscribe to payload changes |
|
|
216
|
+
|
|
217
|
+
#### `clipbus.attachmentRenderer` — renderer operations (attachment context)
|
|
218
|
+
|
|
219
|
+
| Member | Shape | Description |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| `clipbus.attachmentRenderer.setButtons({buttons})` | `Verb` | Replace native renderer button list (whole-list replace; first call overrides resolveAttachment seed). Strict wire shape — pass `{buttons: [...]}`. |
|
|
222
|
+
| `clipbus.attachmentRenderer.onHostInvoke(fn)` | `Stream` | Subscribe to host-dispatched button clicks; callback receives `{ buttonID }` |
|
|
223
|
+
|
|
224
|
+
### `clipbus.theme` — Topic\<PluginThemeTokenSnapshot\>
|
|
225
|
+
|
|
226
|
+
| Member | Shape | Description |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| `clipbus.theme.current()` | `() → PluginThemeTokenSnapshot \| undefined` | Read current theme tokens. The host injects `__CLIPBUS_PLUGIN_THEME__` at WebView startup, so this is synchronously available in practice. |
|
|
229
|
+
| `clipbus.theme.on(fn)` | `(fn) → Unsubscribe` | Subscribe to theme updates via the `clipbus-plugin-theme` host event |
|
|
230
|
+
|
|
231
|
+
There is **no** `clipbus.theme.refresh()` or `clipbus.theme.getThemeSnapshot()` — the unified topic replaces both.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
### `clipbus.asset` — local image URLs for `<img>` (any context)
|
|
236
|
+
|
|
237
|
+
Returns opaque `clipbus-asset://` URLs so a WebView can render local images in `<img>` without ever seeing a real file path (the WebView sandbox blocks `file://`). The host resolves the token internally; tokens are session-scoped and invalidated when the owner is released.
|
|
238
|
+
|
|
239
|
+
| Member | Shape | Description |
|
|
240
|
+
|---|---|---|
|
|
241
|
+
| `clipbus.asset.currentItemImageUrl()` | `Verb → {url?: string}` | URL of the current item's own image. `url` is absent when the item is not an image. |
|
|
242
|
+
| `clipbus.asset.pathReferenceImageUrl({index})` | `Verb → {url?: string}` | URL of the current item's `path_reference` entry at `index`. `url` is absent when out of range or the entry is not an image. |
|
|
243
|
+
|
|
244
|
+
Arbitrary / generated images are minted only on the runtime side via `ctx.host.asset.registerImage({path})` (see above), so a WebView XSS cannot turn an arbitrary file into a renderable URL.
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
import { clipbus } from '@clipbus/plugin-sdk/ui';
|
|
248
|
+
const { url } = await clipbus.asset.currentItemImageUrl();
|
|
249
|
+
if (url) imgEl.src = url; // url is undefined for non-image items
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
### `clipbus.runtime`
|
|
255
|
+
|
|
256
|
+
| Member | Shape | Description |
|
|
257
|
+
|---|---|---|
|
|
258
|
+
| `clipbus.runtime.invoke<TResp>({key, payload, timeoutMs?})` | `Verb → TResp` | Call a handler registered in the plugin's own Node runtime via `messageHandlers`. Strict wire shape — single object parameter. Default timeout: 30 s. Returns the handler's value directly (the SDK auto-unwraps the IPC envelope). Throws on handler error; `err.name` and `err.data` are preserved across the process boundary. |
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
import { clipbus, defineMessage } from '@clipbus/plugin-sdk/ui';
|
|
262
|
+
|
|
263
|
+
// Recommended: type-safe via defineMessage (shared with the runtime side)
|
|
264
|
+
const GenerateImage = defineMessage<{ prompt: string }, { imageTempPath: string }>('generate-image');
|
|
265
|
+
const { imageTempPath } = await GenerateImage.invoke({ prompt: 'a cat' }, { timeoutMs: 60_000 });
|
|
266
|
+
|
|
267
|
+
// Or bare interface — returns TResp directly (no { response } wrapper)
|
|
268
|
+
const { imageTempPath: path2 } = await clipbus.runtime.invoke<{ imageTempPath: string }>({
|
|
269
|
+
key: 'generate-image',
|
|
270
|
+
payload: { prompt: 'a cat' },
|
|
271
|
+
timeoutMs: 60_000,
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
### `clipbus.action` (action context only)
|
|
278
|
+
|
|
279
|
+
Namespace of verbs and sub-topics scoped to the action WebView. `clipbus.action` itself is not a Topic — it's a grouping. The mutation verbs below require the WebView to be in action context (`clipbus.pluginContext.current()?.mode === 'action'`) and reject otherwise with `PluginContextError`.
|
|
280
|
+
|
|
281
|
+
| Member | Shape | Description |
|
|
282
|
+
|---|---|---|
|
|
283
|
+
| `clipbus.action.setButtons({buttons})` | `Verb` | Replace native action button list (whole-list replace; first call overrides `resolveSession` seed). Strict wire shape — pass `{buttons: [...]}`. |
|
|
284
|
+
| `clipbus.action.complete({result, userMessage?})` | `Verb` | Submit draft action result to host; triggers phase transition to `.success` |
|
|
285
|
+
| `clipbus.action.onHostInvoke(fn)` | `Stream` | Subscribe to host-dispatched button clicks; callback receives `{ buttonID }` |
|
|
286
|
+
|
|
287
|
+
#### `clipbus.action.draft` — OptionalTopic\<Record\<string, unknown\>\>
|
|
288
|
+
|
|
289
|
+
Draft is **read-only** from the UI. The host pushes `initialDraft` (returned by `resolveSession`) as the topic value; the UI keeps form state locally and submits the final result via `clipbus.action.complete(...)`.
|
|
290
|
+
|
|
291
|
+
| Member | Shape | Description |
|
|
292
|
+
|---|---|---|
|
|
293
|
+
| `clipbus.action.draft.current()` | `() → Record<string, unknown> \| undefined` | Current draft state pushed by host. Cast to your draft type at the call site. |
|
|
294
|
+
| `clipbus.action.draft.on(fn)` | `(fn) → Unsubscribe` | Subscribe to draft updates (rare — `initialDraft` is usually all the UI consumes). |
|
|
295
|
+
|
|
296
|
+
There is **no** `clipbus.action.draft.update(...)` or `clipbus.action.draft.getDraftSnapshot()`. To run runtime-side logic from the UI mid-edit (allocate image temp path, fetch external data), use `clipbus.runtime.invoke(...)` against your own `messageHandlers`.
|
|
297
|
+
|
|
298
|
+
#### `PluginContextError`
|
|
299
|
+
|
|
300
|
+
Context-bound verbs (e.g. `clipbus.action.complete`, `clipbus.action.setButtons`, `clipbus.attachmentRenderer.setButtons`) reject with `PluginContextError` when called from the wrong WebView pane:
|
|
301
|
+
|
|
302
|
+
```js
|
|
303
|
+
import { clipbus, PluginContextError } from '@clipbus/plugin-sdk/ui';
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
await clipbus.action.complete({ result: { resultKind: 'none' } });
|
|
307
|
+
} catch (e) {
|
|
308
|
+
if ((e as Error).name === 'PluginContextError') {
|
|
309
|
+
// current WebView is not an action context
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
### `clipbus.window`
|
|
317
|
+
|
|
318
|
+
| Member | Shape | Description |
|
|
319
|
+
|---|---|---|
|
|
320
|
+
| `clipbus.window.setHeight({height})` | `Verb` | Report current WebView height (integer pixels). Strict wire shape — pass `{height: number}`, not a bare number. |
|
|
321
|
+
| `clipbus.window.autoFit()` | `Verb` | Tell the host to enter auto-fit mode for this WebView. Bounds come from `manifest.attachmentRenderers[].height` (`"auto"` or `{min, max}`). No options. |
|
|
322
|
+
|
|
323
|
+
For convenience, `@clipbus/plugin-sdk/dom` also exports an `autoFit({min, max, target})` helper that wires up a `ResizeObserver` and calls `clipbus.window.setHeight(...)` on every layout change — see the DOM utilities section above.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
### `clipbus.infoPanel` — open the native info panel (any context)
|
|
328
|
+
|
|
329
|
+
Opens Clipbus's native detail panel from a plugin, rendered from a structured document (rich text / code / QR / key-value fields / tags / actions). Not permission-gated. Usable from both `attachmentRenderer` and `action` WebViews.
|
|
330
|
+
|
|
331
|
+
| Member | Shape | Description |
|
|
332
|
+
|---|---|---|
|
|
333
|
+
| `clipbus.infoPanel.open({document})` | `Verb → {panelID: string}` | Open a panel; returns a `panelID` that keys the streams below. |
|
|
334
|
+
| `clipbus.infoPanel.close({panelID})` | `Verb` | Close a panel **this plugin opened**. No-op for an unknown / not-owned `panelID`. |
|
|
335
|
+
| `clipbus.infoPanel.onAction.on(fn)` | `Stream` | Subscribe to button clicks; callback receives `{ panelID, buttonID }`. |
|
|
336
|
+
| `clipbus.infoPanel.onClose.on(fn)` | `Stream` | Fires once when a panel closes (user-dismissed or `close()`); receives `{ panelID }`. |
|
|
337
|
+
|
|
338
|
+
**`document` shape** — `{ icon?, title, subtitle?, badges?: string[], blocks: Block[], actions: Action[] }`.
|
|
339
|
+
`Block` is a discriminated union on `type`:
|
|
340
|
+
|
|
341
|
+
| `type` | Fields |
|
|
342
|
+
|---|---|
|
|
343
|
+
| `label` | `{ text }` |
|
|
344
|
+
| `text` | `{ text, style: 'body' \| 'mono' \| 'muted' }` |
|
|
345
|
+
| `code` | `{ text, language: 'plain' \| 'json' }` |
|
|
346
|
+
| `qrCode` | `{ value }` |
|
|
347
|
+
| `fields` | `{ fields: { key, value, isMono? }[] }` |
|
|
348
|
+
| `tags` | `{ tags: string[] }` |
|
|
349
|
+
| `divider` | `{}` |
|
|
350
|
+
|
|
351
|
+
`Action` is `{ id, label, isPrimary? }`.
|
|
352
|
+
|
|
353
|
+
**Buttons are pure-notification.** The host runs **no** built-in effect for a plugin button — every click is delivered to `infoPanel.onAction`, and the plugin performs its own side effect (e.g. call `clipbus.clipboard.copyText(...)` / `clipbus.navigation.openUrl(...)` in the handler). There is no host-side `copy` / `openURL` effect and no `link` text style.
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
const { panelID } = await clipbus.infoPanel.open({
|
|
357
|
+
document: {
|
|
358
|
+
title: "Details",
|
|
359
|
+
blocks: [
|
|
360
|
+
{ type: "text", text: "Tap a button below.", style: "body" },
|
|
361
|
+
{ type: "tags", tags: ["demo"] },
|
|
362
|
+
],
|
|
363
|
+
actions: [{ id: "copy", label: "Copy", isPrimary: true }],
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
clipbus.infoPanel.onAction.on(({ buttonID }) => {
|
|
367
|
+
if (buttonID === "copy") clipbus.clipboard.copyText({ text: "…" });
|
|
368
|
+
});
|
|
369
|
+
clipbus.infoPanel.onClose.on(({ panelID }) => { /* clean up */ });
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Shared-resource semantics.** Info panels are a global resource — the host caps the total number of open panels (currently 12, shared across the app and all plugins) and reclaims the oldest non-focused window when the cap is exceeded; a reclaimed panel still fires `onClose`. Events and `close()` are scoped to the **opening** plugin session: `onAction` / `onClose` are delivered only to the plugin that opened the panel, and `close()` only affects that plugin's own panels. When a plugin's WebView is torn down its `onAction` / `onClose` subscriptions stop firing; any panel it left open stays on screen until the user dismisses it or the cap reclaims it.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
### `clipbus.clipboard`
|
|
377
|
+
|
|
378
|
+
| Member | Shape | Description |
|
|
379
|
+
|---|---|---|
|
|
380
|
+
| `clipbus.clipboard.copyText({text})` | `Verb` | Copy text to system clipboard |
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
### `clipbus.navigation`
|
|
385
|
+
|
|
386
|
+
| Member | Shape | Description |
|
|
387
|
+
|---|---|---|
|
|
388
|
+
| `clipbus.navigation.openUrl({url})` | `Verb` | Open a URL |
|
|
389
|
+
| `clipbus.navigation.revealInFinder({path})` | `Verb` | Reveal file path in Finder |
|
|
390
|
+
| `clipbus.navigation.openFilePath({path})` | `Verb` | Open a file by path |
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
### `clipbus.settings`
|
|
395
|
+
|
|
396
|
+
| Member | Shape | Description |
|
|
397
|
+
|---|---|---|
|
|
398
|
+
| `clipbus.settings.get({key})` | `Verb → {value: unknown \| null}` | Read a plugin setting by key |
|
|
399
|
+
| `clipbus.settings.getAll()` | `Verb → {settings: Record<string, unknown>}` | Read all plugin settings |
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
### `clipbus.console` / `clipbus.textInput` / `clipbus.pluginContext`
|
|
404
|
+
|
|
405
|
+
| Member | Shape | Description |
|
|
406
|
+
|---|---|---|
|
|
407
|
+
| `clipbus.console.log({level, message})` | `Verb` | Write a log line to the host log (`level: 'debug' \| 'info' \| 'warn' \| 'error'`) |
|
|
408
|
+
| `clipbus.textInput.stateChanged({isFocused, isComposing})` | `Verb` | Notify host that a text input's focus / IME state changed |
|
|
409
|
+
| `clipbus.pluginContext.current()` | Topic | `{ mode: 'attachmentRenderer' \| 'action', pluginID }` — read which kind of WebView this code is running in |
|
|
410
|
+
| `clipbus.pluginContext.on(fn)` | Topic | Subscribe (rare; context doesn't normally change after bootstrap) |
|
|
411
|
+
|
|
412
|
+
> Strict wire shape: every verb takes a single object parameter (`{text}`, `{url}`, etc.), never a bare value. The single source of truth for every payload / response shape is [API.md](./API.md), generated from `protocol/plugin/src/catalog.ts`.
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Types
|
|
417
|
+
|
|
418
|
+
All public types live in the generated `data.generated.ts` / `capabilityClients.generated.ts` and carry the `Plugin` prefix. The pre-`plugin-api-shrink` aliases without the prefix have been removed.
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
// Data types — re-exported from both /runtime and /ui
|
|
422
|
+
import type {
|
|
423
|
+
PluginClipboardItem,
|
|
424
|
+
PluginAttachmentPayload,
|
|
425
|
+
PluginAttachmentEntry,
|
|
426
|
+
PluginAttachmentRef,
|
|
427
|
+
PluginAttachmentMutationEntry,
|
|
428
|
+
PluginSearchExtensionEntry,
|
|
429
|
+
PluginContentEnvelope, // discriminated union: text | image | path_reference
|
|
430
|
+
PluginPathEntry, // { kind: 'file' | 'folder', path, displayName }
|
|
431
|
+
PluginDetectorArtifact,
|
|
432
|
+
PluginDetectorInput,
|
|
433
|
+
PluginResolveAttachmentInput,
|
|
434
|
+
PluginAttachmentResolveResult,
|
|
435
|
+
PluginResolveActionSessionInput,
|
|
436
|
+
PluginActionResolveResult,
|
|
437
|
+
PluginAutoRunActionInput,
|
|
438
|
+
PluginActionOperationResult,
|
|
439
|
+
PluginActionResult, // discriminated union: text | image | none
|
|
440
|
+
PluginActionButton,
|
|
441
|
+
PluginThemeTokens,
|
|
442
|
+
PluginThemeTokenSnapshot,
|
|
443
|
+
PluginConsoleLogLevel,
|
|
444
|
+
} from '@clipbus/plugin-sdk/runtime';
|
|
445
|
+
|
|
446
|
+
// Handler interfaces — runtime side only
|
|
447
|
+
import type {
|
|
448
|
+
PluginDetectorHandler,
|
|
449
|
+
PluginAttachmentRendererHandler,
|
|
450
|
+
PluginAutoRunActionHandler, // covers both auto-run and draft lifecycles
|
|
451
|
+
} from '@clipbus/plugin-sdk/runtime';
|
|
452
|
+
|
|
453
|
+
// definePlugin types — runtime side only
|
|
454
|
+
import type {
|
|
455
|
+
PluginRegistry,
|
|
456
|
+
MessageHandler,
|
|
457
|
+
MessageHandlerContext,
|
|
458
|
+
} from '@clipbus/plugin-sdk/runtime';
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
For the exhaustive type list, run `cd protocol/plugin && npm run codegen` and read the synced `data.generated.ts` next to this file.
|
|
462
|
+
|
|
463
|
+
## See also
|
|
464
|
+
|
|
465
|
+
- [API.md](./API.md) — autoritative API reference, regenerated from `protocol/plugin/src/catalog.ts`
|
|
466
|
+
- [SPECIFICATION.md](./SPECIFICATION.md) — API shape rules, mirror table, naming conventions, PR checklist
|