node-red-contrib-vectorprime 0.1.9 → 0.1.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
@@ -1,68 +1,45 @@
1
1
  # node-red-contrib-vectorprime
2
2
 
3
- Official VectorPrime Decision Kernel node for Node-RED.
3
+ **VectorPrime Rank** — automatically rank tasks, alerts, or decisions using the VectorPrime Decision Kernel.
4
4
 
5
- This node sends a decision request to VectorPrime and returns a ranked result.
5
+ This node helps you pick the **best next action** when you have multiple choices and need to choose the optimal one fast.
6
6
 
7
- ---
8
-
9
- ## What this node does
10
-
11
- - **Input:** `msg.payload` (your decision request JSON)
12
- - **Output:** `msg.payload` (VectorPrime response JSON)
7
+ ✅ Prioritize tasks
8
+ ✅ Rank alerts by urgency
9
+ Choose the best option from multiple choices
10
+ ✅ Turn “too many choices” into a single clear answer
11
+ Works inside Node-RED flows with 1 click
13
12
 
14
13
  ---
15
14
 
16
- ## Quick Start
15
+ ## What it does (in 1 sentence)
17
16
 
18
- ### Endpoint
19
-
20
- `/v1/kernel/rank`
17
+ **Send a list of options → get them ranked best-to-worst with a recommended top choice.**
21
18
 
22
19
  ---
23
20
 
24
- ## How to use in Node-RED
25
-
26
- ### 1) Drag these nodes into your flow:
21
+ ## Why developers install this
27
22
 
28
- - **Inject**
29
- - **VectorPrime**
30
- - **Debug**
23
+ Most automation flows break down when you have **multiple possible actions** and don’t know which one to run.
31
24
 
32
- ### 2) Connect them like this:
25
+ VectorPrime solves that by turning this:
33
26
 
34
- Inject VectorPrime Debug
27
+ - “Which alert should fire first?”
28
+ - “Which ticket should I do next?”
29
+ - “Which customer should get priority?”
30
+ - “Which action gives the best outcome with least risk?”
31
+ - “Which fix should I deploy first?”
35
32
 
36
- ### 3) Configure the VectorPrime node
33
+ Into:
37
34
 
38
- Double-click the **VectorPrime** node, then:
39
-
40
- ✅ Paste your VectorPrime API key into the node field
41
- (or use the **Get Free Key** button if enabled)
42
-
43
- ### 4) Run it
44
-
45
- 1. Click **Deploy**
46
- 2. Press the **Inject** button
47
- 3. View output in the **Debug** sidebar
35
+ **One ranked result** (best option on top)
48
36
 
49
37
  ---
50
38
 
51
- ## Example Inject Payload
52
-
53
- ### IMPORTANT:
54
- Your Inject node must send a JSON object inside `msg.payload`.
39
+ ## Quick Start (60 seconds)
55
40
 
56
- Set your Inject node payload type to **JSON** and paste this:
41
+ ### 1) Install the node
42
+ In your Node-RED user directory (usually `C:\Users\mxz\.node-red`):
57
43
 
58
- ```json
59
- {
60
- "decision_id": "node-red-demo-001",
61
- "prompt": "Pick one.",
62
- "options": [
63
- { "id": "a", "label": "Option A" },
64
- { "id": "b", "label": "Option B" }
65
- ],
66
- "engine": "classical",
67
- "seed": 123
68
- }
44
+ ```bash
45
+ npm install node-red-contrib-vectorprime
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-vectorprime",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "VectorPrime Decision Kernel node for Node-RED",
5
5
  "main": "vectorprime.js",
6
6
  "license": "MIT",
package/vectorprime.html CHANGED
@@ -1,4 +1,7 @@
1
- <script type="text/html" data-template-name="vectorprime-config">
1
+ <!-- =========================
2
+ VectorPrime Config Template
3
+ ========================= -->
4
+ <script type="text/x-red" data-template-name="vectorprime-config">
2
5
  <div class="form-row">
3
6
  <label for="node-config-input-baseUrl">
4
7
  <i class="fa fa-globe"></i> Base URL
@@ -105,7 +108,7 @@
105
108
  </script>
106
109
 
107
110
  <!-- ✅ HELP TAB: vectorprime-config -->
108
- <script type="text/html" data-help-name="vectorprime-config">
111
+ <script type="text/x-red" data-help-name="vectorprime-config">
109
112
  <p><b>VectorPrime Config</b> stores your Base URL and API key securely.</p>
110
113
 
111
114
  <h3>Quick Setup</h3>
@@ -130,7 +133,10 @@
130
133
 
131
134
  <hr />
132
135
 
133
- <script type="text/html" data-template-name="vectorprime-rank">
136
+ <!-- =========================
137
+ VectorPrime Rank Template
138
+ ========================= -->
139
+ <script type="text/x-red" data-template-name="vectorprime-rank">
134
140
  <div class="form-row">
135
141
  <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
136
142
  <input type="text" id="node-input-name" placeholder="Rank Decision (VectorPrime)">
@@ -169,9 +175,6 @@
169
175
  // ✅ This is the name users see in the palette
170
176
  paletteLabel: "Rank Decision (VectorPrime)",
171
177
 
172
- // ✅ This is the short description used in the tooltip card
173
- description: "Automatically ranks tasks, alerts, or choices in msg.payload so your flow can pick the best next action.",
174
-
175
178
  label: function () {
176
179
  return this.name || "Rank Decision (VectorPrime)";
177
180
  }
@@ -179,29 +182,29 @@
179
182
  </script>
180
183
 
181
184
  <!-- ✅ HELP TAB: vectorprime-rank -->
182
- <script type="text/html" data-help-name="vectorprime-rank">
185
+ <script type="text/x-red" data-help-name="vectorprime-rank">
183
186
  <p>
184
- <b>Rank Decision (VectorPrime)</b> sorts your options and returns the best next action.
185
- Use it anywhere you need <b>priority scoring</b> or <b>decision ranking</b> inside a flow.
187
+ <b>Rank Decision (VectorPrime)</b> picks the best next action automatically.
188
+ Use it anywhere your flow needs <b>priority scoring</b> or <b>decision ranking</b>.
186
189
  </p>
187
190
 
188
191
  <h3>What it does</h3>
189
192
  <ul>
190
- <li>Reads input from <code>msg.payload</code></li>
191
- <li>Calls VectorPrime Rank API</li>
192
- <li>Returns the ranked decision in <code>msg.payload</code></li>
193
+ <li>Reads options from <code>msg.payload</code></li>
194
+ <li>Calls the VectorPrime Decision Kernel Rank API</li>
195
+ <li>Returns the ranked decision inside <code>msg.payload</code></li>
193
196
  </ul>
194
197
 
195
- <h3>Best use cases</h3>
198
+ <h3>Where developers use this most</h3>
196
199
  <ul>
197
- <li><b>Incident response:</b> rank alerts by urgency</li>
198
- <li><b>Task automation:</b> choose the best next action automatically</li>
199
- <li><b>Ops + IoT:</b> prioritize device events and maintenance actions</li>
200
- <li><b>Workflows:</b> sort leads, tickets, jobs, or notifications</li>
200
+ <li><b>Incident response:</b> rank alerts fix the most critical first</li>
201
+ <li><b>Automation flows:</b> choose the best next action from multiple tasks</li>
202
+ <li><b>Ops / IoT:</b> prioritize device events + maintenance decisions</li>
203
+ <li><b>Ticketing & work queues:</b> rank jobs, leads, or support tickets</li>
201
204
  </ul>
202
205
 
203
- <h3>Input format (example)</h3>
204
- <p>Send a list of options to rank inside <code>msg.payload</code>:</p>
206
+ <h3>Input format (simple)</h3>
207
+ <p>Send a list to rank inside <code>msg.payload.items</code>:</p>
205
208
 
206
209
  <pre><code>{
207
210
  "items": [
@@ -211,17 +214,14 @@
211
214
  ]
212
215
  }</code></pre>
213
216
 
214
- <h3>Output</h3>
215
217
  <p>
216
- The response is returned in <code>msg.payload</code> (ranked order + decision result).
217
- If your free tier limit is reached, the node may return an <b>upgrade URL</b>.
218
+ The node automatically converts <code>items</code> into the backend format
219
+ (<code>decision_id</code>, <code>prompt</code>, <code>options</code>) so flows stay easy.
218
220
  </p>
219
221
 
220
222
  <h3>1-Click Example Flow (Import This)</h3>
221
223
  <p>
222
- Copy the JSON below, then go to:
223
- <br/>
224
- <b>Node-RED Menu → Import → Clipboard → Paste → Import</b>
224
+ Copy the JSON below Node-RED Menu → Import → Clipboard → Paste → Import
225
225
  </p>
226
226
 
227
227
  <pre><code>[
@@ -237,11 +237,7 @@
237
237
  "type": "inject",
238
238
  "z": "a111111111111111",
239
239
  "name": "Run VectorPrime Rank (1-click)",
240
- "props": [
241
- {
242
- "p": "payload"
243
- }
244
- ],
240
+ "props": [{ "p": "payload" }],
245
241
  "payload": "{\"items\":[{\"id\":\"fix-prod-bug\",\"label\":\"Fix production bug\",\"urgency\":10,\"impact\":10},{\"id\":\"ship-feature\",\"label\":\"Ship new feature\",\"urgency\":7,\"impact\":8},{\"id\":\"refactor\",\"label\":\"Refactor module\",\"urgency\":3,\"impact\":5}]}",
246
242
  "payloadType": "json",
247
243
  "repeat": "",
@@ -249,13 +245,9 @@
249
245
  "once": false,
250
246
  "onceDelay": 0.1,
251
247
  "topic": "",
252
- "x": 220,
248
+ "x": 230,
253
249
  "y": 160,
254
- "wires": [
255
- [
256
- "c111111111111111"
257
- ]
258
- ]
250
+ "wires": [["c111111111111111"]]
259
251
  },
260
252
  {
261
253
  "id": "c111111111111111",
@@ -264,13 +256,9 @@
264
256
  "name": "VectorPrime Rank",
265
257
  "config": "d111111111111111",
266
258
  "endpoint": "/v1/kernel/rank",
267
- "x": 460,
259
+ "x": 470,
268
260
  "y": 160,
269
- "wires": [
270
- [
271
- "e111111111111111"
272
- ]
273
- ]
261
+ "wires": [["e111111111111111"]]
274
262
  },
275
263
  {
276
264
  "id": "e111111111111111",
@@ -283,8 +271,6 @@
283
271
  "tostatus": false,
284
272
  "complete": "payload",
285
273
  "targetType": "msg",
286
- "statusVal": "",
287
- "statusType": "auto",
288
274
  "x": 700,
289
275
  "y": 160,
290
276
  "wires": []
@@ -296,10 +282,4 @@
296
282
  "baseUrl": "https://vectorprime-kernel-backend.onrender.com"
297
283
  }
298
284
  ]</code></pre>
299
-
300
- <h3>Advanced tip: override Authorization</h3>
301
- <p>
302
- If you set <code>msg.headers.Authorization</code> before this node runs, it will use your header instead
303
- of the stored key.
304
- </p>
305
285
  </script>
package/vectorprime.js CHANGED
@@ -1,7 +1,11 @@
1
1
  module.exports = function (RED) {
2
- // ✅ Works with node-fetch v2 or v3
3
- const fetch = (...args) =>
4
- import("node-fetch").then((mod) => (mod.default ? mod.default(...args) : mod(...args)));
2
+ "use strict";
3
+
4
+ // ✅ Node 22 has global fetch, but keep fallback for safety
5
+ const fetchFn =
6
+ typeof fetch === "function"
7
+ ? fetch
8
+ : (...args) => import("node-fetch").then(({ default: f }) => f(...args));
5
9
 
6
10
  function normalizeBaseUrl(url) {
7
11
  url = (url || "").trim();
@@ -12,27 +16,30 @@ module.exports = function (RED) {
12
16
 
13
17
  function normalizeEndpoint(path) {
14
18
  path = (path || "").trim();
15
- if (!path) return "/v1/kernel/rank";
16
19
  if (!path.startsWith("/")) path = "/" + path;
17
20
  return path;
18
21
  }
19
22
 
20
- // ✅ REQUIRED: runtime config node registration
23
+ // -----------------------------
24
+ // Config Node (stores Base URL + API Key)
25
+ // -----------------------------
21
26
  function VectorPrimeConfigNode(n) {
22
27
  RED.nodes.createNode(this, n);
23
28
 
24
- // baseUrl stored in node settings (non-credential)
25
- this.baseUrl = n.baseUrl || "https://vectorprime-kernel-backend.onrender.com";
29
+ this.baseUrl =
30
+ (n.baseUrl && String(n.baseUrl).trim()) ||
31
+ "https://vectorprime-kernel-backend.onrender.com";
26
32
  }
27
33
 
28
- // ✅ IMPORTANT: credentials must be declared here too
29
34
  RED.nodes.registerType("vectorprime-config", VectorPrimeConfigNode, {
30
35
  credentials: {
31
36
  apiKey: { type: "password" },
32
37
  },
33
38
  });
34
39
 
35
- // ✅ Main node
40
+ // -----------------------------
41
+ // Rank Node (calls VectorPrime backend)
42
+ // -----------------------------
36
43
  function VectorPrimeRankNode(config) {
37
44
  RED.nodes.createNode(this, config);
38
45
  const node = this;
@@ -40,19 +47,17 @@ module.exports = function (RED) {
40
47
  node.name = config.name;
41
48
  node.endpoint = config.endpoint || "/v1/kernel/rank";
42
49
 
50
+ const cfg = RED.nodes.getNode(config.config);
51
+
43
52
  node.on("input", async function (msg, send, done) {
44
53
  send = send || function () { node.send.apply(node, arguments); };
45
54
 
46
55
  try {
47
- const cfg = RED.nodes.getNode(config.config);
48
56
  if (!cfg) {
49
57
  node.status({ fill: "red", shape: "ring", text: "missing config" });
50
- msg.payload = {
51
- error: "VectorPrime config missing. Open node settings and set Config.",
52
- };
53
- send(msg);
54
- if (done) done();
55
- return;
58
+ throw new Error(
59
+ "VectorPrime config missing. Open node settings and select/create a Config."
60
+ );
56
61
  }
57
62
 
58
63
  const baseUrl = normalizeBaseUrl(cfg.baseUrl);
@@ -60,94 +65,82 @@ module.exports = function (RED) {
60
65
 
61
66
  if (!baseUrl.startsWith("http")) {
62
67
  node.status({ fill: "red", shape: "ring", text: "invalid base url" });
63
- msg.payload = { error: `Base URL missing/invalid: ${baseUrl}` };
64
- send(msg);
65
- if (done) done();
66
- return;
68
+ throw new Error(`Base URL missing/invalid: ${baseUrl}`);
67
69
  }
68
70
 
69
- // ✅ Ensure headers object exists
70
- msg.headers = msg.headers || {};
71
-
72
- // ✅ Allow incoming Authorization override (advanced users)
73
- const incomingAuth =
74
- msg.headers.authorization ||
75
- msg.headers.Authorization ||
76
- "";
77
-
78
71
  const storedKey =
79
72
  cfg.credentials && cfg.credentials.apiKey
80
73
  ? String(cfg.credentials.apiKey).trim()
81
74
  : "";
82
75
 
83
- // If user didn't provide Authorization, use stored key from config credentials
84
- if (!incomingAuth) {
85
- if (!storedKey) {
86
- node.status({ fill: "red", shape: "ring", text: "missing api key" });
87
- msg.payload = {
88
- error: "No API key. Open VectorPrime Config and click 'Get Free Key'.",
89
- };
90
- send(msg);
91
- if (done) done();
92
- return;
93
- }
94
- msg.headers.Authorization = `Bearer ${storedKey}`;
76
+ if (!storedKey) {
77
+ node.status({ fill: "red", shape: "ring", text: "missing api key" });
78
+ throw new Error(
79
+ "No API key. Open VectorPrime Config and click 'Get Free Key' or paste your paid key."
80
+ );
95
81
  }
96
82
 
97
- // ✅ Force JSON
98
- msg.headers["Content-Type"] = "application/json";
83
+ // -----------------------------
84
+ // ✅ AUTO-CONVERT ITEMS → OPTIONS (YOUR REQUIRED FIX)
85
+ // Backend expects: decision_id, prompt, options
86
+ // Users send: {items:[...]}
87
+ // -----------------------------
88
+ let payload = msg.payload || {};
89
+
90
+ if (payload && typeof payload === "object" && Array.isArray(payload.items) && !payload.options) {
91
+ const decision_id = payload.decision_id || `node-red-${Date.now()}`;
92
+ const prompt = payload.prompt || "Rank the best next action from these options.";
93
+
94
+ const options = payload.items.map((it, idx) => ({
95
+ id: it.id || `opt-${idx + 1}`,
96
+ label: it.label || it.title || it.id || `Option ${idx + 1}`,
97
+ urgency: it.urgency,
98
+ impact: it.impact,
99
+ effort: it.effort,
100
+ }));
101
+
102
+ payload = { decision_id, prompt, options };
103
+ }
99
104
 
100
- const payload = msg.payload || {};
105
+ // Validate final expected shape
106
+ if (!payload.decision_id || !payload.prompt || !payload.options) {
107
+ node.status({ fill: "red", shape: "ring", text: "bad payload" });
108
+ throw new Error(
109
+ "Invalid payload. Provide { items:[...] } or full { decision_id, prompt, options }."
110
+ );
111
+ }
101
112
 
102
113
  node.status({ fill: "blue", shape: "dot", text: "ranking..." });
103
114
 
104
- const resp = await fetch(`${baseUrl}${endpoint}`, {
115
+ const resp = await fetchFn(`${baseUrl}${endpoint}`, {
105
116
  method: "POST",
106
- headers: msg.headers,
117
+ headers: {
118
+ "Authorization": `Bearer ${storedKey}`,
119
+ "Content-Type": "application/json",
120
+ },
107
121
  body: JSON.stringify(payload),
108
122
  });
109
123
 
110
124
  const text = await resp.text();
111
125
  let data;
112
- try {
113
- data = JSON.parse(text);
114
- } catch {
115
- data = { raw: text };
116
- }
126
+ try { data = JSON.parse(text); } catch { data = { raw: text }; }
117
127
 
118
- // ✅ Free tier limit hit → return upgrade url inside Node-RED
119
128
  if (!resp.ok) {
120
- const upgradeUrl = data.upgrade_url || data.checkout_url || null;
121
-
122
- if (resp.status === 402 && upgradeUrl) {
123
- node.status({
124
- fill: "yellow",
125
- shape: "ring",
126
- text: "limit reached → upgrade",
127
- });
128
- msg.payload = data;
129
- msg.vectorprime = { upgrade_url: upgradeUrl };
130
- send(msg);
131
- if (done) done();
132
- return;
133
- }
134
-
135
129
  node.status({ fill: "red", shape: "ring", text: `error ${resp.status}` });
136
- msg.payload = data;
137
- send(msg);
138
- if (done) done();
139
- return;
130
+ throw new Error(`VectorPrime API ${resp.status}: ${JSON.stringify(data)}`);
140
131
  }
141
132
 
142
133
  node.status({ fill: "green", shape: "dot", text: "ok" });
134
+
135
+ // ✅ Replace payload with ranked result
143
136
  msg.payload = data;
137
+
144
138
  send(msg);
145
- if (done) done();
139
+ done();
146
140
  } catch (err) {
147
141
  node.status({ fill: "red", shape: "ring", text: "failed" });
148
- msg.payload = { error: err.message || String(err) };
149
- send(msg);
150
- if (done) done(err);
142
+ node.error(err, msg);
143
+ done(err);
151
144
  }
152
145
  });
153
146
  }