@flowsint/plugin-sdk 2.6.2 → 2.6.4

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 +36 -171
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,81 +1,56 @@
1
1
  # @flowsint/plugin-sdk
2
2
 
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.
3
+ ⚠️ This is a package used for later versions of Flowsint (>v2.0). This exposes Flowsint types through a typescript interface. This will later be useful for creating local (desktop version) enrichers, and extend the application.
7
4
 
8
5
  ## Install
9
6
 
10
7
  ```sh
11
- npm i -D @flowsint/plugin-sdk
8
+ # pnpm
9
+ pnpm add @flowsint/plugin-sdk
10
+ # yarn
11
+ yarn add @flowsint/plugin-sdk
12
+ # npm
13
+ npm i @flowsint/plugin-sdk
12
14
  ```
13
15
 
14
16
  ## Quick start
15
17
 
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`:
20
-
21
- ```json
22
- {
23
- "name": "my-domain-to-ip",
24
- "version": "1.0.0",
25
- "main": "dist/index.js",
26
- "engines": { "flowsint": "^2.6.1" },
27
- "flowsint": {
28
- "category": "Domain",
29
- "source": "dns",
30
- "inputs": { "type": "domain", "properties": [{ "name": "domain", "type": "string" }] },
31
- "outputs": { "type": "ip", "properties": [{ "name": "address", "type": "string" }] }
32
- },
33
- "scripts": {
34
- "build": "esbuild src/index.ts --bundle --platform=node --format=cjs --target=node20 --outfile=dist/index.js"
35
- }
36
- }
37
- ```
38
-
39
- `src/index.ts`:
40
-
41
18
  ```ts
42
- import { randomUUID } from 'node:crypto'
43
- import { lookup } from 'node:dns/promises'
44
- import type { Domain, Ip, LocalEnricherRun } from '@flowsint/plugin-sdk'
19
+ import { randomUUID } from "node:crypto";
20
+ import { lookup } from "node:dns/promises";
21
+ import type {
22
+ Domain,
23
+ Ip,
24
+ LocalEnricherRun,
25
+ LocalEnricherEdge,
26
+ } from "@flowsint/plugin-sdk";
45
27
 
46
28
  export const run: LocalEnricherRun = async function* (entities, logger) {
47
29
  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()]
30
+ if (entity.type !== "domain") continue;
31
+ const domain: Domain = { domain: String(entity.properties.domain) };
32
+ const ip: Ip = { address: (await lookup(domain.domain)).address };
33
+ logger.info(`${domain.domain} resolves to ${ip.address}`);
34
+ const [domainId, ipId] = [randomUUID(), randomUUID()];
35
+ const relation: LocalEnricherEdge = {
36
+ id: randomUUID(),
37
+ source: domainId,
38
+ target: ipId,
39
+ label: "RESOLVES_TO",
40
+ };
53
41
  yield {
54
42
  nodes: [
55
- { id: domainId, nodeType: 'domain', nodeProperties: domain },
56
- { id: ipId, nodeType: 'ip', nodeProperties: ip }
43
+ { id: domainId, nodeType: "domain", nodeProperties: domain },
44
+ { id: ipId, nodeType: "ip", nodeProperties: ip },
57
45
  ],
58
- edges: [{ id: randomUUID(), source: domainId, target: ipId, label: 'RESOLVES_TO' }]
59
- }
46
+ edges: [relation],
47
+ };
60
48
  }
61
- }
49
+ };
62
50
  ```
63
51
 
64
52
  Build it (`npm i -D @flowsint/plugin-sdk esbuild` first), then install it:
65
53
 
66
- ```sh
67
- npm run build
68
- ```
69
-
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.
78
-
79
54
  ## Entity types
80
55
 
81
56
  | Export | What it is |
@@ -88,119 +63,9 @@ A node is checked against its `nodeType`:
88
63
 
89
64
  ```ts
90
65
  // 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' } }
66
+ const node: LocalEnricherNode = {
67
+ id: "i",
68
+ nodeType: "ip",
69
+ nodeProperties: { ip: "8.8.8.8" },
70
+ };
92
71
  ```
93
-
94
- ## Plugin contract
95
-
96
- Your `main` file exports `run` (required) and `init` (optional). Nothing
97
- else is read.
98
-
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. |
103
-
104
- ### `run` arguments
105
-
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`. |
112
-
113
- ### `LocalEnricherFinding` (one yielded chunk)
114
-
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? }`. |
120
-
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.
123
-
124
- ### `InitResult`
125
-
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). |
130
-
131
- ```ts
132
- import { spawn } from 'node:child_process'
133
- import type { InitResult } from '@flowsint/plugin-sdk'
134
-
135
- export function init(): Promise<InitResult> {
136
- return new Promise((resolve) => {
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))
145
- })
146
- }
147
- ```
148
-
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.2",
3
+ "version": "2.6.4",
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>",