@vornrun/connector-sdk 0.7.0-beta.7 → 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/README.md +285 -2
- package/dist/check-62s2GvcO.d.ts +1137 -0
- package/dist/chunk-ZKHXHE3O.js +3511 -0
- package/dist/cli.d.ts +14 -1
- package/dist/cli.js +105 -12
- package/dist/index.d.ts +176 -412
- package/dist/index.js +98 -34
- package/package.json +4 -2
- package/dist/chunk-457KOZUU.js +0 -773
package/README.md
CHANGED
|
@@ -84,6 +84,73 @@ await serveConnector(connector)
|
|
|
84
84
|
|
|
85
85
|
Publish it like any other package (`"bin": { "acme-connector": "dist/bin.js" }`).
|
|
86
86
|
|
|
87
|
+
`vorn-connector new acme` writes all of the above — package, entry, definition,
|
|
88
|
+
a test that needs no network — already building, checking and packing.
|
|
89
|
+
|
|
90
|
+
## Declare an action instead of writing one
|
|
91
|
+
|
|
92
|
+
Most actions put arguments into a request and keep part of the answer. Say that
|
|
93
|
+
and the SDK does the rest: `{{args.x}}` and `{{config.y}}` are filled in, an
|
|
94
|
+
argument nobody supplied is left out, a failed status is raised with what the
|
|
95
|
+
body said, and `postReceive` reshapes what came back.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
{
|
|
99
|
+
type: 'createIssue',
|
|
100
|
+
label: 'Create issue',
|
|
101
|
+
idempotent: false,
|
|
102
|
+
inputs: [{ key: 'title', label: 'Title', required: true }],
|
|
103
|
+
request: {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
url: '{{config.baseUrl}}/issues',
|
|
106
|
+
headers: { authorization: 'Bearer {{config.apiToken}}' },
|
|
107
|
+
body: { title: '{{args.title}}' }
|
|
108
|
+
},
|
|
109
|
+
// pick · rename · flatten · filter · map, applied left to right.
|
|
110
|
+
postReceive: [{ op: 'pick', keys: ['id', 'html_url'] }]
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Add `paginate` to follow every page rather than the first — `{ kind: 'cursor',
|
|
115
|
+
cursorPath: 'next', param: 'cursor' }`, `{ kind: 'page', param: 'page' }` or
|
|
116
|
+
`{ kind: 'link' }` — and the pages arrive concatenated.
|
|
117
|
+
|
|
118
|
+
Retry, backoff and rate-limit handling are applied for you, to declared
|
|
119
|
+
requests and to `context.fetch` alike. A read is always retried; a write only
|
|
120
|
+
when the action declares `idempotent: true`, because repeating a create makes a
|
|
121
|
+
second one. Prefer `context.fetch` over the global one in a hand-written action.
|
|
122
|
+
|
|
123
|
+
## Offer a field the choices it has
|
|
124
|
+
|
|
125
|
+
A `select` with fixed choices carries them; one whose choices only exist against
|
|
126
|
+
a live connection names a set the connector serves.
|
|
127
|
+
|
|
128
|
+
Choices are **suggestions, not a closed set**. Vorn draws a picker while the
|
|
129
|
+
value is one of them and its template-aware input whenever it is not, because a
|
|
130
|
+
step is entitled to compute the value from an earlier one — so the served tool
|
|
131
|
+
schema keeps the argument a plain string and lists the choices in its
|
|
132
|
+
description. Your action still receives whatever was finally sent, and it is
|
|
133
|
+
the connector's job to refuse a value it cannot use.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
options: { channels: async ({ config, fetch }) => ['general', 'random'] },
|
|
137
|
+
actions: [{
|
|
138
|
+
type: 'post',
|
|
139
|
+
label: 'Post',
|
|
140
|
+
inputs: [{ key: 'channel', label: 'Channel', type: 'select', loadOptions: 'channels' }],
|
|
141
|
+
request: { method: 'POST', url: '{{config.baseUrl}}/post/{{args.channel}}' }
|
|
142
|
+
}]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`loadOptions` is served today but not yet consumed: the SDK registers a
|
|
146
|
+
`vorn_connector_options` tool and answers it, and the manifest carries the set's
|
|
147
|
+
name, but the app does not fetch the list yet — such a field is edited as text
|
|
148
|
+
until it does. Declaring it now is what makes it work then; a field whose
|
|
149
|
+
choices are already known should use `options` instead.
|
|
150
|
+
|
|
151
|
+
A `json` argument arrives parsed. `builderHint` on a field is a note for whoever
|
|
152
|
+
writes the next connector, not for whoever runs this one.
|
|
153
|
+
|
|
87
154
|
## Poll a database instead of an API
|
|
88
155
|
|
|
89
156
|
Nothing about a trigger is HTTP-specific — it just returns items. A SQL pull
|
|
@@ -135,6 +202,25 @@ Two rules make a pull trigger reliable, and the SDK enforces both:
|
|
|
135
202
|
it. Use `>=` when filtering on `since` and sort ascending; returning a few
|
|
136
203
|
items again is free, because the SDK drops anything already delivered.
|
|
137
204
|
|
|
205
|
+
## Act through a signed-in window
|
|
206
|
+
|
|
207
|
+
A service with no API to key can still be reached as the person using it. Declare the `browser` rung with the page to sign in on, the origins the connector may act on, and a check that answers 2xx only while someone is signed in:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
auth: {
|
|
211
|
+
rung: 'browser',
|
|
212
|
+
browser: {
|
|
213
|
+
signInUrl: 'https://example.com/login',
|
|
214
|
+
origins: ['https://example.com', 'https://*.example.com'],
|
|
215
|
+
check: { url: 'https://example.com/api/me', identity: ['name', 'handle'] }
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Vorn opens a window on a browser profile that belongs to one connection, and the person signs in there. The connector's code then gets `ctx.session.fetch` beside `ctx.fetch`. A call through `ctx.session.fetch` runs inside that signed-in window as a same-origin request, so the service sees its own page asking and no cookie reaches the connector. Calls outside `origins` are refused. `ctx.fetch` stays a plain fetch for public reads, such as a feed, which work before anyone signs in. A `browser` connector's declared `request` actions go through the window.
|
|
221
|
+
|
|
222
|
+
`--mock` serves signed-in calls from the same routes as every other call. A `--live` run from a terminal has no window, so it skips them.
|
|
223
|
+
|
|
138
224
|
## Dedupe strategies
|
|
139
225
|
|
|
140
226
|
`dedupe` tells the SDK how to recognize new items, and it then owns the cursor
|
|
@@ -198,6 +284,48 @@ trigger and they are replayed through the real dedupe pipeline, so a connector
|
|
|
198
284
|
can be checked before anyone has credentials for it; pass `--live` to poll the
|
|
199
285
|
real source instead.
|
|
200
286
|
|
|
287
|
+
`--mock` is the full gate, and the one to run in CI. It answers every HTTP
|
|
288
|
+
request from routes instead of the network, runs each action on its own
|
|
289
|
+
declared arguments, and asks the questions `pack` asks about the package —
|
|
290
|
+
install-time scripts, and anything that would still need a registry at launch:
|
|
291
|
+
|
|
292
|
+
```console
|
|
293
|
+
$ npx vorn-connector check ./dist/index.js --mock --receipt verified.json
|
|
294
|
+
Verified manifest, auth, secrets, actions, dedupe, no-lifecycle-scripts, keywords, no-runtime-deps, mock — wrote verified.json
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
`--receipt <file>` writes what was verified, for a catalog to carry:
|
|
298
|
+
|
|
299
|
+
```json
|
|
300
|
+
{ "schema": 1, "version": "1.2.0", "checkedAt": "…", "checks": ["manifest", "…"] }
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
A check that could not run is left out rather than listed as passed, and a
|
|
304
|
+
connector with any error gets no receipt at all.
|
|
305
|
+
|
|
306
|
+
`--live` additionally asks `preflight` whether the connector can sign in, then
|
|
307
|
+
runs each action that declared `idempotent: true`. Actions that did not are
|
|
308
|
+
never called: a smoke test must leave nothing behind.
|
|
309
|
+
|
|
310
|
+
In a unit test the same stub is available directly, so an author can assert on
|
|
311
|
+
what their connector sent:
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
const { result, calls } = await harness.withMockHttp(
|
|
315
|
+
[{ url: '/api/messages', method: 'POST', body: { id: 'm-1' } }],
|
|
316
|
+
() => harness.execute('post', { text: 'hi' })
|
|
317
|
+
)
|
|
318
|
+
expect(calls[0].url).toBe('https://acme.test/api/messages')
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
A request no route matches is refused rather than served, so a test says which
|
|
322
|
+
call escaped instead of quietly reaching a real service.
|
|
323
|
+
|
|
324
|
+
The stub replaces `fetch`, and only `fetch`. A connector that shells out to a
|
|
325
|
+
CLI or opens its own socket is not intercepted by it, so `--mock` reports any
|
|
326
|
+
action the stub never heard from as `mock-not-observed` and leaves `mock` out
|
|
327
|
+
of the receipt rather than vouching for a run it did not see.
|
|
328
|
+
|
|
201
329
|
```ts
|
|
202
330
|
{
|
|
203
331
|
type: 'newTicket',
|
|
@@ -257,6 +385,38 @@ hand:
|
|
|
257
385
|
npx vorn-connector setup ./dist/index.js
|
|
258
386
|
```
|
|
259
387
|
|
|
388
|
+
### Where a config field is read from
|
|
389
|
+
|
|
390
|
+
A field is read from the environment variable it names in `env`, or from its
|
|
391
|
+
key in CONSTANT_CASE when it names none — `apiToken` becomes `API_TOKEN`. The
|
|
392
|
+
same rule decides what `--live` reads and what the host must set when it hands
|
|
393
|
+
a connector its credentials, so it is exported rather than kept private:
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
import { envNameFor } from '@vornrun/connector-sdk'
|
|
397
|
+
|
|
398
|
+
envNameFor('apiToken') // API_TOKEN
|
|
399
|
+
envNameFor('apiToken', 'GH_TOKEN') // GH_TOKEN — an explicit env always wins
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Anything computing these names on the host side should call this rather than
|
|
403
|
+
re-implement it, or the two will disagree about a field named `oauth2Token`.
|
|
404
|
+
|
|
405
|
+
## Pack it as a file
|
|
406
|
+
|
|
407
|
+
`vorn-connector pack` builds a single installable file: the manifest plus one
|
|
408
|
+
bundled entry with every dependency inlined.
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
npx vorn-connector pack ./dist/index.js --out ./release
|
|
412
|
+
# → release/acme-1.2.3.vorn.tgz
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Packing runs `check` first, then two gates a pack must pass: the source package
|
|
416
|
+
declares no install-time scripts, and nothing was left outside the bundle. A
|
|
417
|
+
pack installs by copying files, so it works with no registry reachable — drop
|
|
418
|
+
it on **Settings → Connectors** and Vorn launches it from disk.
|
|
419
|
+
|
|
260
420
|
### Ship an icon
|
|
261
421
|
|
|
262
422
|
Without one, a connector shows the generic MCP glyph and is hard to pick out
|
|
@@ -283,19 +443,142 @@ reads the manifest.
|
|
|
283
443
|
Paths are filled with `currentColor`, so the icon picks up the surrounding
|
|
284
444
|
text color instead of fighting the theme.
|
|
285
445
|
|
|
446
|
+
## Extensions
|
|
447
|
+
|
|
448
|
+
A connector polls a service. An **extension** contributes to a session card
|
|
449
|
+
instead: a footer band under the status bar, a pane beside the terminal, or a
|
|
450
|
+
handler offered when a link in the terminal is clicked. It is the same pack —
|
|
451
|
+
same manifest, same check, same receipt, same catalog — declared with
|
|
452
|
+
`defineExtension` rather than `defineConnector`.
|
|
453
|
+
|
|
454
|
+
```ts
|
|
455
|
+
import { defineExtension } from '@vornrun/connector-sdk'
|
|
456
|
+
|
|
457
|
+
export const connector = defineExtension({
|
|
458
|
+
id: 'review',
|
|
459
|
+
name: 'Review',
|
|
460
|
+
description: 'Reads the session',
|
|
461
|
+
permissions: ['terminal.read'],
|
|
462
|
+
activates: { workspaceContains: ['package.json'] },
|
|
463
|
+
footers: [
|
|
464
|
+
{
|
|
465
|
+
id: 'checks',
|
|
466
|
+
title: 'Checks',
|
|
467
|
+
every: 30,
|
|
468
|
+
async run(context) {
|
|
469
|
+
const output = await context.host.output({ lines: 200 })
|
|
470
|
+
return [{ label: 'tests', value: /FAIL/.test(output) ? 'failing' : 'passing' }]
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
],
|
|
474
|
+
panes: [{ id: 'report', title: 'Report', web: 'web/report/index.html' }]
|
|
475
|
+
})
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
Start one with `vorn-connector new review --extension`.
|
|
479
|
+
|
|
480
|
+
### What it may ask the host for
|
|
481
|
+
|
|
482
|
+
Every method of `context.host` costs one permission, and the manifest has to
|
|
483
|
+
declare it. A call outside what was declared is refused rather than answered —
|
|
484
|
+
by `check` against its stub host, and by Vorn at run time — so what a person
|
|
485
|
+
agreed to when installing is what the extension can reach.
|
|
486
|
+
|
|
487
|
+
| Permission | Host method | What it grants |
|
|
488
|
+
| -------------------- | -------------------- | ------------------------------------ |
|
|
489
|
+
| `git.read` | `diff()`, `status()` | The worktree's diff and status |
|
|
490
|
+
| `terminal.read` | `output()` | The session's recent terminal output |
|
|
491
|
+
| `terminal.selection` | `selection()` | The text selected in the terminal |
|
|
492
|
+
| `terminal.send` | `send(text)` | Typing into the session's terminal |
|
|
493
|
+
| `card.rename` | `rename(name)` | Naming the session card |
|
|
494
|
+
| `agent.usage` | `usage()` | Context and provider allowance |
|
|
495
|
+
|
|
496
|
+
Ask for only what the extension spends: `check` names a permission that was
|
|
497
|
+
declared and never used.
|
|
498
|
+
|
|
499
|
+
### Where it shows
|
|
500
|
+
|
|
501
|
+
`activates` on the extension, and `when` on any one contribution, narrow where
|
|
502
|
+
it appears. Every declared field has to hold, and each is satisfied by any one
|
|
503
|
+
of its values, so an extension is simply absent where it has nothing to say.
|
|
504
|
+
|
|
505
|
+
```ts
|
|
506
|
+
activates: {
|
|
507
|
+
workspaceContains: ['Cargo.toml'], // paths relative to the worktree
|
|
508
|
+
remoteHost: ['github.com'],
|
|
509
|
+
agent: ['claude', 'shell'],
|
|
510
|
+
platform: ['darwin', 'linux']
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### What a pane is drawn from
|
|
515
|
+
|
|
516
|
+
A pane is either a page the pack carries or a program it runs. A page lives
|
|
517
|
+
under `web/` in the package, and the directory it sits in is carried into the
|
|
518
|
+
pack, so its stylesheet and script travel with it. Vorn serves the page and
|
|
519
|
+
answers `bridge/<method>` beside it, on the page's own origin: the page holds no
|
|
520
|
+
token, and Vorn grants the call exactly the permissions the manifest declared,
|
|
521
|
+
knowing from the origin which pane is asking. A program is argv, run in the
|
|
522
|
+
session's worktree and drawn as a terminal.
|
|
523
|
+
|
|
524
|
+
```js
|
|
525
|
+
const response = await fetch('bridge/output', {
|
|
526
|
+
method: 'POST',
|
|
527
|
+
headers: { 'content-type': 'application/json' },
|
|
528
|
+
body: JSON.stringify({ lines: 200 })
|
|
529
|
+
})
|
|
530
|
+
const output = (await response.json()).result
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
```ts
|
|
534
|
+
panes: [
|
|
535
|
+
{ id: 'report', title: 'Report', web: 'web/report/index.html' },
|
|
536
|
+
{ id: 'log', title: 'Log', command: ['./bin/log'], when: { agent: ['shell'] } }
|
|
537
|
+
]
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### What a link handler is offered for
|
|
541
|
+
|
|
542
|
+
A handler names the pattern it matches against clicked text, and one example
|
|
543
|
+
link it is for. The example is what `check` runs the handler on, so a handler is
|
|
544
|
+
proved against a link it will really be offered for rather than a made-up one.
|
|
545
|
+
|
|
546
|
+
```ts
|
|
547
|
+
linkHandlers: [
|
|
548
|
+
{
|
|
549
|
+
id: 'pull-request',
|
|
550
|
+
title: 'Pull request',
|
|
551
|
+
pattern: 'https://github\\.com/[^/]+/[^/]+/pull/\\d+',
|
|
552
|
+
example: 'https://github.com/vorn-run/vorn/pull/1',
|
|
553
|
+
async run(context) {
|
|
554
|
+
await context.host.send(`Look at ${context.url}`)
|
|
555
|
+
return { openPane: 'report' }
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
]
|
|
559
|
+
```
|
|
560
|
+
|
|
286
561
|
## CLI
|
|
287
562
|
|
|
288
563
|
```
|
|
564
|
+
vorn-connector new <id> Scaffold a new connector, ready to build
|
|
289
565
|
vorn-connector manifest <module> Print the manifest as JSON
|
|
290
566
|
vorn-connector setup <module> [trigger] Print the Vorn connection settings
|
|
291
567
|
vorn-connector poll <module> <trigger> Run one poll against the environment
|
|
292
568
|
vorn-connector check <module> Verify the connector against the contract
|
|
569
|
+
vorn-connector pack <module> Build an installable .vorn.tgz pack
|
|
293
570
|
vorn-connector serve <module> Serve on stdio (what Vorn runs)
|
|
294
571
|
```
|
|
295
572
|
|
|
573
|
+
`new` accepts `--out <dir>`, `--name "Display Name"`, `--repo-conventions`,
|
|
574
|
+
which shapes the package the way the connectors repository expects it (scoped
|
|
575
|
+
name, changelog, compiler and test settings), and `--extension`, which
|
|
576
|
+
scaffolds an extension rather than a connector; `pack` accepts `--out <dir>`.
|
|
577
|
+
|
|
296
578
|
`poll` accepts `--since <iso>` and `--limit <n>`, and reads the connector's
|
|
297
579
|
declared config from your shell environment — the fastest way to confirm
|
|
298
580
|
credentials and field mapping before wiring anything up.
|
|
299
581
|
|
|
300
|
-
`check` runs against declared `sample` data by default
|
|
301
|
-
|
|
582
|
+
`check` runs against declared `sample` data by default. `--mock` serves its
|
|
583
|
+
HTTP and runs every action, `--live` polls the real source and runs the actions
|
|
584
|
+
that are safe to repeat, and `--receipt <file>` writes down what was verified.
|