@flowsint/plugin-sdk 2.6.1 → 2.6.2

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 (2) hide show
  1. package/README.md +156 -324
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,374 +1,206 @@
1
- # Flowsint enricher plugins (`@flowsint/plugin-sdk`)
1
+ # @flowsint/plugin-sdk
2
2
 
3
- The SDK for Flowsint local enricher plugins: the plugin contract and one
4
- TypeScript interface per Flowsint entity type. Types only.
3
+ Write Flowsint enricher plugins with typed entities. Import `Domain`, `Ip`,
4
+ `Email` and every other Flowsint entity type, plus the plugin contract, so
5
+ a wrong field name is a compile error instead of an empty node in the graph.
6
+ Types only: nothing ships at runtime.
7
+
8
+ ## Install
5
9
 
6
10
  ```sh
7
- npm i -D @flowsint/plugin-sdk@<Flowsint version you target>
11
+ npm i -D @flowsint/plugin-sdk
8
12
  ```
9
13
 
10
- The package version is the Flowsint version. A plugin built against
11
- `@flowsint/plugin-sdk@X.Y.Z` declares `"engines": { "flowsint": "^X.Y.Z" }`:
12
- removing or renaming a spec field only ships in a major release. This
13
- guarantee covers the published types only, not the app's runtime
14
- handling of what a plugin returns.
15
-
16
- A plugin is a directory dropped into Flowsint's plugins folder
17
- (`~/Library/Application Support/Flowsint/plugins/` on macOS,
18
- `%APPDATA%/Flowsint/plugins/` on Windows,
19
- `~/.config/Flowsint/plugins/` on Linux), containing:
20
-
21
- - `package.json` - see shape below.
22
- - `README.md` - **required**. Shown in Settings -> Plugins ("README"
23
- button, rendered as markdown). Cover: what the dependency is, how to
24
- install it, and your inputs/outputs contract. The section below
25
- shows the layout Flowsint's own bundled plugins use.
26
- - Whatever `package.json`'s `"main"` points to. Your bundled entry
27
- file, exporting `run` per the signature below.
28
- - `node_modules/`. Only if your bundler couldn't inline a dependency
29
- (e.g. a native addon).
30
-
31
- Flowsint never runs `npm install` for you. Bundle your dependencies
32
- (esbuild, tsup, whatever) before distributing - your plugin must work
33
- by `require()` alone, on a machine with no package manager.
34
-
35
- ## package.json
14
+ ## Quick start
15
+
16
+ A plugin is a folder with a `package.json`, a `README.md` and one bundled
17
+ CommonJS file. This one resolves a domain to its IP address.
18
+
19
+ `package.json`:
36
20
 
37
21
  ```json
38
22
  {
39
- "name": "my-plugin",
23
+ "name": "my-domain-to-ip",
40
24
  "version": "1.0.0",
41
25
  "main": "dist/index.js",
42
- "author": "you",
43
26
  "engines": { "flowsint": "^2.6.1" },
44
27
  "flowsint": {
45
28
  "category": "Domain",
46
- "description": "What this does, one sentence.",
29
+ "source": "dns",
47
30
  "inputs": { "type": "domain", "properties": [{ "name": "domain", "type": "string" }] },
48
- "outputs": { "type": "ip", "properties": [{ "name": "address", "type": "string" }] },
49
- "icon": "Globe",
50
- "installUrl": "https://example.com/install-docs",
51
- "params": [
52
- {
53
- "name": "LIMIT",
54
- "type": "string",
55
- "description": "Max results",
56
- "default": "50",
57
- "required": false
58
- },
59
- {
60
- "name": "API_KEY",
61
- "type": "vaultSecret",
62
- "description": "Your API key",
63
- "default": "",
64
- "required": true
65
- }
66
- ]
31
+ "outputs": { "type": "ip", "properties": [{ "name": "address", "type": "string" }] }
67
32
  },
68
- "dependencies": {}
33
+ "scripts": {
34
+ "build": "esbuild src/index.ts --bundle --platform=node --format=cjs --target=node20 --outfile=dist/index.js"
35
+ }
69
36
  }
70
37
  ```
71
38
 
72
- - `name` is your plugin's id - must be unique across every installed
73
- plugin. Two plugins with the same `name`: the one Flowsint finds
74
- first (alphabetical by directory name) loads, the other is skipped
75
- with an error visible in Settings → Plugins.
76
- - `engines.flowsint` is a normal semver range, checked against the
77
- running app version. Omit it to skip the check (not recommended).
78
- - `flowsint.category` and `flowsint.inputs` are required. Everything
79
- else in the `flowsint` key is optional.
80
- - `flowsint.icon` is a [lucide-react](https://lucide.dev/icons/) icon
81
- name.
82
- - `flowsint.installUrl` - link to your dependency's install docs, shown
83
- as a clickable link in Settings → Plugins. Omit it if your plugin has
84
- no external dependency (pure Node/OS APIs only). This is static
85
- metadata, unrelated to `init()`'s `fix` message below - see "Checking
86
- a dependency."
87
- - `flowsint.params` - optional. An array of
88
- `{ name, type, description, default, required }` entries, shown
89
- in the launch settings drawer before your plugin runs. A
90
- `type: "vaultSecret"` entry is resolved against Flowsint's local
91
- vault (the same store backing the chat feature's provider keys)
92
- before your `run()` is called - you receive the decrypted secret
93
- as a plain string in `params[name]` (or `null` if it's optional and
94
- the user never set one), never a key id or reference.
95
- `type: "select"` renders a dropdown of its
96
- `options: [{ "label": "...", "value": "..." }]` (malformed entries
97
- are dropped) and passes the picked `value`. `type: "date"` renders a
98
- calendar and passes a `Date` at midnight UTC of the picked day
99
- (`undefined` if left empty). `type: "number"` renders a numeric
100
- input and passes a `number` (`undefined` if left empty).
101
- `type: "boolean"` renders a switch and passes a `boolean` (`false`
102
- if never touched). `type: "list"` renders a tags input and passes a
103
- `string[]` (`[]` if empty). An invalid value fails the run before
104
- `run()` is called. Any other `type` is passed through as the raw string the
105
- user typed.
106
-
107
- ## `main`'s export
108
-
109
- Two things your entry file can export - `run` is required, `init` is
110
- optional, nothing else is read. The contract lives in
111
- this package's `plugin.d.ts`, the one definition the app's plugin
112
- runner also imports. Shown here without
113
- its doc comments:
39
+ `src/index.ts`:
114
40
 
115
41
  ```ts
116
- interface EnricherEntityInput {
117
- type: string
118
- properties: Record<string, unknown>
119
- }
120
-
121
- interface LocalEnricherNode {
122
- id: string
123
- nodeType: string
124
- nodeProperties: Record<string, unknown> // typed per nodeType, see below
125
- nodeLabel?: string // falls back to `nodeType`
126
- nodeSize?: number // falls back to the app's default size
127
- sources?: string[] // overrides the finding's `source`
128
- }
129
-
130
- interface LocalEnricherEdge {
131
- id: string
132
- source: string
133
- target: string
134
- label: string
135
- sources?: string[]
136
- }
137
-
138
- interface LocalEnricherEvent {
139
- timestamp: number
140
- endTimestamp?: number | null
141
- title: string
142
- description?: string | null
143
- type?: string
144
- confidence?: number | null
145
- nodeIds?: string[] // ids of nodes in this same chunk
146
- tags?: string[]
147
- properties?: Record<string, unknown>
148
- }
149
-
150
- interface LocalEnricherFinding {
151
- nodes: LocalEnricherNode[]
152
- edges: LocalEnricherEdge[]
153
- events?: LocalEnricherEvent[]
42
+ import { randomUUID } from 'node:crypto'
43
+ import { lookup } from 'node:dns/promises'
44
+ import type { Domain, Ip, LocalEnricherRun } from '@flowsint/plugin-sdk'
45
+
46
+ export const run: LocalEnricherRun = async function* (entities, logger) {
47
+ for (const entity of entities) {
48
+ if (entity.type !== 'domain') continue
49
+ const domain: Domain = { domain: String(entity.properties.domain) }
50
+ const ip: Ip = { address: (await lookup(domain.domain)).address }
51
+ logger.info(`${domain.domain} resolves to ${ip.address}`)
52
+ const [domainId, ipId] = [randomUUID(), randomUUID()]
53
+ yield {
54
+ nodes: [
55
+ { id: domainId, nodeType: 'domain', nodeProperties: domain },
56
+ { id: ipId, nodeType: 'ip', nodeProperties: ip }
57
+ ],
58
+ edges: [{ id: randomUUID(), source: domainId, target: ipId, label: 'RESOLVES_TO' }]
59
+ }
60
+ }
154
61
  }
62
+ ```
155
63
 
156
- interface LocalEnricherLogger {
157
- info(message: string): void
158
- warn(message: string): void
159
- error(message: string): void
160
- success(message: string): void
161
- }
64
+ Build it (`npm i -D @flowsint/plugin-sdk esbuild` first), then install it:
162
65
 
163
- // An async generator (`async function* run`): each yielded finding is a
164
- // chunk, merged into the graph as soon as it is yielded.
165
- function run(
166
- entities: EnricherEntityInput[],
167
- logger: LocalEnricherLogger,
168
- params: Record<string, unknown>,
169
- signal: AbortSignal
170
- ): AsyncIterable<LocalEnricherFinding>
66
+ ```sh
67
+ npm run build
171
68
  ```
172
69
 
173
- Yield one chunk per entity: the entity's node, what you found for it
174
- and the edges between them. **An edge's `source` and `target` (and an
175
- event's `nodeIds`) must be nodes of the same chunk.** When results are
176
- shared across entities, yield everything once at the end instead.
177
- Merging a chunk twice is a no-op.
70
+ 1. Copy the folder (`package.json`, `README.md`, `dist/`) into the plugins
71
+ folder: `~/Library/Application Support/Flowsint/plugins/` (macOS),
72
+ `%APPDATA%/Flowsint/plugins/` (Windows), `~/.config/Flowsint/plugins/`
73
+ (Linux).
74
+ 2. In Flowsint, open Settings -> Plugins and click "Reload plugins". Your
75
+ plugin shows `loaded`.
76
+ 3. Add a domain to a sketch and run your plugin on it. Your `logger` calls
77
+ appear in the sketch's logs.
178
78
 
179
- `signal` aborts after one hour: Flowsint stops reading your chunks
180
- and fails the run, keeping the chunks already merged. Pass it to
181
- `spawn(cmd, args, { signal })` and `fetch(url, { signal })`, and call
182
- `signal.throwIfAborted()` in a per-entity `catch` so an abort ends the
183
- run instead of being logged as one failed entity.
79
+ ## Entity types
184
80
 
185
- `entities` are not narrowed per type: an input can be a user-defined
186
- custom type. Check `entity.type` before reading `entity.properties`.
81
+ | Export | What it is |
82
+ | ------------------------------------------ | ---------------------------------------------------------------------- |
83
+ | `Domain`, `Ip`, `Email`, `Individual`, ... | One interface per Flowsint entity type: the shape of `nodeProperties`. |
84
+ | `FlowsintTypeMap` | Type key (`"domain"`, `"ip"`, ...) to its interface. |
85
+ | `FlowsintTypeKey` | Union of every type key. |
187
86
 
188
- `params` mirrors your `flowsint.params` declaration by name - a
189
- plugin with no `params` field in `package.json` still receives an
190
- (empty) object, never `undefined`. See the `flowsint.params` bullet
191
- above for what a `vaultSecret` entry resolves to.
87
+ A node is checked against its `nodeType`:
192
88
 
193
89
  ```ts
194
- type InitResult = { ok: true } | { ok: false; message: string; fix: string }
195
-
196
- // Optional - omit entirely if your plugin has no runtime precondition
197
- // to check. Called once at app boot and again whenever the user clicks
198
- // "Reload plugins" in Settings → Plugins - never on a timer, never
199
- // before a run. A missing/failing dependency keeps your plugin out of
200
- // the enricher picker until init() resolves { ok: true }.
201
- function init(): Promise<InitResult>
90
+ // Compile error: Ip has no `ip` field, its primary field is `address`.
91
+ const node: LocalEnricherNode = { id: 'i', nodeType: 'ip', nodeProperties: { ip: '8.8.8.8' } }
202
92
  ```
203
93
 
204
- `logger` calls show up live in the sketch's Logs panel; anything you
205
- `throw` surfaces as a failed run there too (chunks already yielded stay).
94
+ ## Plugin contract
206
95
 
207
- ## Typing your plugin with `@flowsint/plugin-sdk`
96
+ Your `main` file exports `run` (required) and `init` (optional). Nothing
97
+ else is read.
208
98
 
209
- Import the contract instead of copying it. Types only: use
210
- `import type`, your bundler erases it and nothing ships at runtime.
99
+ | Export | Signature | Called |
100
+ | ------ | ----------------------------------------------------------------------------------- | --------------------------------------------------------- |
101
+ | `run` | `async function* (entities, logger, params, signal)`, yields `LocalEnricherFinding` | Each time a user runs the plugin. |
102
+ | `init` | `() => Promise<InitResult>` | At app start and on "Reload plugins". Never before a run. |
211
103
 
212
- ```ts
213
- import type {
214
- EnricherEntityInput,
215
- LocalEnricherFinding,
216
- LocalEnricherLogger,
217
- LocalEnricherNode
218
- } from '@flowsint/plugin-sdk'
104
+ ### `run` arguments
219
105
 
220
- // Compile error: Ip has no `ip` field, its primary field is `address`.
221
- const node: LocalEnricherNode = { id, nodeType: 'ip', nodeProperties: { ip: '8.8.8.8' } }
222
- ```
106
+ | Argument | Type | Content |
107
+ | ---------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------ |
108
+ | `entities` | `EnricherEntityInput[]` | `{ type, properties }` per selected entity. Not narrowed: check `type` first, an input can be a user-defined type. |
109
+ | `logger` | `LocalEnricherLogger` | `info`, `warn`, `error`, `success`, shown live in the sketch's logs. |
110
+ | `params` | `Record<string, unknown>` | Values of your `flowsint.params`, by name. `{}` when you declare none. |
111
+ | `signal` | `AbortSignal` | Aborts when the user stops the run or after 6 hours. Pass it to `fetch` and `spawn`. |
223
112
 
224
- `LocalEnricherNode` is discriminated by `nodeType`: `nodeProperties`
225
- is checked against that type's spec (the interfaces are generated from
226
- Flowsint's own type specs, so they never drift from them). Narrow on
227
- `nodeType` before reading properties
228
- (`nodes.filter((n) => n.nodeType === 'dnsrecord')`).
229
-
230
- `LocalEnricherRun` types `run` as a whole (`export const run:
231
- LocalEnricherRun = async function* (entities, logger, params, signal) {...}`).
232
- `PluginPackageJson` is the shape of your `package.json` and
233
- `LocalEnricherPlugin` the module the host loads from `main`.
234
-
235
- ## Validating params before you spawn anything
236
-
237
- If your plugin shells out to a CLI (most do), the params you declared
238
- in `flowsint.params` will usually become argv values for that
239
- `spawn()` call. Calling `spawn(cmd, args)` with an argv array, never
240
- with `shell: true` and never by building a single command string
241
- yourself, already rules out classic shell-metacharacter injection
242
- (`;`, `|`, `` ` ``, `$()`) by construction: no shell ever parses the
243
- strings you pass, the OS execs your binary directly with each array
244
- entry as one literal argv token. Do not build commands by
245
- concatenating strings into `exec()`; use `spawn()` with an argv array
246
- every time.
247
-
248
- That guarantee alone is not a reason to pass a param straight through
249
- unchecked, though. A malformed value can still confuse your CLI's own
250
- argument parser (a stray value swallowed as the wrong flag's argument,
251
- a numeric flag rejecting a non-numeric string with a cryptic error) or
252
- trigger something your plugin didn't intend (a flag with a very
253
- different meaning than the one the param name suggests). Validate each
254
- param before using it, and fail loud - `throw` before calling `spawn`
255
- at all, not after - so the whole run aborts with a clear error instead
256
- of silently doing the wrong thing:
113
+ ### `LocalEnricherFinding` (one yielded chunk)
257
114
 
258
- ```ts
259
- const INT_RE = /^\d+$/
260
- const NAME_LIST_RE = /^[A-Za-z0-9_-]+(,[A-Za-z0-9_-]+)*$/
261
-
262
- function requireInt(name: string, v: unknown): string | undefined {
263
- if (typeof v !== 'string' || v.trim() === '') return undefined
264
- const s = v.trim()
265
- if (!INT_RE.test(s))
266
- throw new Error(`Invalid ${name}: expected a non-negative integer, got "${s}"`)
267
- return s
268
- }
115
+ | Field | Type | Rule |
116
+ | -------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
117
+ | `nodes` | `LocalEnricherNode[]` | `{ id, nodeType, nodeProperties, nodeLabel?, nodeSize?, sources? }`. Use a fresh `randomUUID()` per node. |
118
+ | `edges` | `LocalEnricherEdge[]` | `{ id, source, target, label, sources? }`. `source` and `target` are node ids **of the same chunk**. |
119
+ | `events` | `LocalEnricherEvent[]` (optional) | Timeline entries: `{ timestamp, title, endTimestamp?, description?, type?, confidence?, nodeIds?, tags?, properties? }`. |
269
120
 
270
- function requireList(name: string, v: unknown, pattern: RegExp): string | undefined {
271
- if (typeof v !== 'string' || v.trim() === '') return undefined
272
- const s = v.trim()
273
- if (!pattern.test(s))
274
- throw new Error(`Invalid ${name}: "${s}" contains characters outside the allowed set`)
275
- return s
276
- }
121
+ Each chunk is merged into the graph as soon as it is yielded. Yield one
122
+ chunk per entity. A `throw` fails the run; chunks already yielded stay.
277
123
 
278
- // usage: requireList('SOURCES', params.SOURCES, NAME_LIST_RE)
279
- ```
124
+ ### `InitResult`
280
125
 
281
- A URL-shaped param (a proxy, a webhook) is simplest to validate with
282
- `new URL(value)` - it throws on anything that isn't a real URL, which
283
- is exactly the check you want, and it's already in Node's stdlib.
284
-
285
- Build the whole argv array **once, before your entity loop**, not
286
- inside the per-entity `try`/`catch` most plugins use around their
287
- `spawn()` call. If a param is invalid, that's a configuration
288
- mistake, not a search failure for one entity - it should abort the
289
- whole run with a thrown error, not get silently caught and logged as
290
- a per-entity warning while the rest of the batch runs on stale/wrong
291
- settings.
292
-
293
- Logging the exact command you're about to run (`logger.info(...)`,
294
- built from the same array you pass to `spawn`, never a re-derived
295
- string) costs one line and pays for itself the first time a param
296
- doesn't do what someone expected - the sketch's Logs panel then shows
297
- precisely what ran, not just "started" or "failed."
298
-
299
- Flowsint's own bundled plugins (seeded into your plugins folder by
300
- the app) all
301
- follow this pattern - `domain-to-dns-records`, `ip-to-ports`, and
302
- `username-to-social-accounts` in particular are worth reading if
303
- you want a fuller example with several param types (integers,
304
- comma-separated allowlisted lists, an exact-match set, a boolean
305
- flag) in one file.
306
-
307
- ## Checking a dependency (optional `init`)
308
-
309
- If your plugin shells out to an external tool - a CLI, a binary that
310
- must already be on the user's `PATH` - check for it in `init()` instead
311
- of letting `run()` fail with a bare "spawn ENOENT". There's no shared
312
- helper to import (see "nothing else is read" above) - copy this
313
- pattern into your own `src/index.ts`:
126
+ | Value | Effect |
127
+ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
128
+ | `{ ok: true }` | Plugin is available. |
129
+ | `{ ok: false, message, fix }` | Plugin is hidden from the enricher list. Settings -> Plugins shows `message` (what is wrong) and `fix` (plain text instruction). |
314
130
 
315
131
  ```ts
316
132
  import { spawn } from 'node:child_process'
133
+ import type { InitResult } from '@flowsint/plugin-sdk'
317
134
 
318
- async function checkBinaryOnPath(bin: string, fix: string): Promise<InitResult> {
135
+ export function init(): Promise<InitResult> {
319
136
  return new Promise((resolve) => {
320
- const probe = spawn(process.platform === 'win32' ? 'where' : 'which', [bin])
321
- probe.on('close', (code) =>
322
- resolve(code === 0 ? { ok: true } : { ok: false, message: `${bin} not found on PATH`, fix })
323
- )
324
- probe.on('error', () => resolve({ ok: false, message: `${bin} not found on PATH`, fix }))
137
+ const fail = {
138
+ ok: false,
139
+ message: 'subfinder not found on PATH',
140
+ fix: 'brew install subfinder'
141
+ } as const
142
+ const probe = spawn(process.platform === 'win32' ? 'where' : 'which', ['subfinder'])
143
+ probe.on('close', (code) => resolve(code === 0 ? { ok: true } : fail))
144
+ probe.on('error', () => resolve(fail))
325
145
  })
326
146
  }
327
-
328
- export async function init(): Promise<InitResult> {
329
- return checkBinaryOnPath(
330
- 'subfinder',
331
- "install with `brew install subfinder` or see the plugin's README"
332
- )
333
- }
334
147
  ```
335
148
 
336
- `message` is what's wrong, shown next to your plugin in Settings →
337
- Plugins. `fix` is free text rendered as-is, no link-parsing - a short
338
- human instruction, not necessarily a URL. The clickable link in
339
- Settings → Plugins comes from `flowsint.installUrl` in `package.json`
340
- instead (see above) - a separate, static piece of metadata that has
341
- nothing to do with `init()`.
342
-
343
- ## Install / uninstall
344
-
345
- Develop wherever you like. The plugins directory above is an install
346
- target, not a workspace. Copy your plugin's folder into it, then
347
- either restart Flowsint or click "Reload plugins" in Settings ->
348
- Plugins. Both re-scan the plugins directory and re-run every plugin's
349
- `init()` check. To remove a plugin, delete its folder and do the same.
350
- There is no enable/disable toggle in v1. Settings -> Plugins is
351
- read-only, showing what's loaded, what's degraded and why, and what's
352
- failed to load entirely.
353
-
354
- Symlinking your dev folder into the plugins directory (instead of
355
- copying on every change) saves the copy step - rebuild, then just
356
- click "Reload plugins".
357
-
358
- ## Testing your plugin
359
-
360
- No test harness ships in the app. "does it work" is checked by
361
- running it for real:
362
-
363
- 1. Settings → Plugins - your plugin should show `loaded`. `degraded`
364
- or `failed` shows the `init()` message/fix or load error right
365
- there; fix that before anything else.
366
- 2. Open a sketch, add an entity matching your `flowsint.inputs.type`.
367
- 3. Pick your plugin from that entity's enricher list and run it.
368
- 4. Bottom panel → "terminal" tab (`Cmd+Shift+T` to toggle) shows your
369
- `logger.info/warn/error/success` calls live, plus anything your
370
- `run`/`init` throws.
371
- 5. Check the sketch's graph for the `nodes`/`edges` you returned.
372
-
373
- Changed the code? Rebuild, click "Reload plugins" in Settings →
374
- Plugins, re-run step 3 - no restart needed.
149
+ ## `package.json` manifest
150
+
151
+ Typed by `PluginPackageJson`. Flowsint reads it without running your code.
152
+
153
+ | Field | Required | Meaning |
154
+ | ---------------------- | -------- | ------------------------------------------------------------------------------- |
155
+ | `name` | yes | Plugin id, unique across installed plugins. |
156
+ | `main` | yes | Bundled CommonJS entry. |
157
+ | `engines.flowsint` | no | Flowsint versions supported, e.g. `^2.6.1`. Skipped when absent. |
158
+ | `flowsint.category` | yes | Group in the enricher list (`Domain`, `Ip`, ...). |
159
+ | `flowsint.source` | yes | Provider slug stamped on every result (`dns`, `whois`): lowercase, digits, `_`. |
160
+ | `flowsint.inputs` | yes | `{ type, properties }`: the entity type the plugin accepts. |
161
+ | `flowsint.outputs` | no | `{ type, properties }`: the main entity type it produces. |
162
+ | `flowsint.description` | no | One sentence shown in the app. |
163
+ | `flowsint.icon` | no | A [lucide](https://lucide.dev/icons/) icon name. |
164
+ | `flowsint.installUrl` | no | Install docs of an external dependency, linked in Settings -> Plugins. |
165
+ | `flowsint.params` | no | Launch parameters, see below. |
166
+
167
+ ### `flowsint.params`
168
+
169
+ Each entry is `{ name, type, description, default, required, options? }`
170
+ (`EnricherParamSchemaItem`). The user fills them in before the run.
171
+
172
+ | `type` | Form control | Value in `params[name]` |
173
+ | --------------- | ----------------------------------------- | ---------------------------------------------------- |
174
+ | `string`, `url` | Text input | `string` |
175
+ | `number` | Numeric input | `number`, `undefined` when empty |
176
+ | `boolean` | Switch | `boolean`, `false` when untouched |
177
+ | `date` | Calendar | `Date` at midnight UTC, `undefined` when empty |
178
+ | `list` | Tags input | `string[]`, `[]` when empty |
179
+ | `select` | Dropdown of `options: [{ label, value }]` | The picked `value` |
180
+ | `vaultSecret` | Key from the local vault | The decrypted secret, `null` when optional and unset |
181
+
182
+ An invalid value fails the run before `run` is called.
183
+
184
+ ## Good practice
185
+
186
+ - **Bundle everything.** Flowsint never runs `npm install`: your plugin must
187
+ work by `require()` alone. Ship `node_modules/` only for a native addon
188
+ the bundler cannot inline.
189
+ - **Write a `README.md`.** It is required, and shown in Settings -> Plugins.
190
+ Say what the plugin needs and what it returns.
191
+ - **Spawn with an argv array.** `spawn(cmd, args)`, never `shell: true` or a
192
+ command string: no shell, no injection.
193
+ - **Validate params before the entity loop.** A bad param should fail the
194
+ whole run with a clear `throw`, not one entity at a time.
195
+ - **Log what you run.** `logger.info` the exact argv: the logs then show
196
+ what happened, not just that it failed.
197
+ - **Develop with a symlink.** Link your folder into the plugins folder, then
198
+ rebuild and click "Reload plugins". No restart needed.
199
+
200
+ ## Versioning
201
+
202
+ The package version is the Flowsint version. Set `engines.flowsint` to `^`
203
+ the version you installed. Removing or renaming an entity type or field,
204
+ narrowing a field's type or making a field required only happens in a
205
+ major release. This covers the types only, not how the app processes what
206
+ a plugin returns.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowsint/plugin-sdk",
3
- "version": "2.6.1",
3
+ "version": "2.6.2",
4
4
  "description": "SDK for Flowsint local enricher plugins: the plugin contract and the entity property types. Types only: consume with `import type`.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "dextmorgn <contact@flowsint.io>",