@vornrun/connector-sdk 0.7.0-beta.1 → 0.7.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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
@@ -198,6 +265,48 @@ trigger and they are replayed through the real dedupe pipeline, so a connector
198
265
  can be checked before anyone has credentials for it; pass `--live` to poll the
199
266
  real source instead.
200
267
 
268
+ `--mock` is the full gate, and the one to run in CI. It answers every HTTP
269
+ request from routes instead of the network, runs each action on its own
270
+ declared arguments, and asks the questions `pack` asks about the package —
271
+ install-time scripts, and anything that would still need a registry at launch:
272
+
273
+ ```console
274
+ $ npx vorn-connector check ./dist/index.js --mock --receipt verified.json
275
+ Verified manifest, auth, secrets, actions, dedupe, no-lifecycle-scripts, keywords, no-runtime-deps, mock — wrote verified.json
276
+ ```
277
+
278
+ `--receipt <file>` writes what was verified, for a catalog to carry:
279
+
280
+ ```json
281
+ { "schema": 1, "version": "1.2.0", "checkedAt": "…", "checks": ["manifest", "…"] }
282
+ ```
283
+
284
+ A check that could not run is left out rather than listed as passed, and a
285
+ connector with any error gets no receipt at all.
286
+
287
+ `--live` additionally asks `preflight` whether the connector can sign in, then
288
+ runs each action that declared `idempotent: true`. Actions that did not are
289
+ never called: a smoke test must leave nothing behind.
290
+
291
+ In a unit test the same stub is available directly, so an author can assert on
292
+ what their connector sent:
293
+
294
+ ```ts
295
+ const { result, calls } = await harness.withMockHttp(
296
+ [{ url: '/api/messages', method: 'POST', body: { id: 'm-1' } }],
297
+ () => harness.execute('post', { text: 'hi' })
298
+ )
299
+ expect(calls[0].url).toBe('https://acme.test/api/messages')
300
+ ```
301
+
302
+ A request no route matches is refused rather than served, so a test says which
303
+ call escaped instead of quietly reaching a real service.
304
+
305
+ The stub replaces `fetch`, and only `fetch`. A connector that shells out to a
306
+ CLI or opens its own socket is not intercepted by it, so `--mock` reports any
307
+ action the stub never heard from as `mock-not-observed` and leaves `mock` out
308
+ of the receipt rather than vouching for a run it did not see.
309
+
201
310
  ```ts
202
311
  {
203
312
  type: 'newTicket',
@@ -257,6 +366,38 @@ hand:
257
366
  npx vorn-connector setup ./dist/index.js
258
367
  ```
259
368
 
369
+ ### Where a config field is read from
370
+
371
+ A field is read from the environment variable it names in `env`, or from its
372
+ key in CONSTANT_CASE when it names none — `apiToken` becomes `API_TOKEN`. The
373
+ same rule decides what `--live` reads and what the host must set when it hands
374
+ a connector its credentials, so it is exported rather than kept private:
375
+
376
+ ```ts
377
+ import { envNameFor } from '@vornrun/connector-sdk'
378
+
379
+ envNameFor('apiToken') // API_TOKEN
380
+ envNameFor('apiToken', 'GH_TOKEN') // GH_TOKEN — an explicit env always wins
381
+ ```
382
+
383
+ Anything computing these names on the host side should call this rather than
384
+ re-implement it, or the two will disagree about a field named `oauth2Token`.
385
+
386
+ ## Pack it as a file
387
+
388
+ `vorn-connector pack` builds a single installable file: the manifest plus one
389
+ bundled entry with every dependency inlined.
390
+
391
+ ```bash
392
+ npx vorn-connector pack ./dist/index.js --out ./release
393
+ # → release/acme-1.2.3.vorn.tgz
394
+ ```
395
+
396
+ Packing runs `check` first, then two gates a pack must pass: the source package
397
+ declares no install-time scripts, and nothing was left outside the bundle. A
398
+ pack installs by copying files, so it works with no registry reachable — drop
399
+ it on **Settings → Connectors** and Vorn launches it from disk.
400
+
260
401
  ### Ship an icon
261
402
 
262
403
  Without one, a connector shows the generic MCP glyph and is hard to pick out
@@ -286,16 +427,23 @@ text color instead of fighting the theme.
286
427
  ## CLI
287
428
 
288
429
  ```
430
+ vorn-connector new <id> Scaffold a new connector, ready to build
289
431
  vorn-connector manifest <module> Print the manifest as JSON
290
432
  vorn-connector setup <module> [trigger] Print the Vorn connection settings
291
433
  vorn-connector poll <module> <trigger> Run one poll against the environment
292
434
  vorn-connector check <module> Verify the connector against the contract
435
+ vorn-connector pack <module> Build an installable .vorn.tgz pack
293
436
  vorn-connector serve <module> Serve on stdio (what Vorn runs)
294
437
  ```
295
438
 
439
+ `new` accepts `--out <dir>`, `--name "Display Name"`, and `--repo-conventions`,
440
+ which shapes the package the way the connectors repository expects it (scoped
441
+ name, changelog, compiler and test settings); `pack` accepts `--out <dir>`.
442
+
296
443
  `poll` accepts `--since <iso>` and `--limit <n>`, and reads the connector's
297
444
  declared config from your shell environment — the fastest way to confirm
298
445
  credentials and field mapping before wiring anything up.
299
446
 
300
- `check` runs against declared `sample` data by default and takes `--live` to
301
- poll the real source instead.
447
+ `check` runs against declared `sample` data by default. `--mock` serves its
448
+ HTTP and runs every action, `--live` polls the real source and runs the actions
449
+ that are safe to repeat, and `--receipt <file>` writes down what was verified.