neuralos 3.9.5 → 3.9.6
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/LICENSE +202 -0
- package/README.md +48 -347
- package/cli.js +78 -0
- package/engine.js +124 -0
- package/index.js +160 -0
- package/package.json +37 -43
- package/test.js +20 -0
- package/tools.js +47 -0
- package/bin/gybackend.cjs +0 -411663
- package/plugins/agentspan-bridge/agentspan-bridge.extreme.spec.ts +0 -328
- package/plugins/agentspan-bridge/agentspan-bridge.phase2.extreme.spec.ts +0 -282
- package/plugins/agentspan-bridge/conductorClient.mjs +0 -182
- package/plugins/agentspan-bridge/index.mjs +0 -389
- package/plugins/agentspan-bridge/playbookToWorkflowDef.mjs +0 -209
- package/plugins/agentspan-bridge/plugin.json +0 -27
- package/plugins/fraudops/index.d.mts +0 -9
- package/plugins/fraudops/index.mjs +0 -185
- package/plugins/fraudops/plugin.json +0 -10
- package/plugins/iam-connector/index.d.mts +0 -10
- package/plugins/iam-connector/index.mjs +0 -229
- package/plugins/iam-connector/plugin.json +0 -10
- package/plugins/mitmproxy-bridge/index.d.mts +0 -20
- package/plugins/mitmproxy-bridge/index.mjs +0 -256
- package/plugins/mitmproxy-bridge/plugin.json +0 -17
- package/plugins/monid-bridge/index.mjs +0 -228
- package/plugins/monid-bridge/monid-bridge.extreme.spec.mjs +0 -75
- package/plugins/monid-bridge/plugin.json +0 -15
- package/plugins/netdata-rterm/index.mjs +0 -229
- package/plugins/netdata-rterm/netdata-rterm.extreme.spec.ts +0 -252
- package/plugins/netdata-rterm/plugin.json +0 -22
- package/plugins/netexec-bridge/index.d.mts +0 -28
- package/plugins/netexec-bridge/index.mjs +0 -325
- package/plugins/netexec-bridge/plugin.json +0 -20
- package/plugins/neuralos/index.mjs +0 -137
- package/plugins/neuralos/neuralos.extreme.spec.mts +0 -386
- package/plugins/neuralos/neuralosEngine.mjs +0 -369
- package/plugins/neuralos/plugin.json +0 -15
- package/plugins/numbat-bridge/index.mjs +0 -229
- package/plugins/numbat-bridge/numbat-bridge.extreme.spec.mjs +0 -148
- package/plugins/numbat-bridge/plugin.json +0 -21
- package/plugins/patch-manager/index.d.mts +0 -10
- package/plugins/patch-manager/index.mjs +0 -288
- package/plugins/patch-manager/plugin.json +0 -10
- package/plugins/promptfoo-redteam/index.d.mts +0 -15
- package/plugins/promptfoo-redteam/index.mjs +0 -277
- package/plugins/promptfoo-redteam/plugin.json +0 -16
- package/plugins/reactorpro-bridge/index.mjs +0 -499
- package/plugins/reactorpro-bridge/plugin.json +0 -26
- package/plugins/reactorpro-bridge/reactorpro-bridge.extreme.spec.mts +0 -409
- package/plugins/reactorpro-bridge/reactorproAgent.mjs +0 -399
- package/plugins/request-router/index.d.mts +0 -9
- package/plugins/request-router/index.mjs +0 -239
- package/plugins/request-router/plugin.json +0 -10
- package/plugins/sample-k8s-slo/index.ts +0 -58
- package/plugins/sample-k8s-slo/plugin.json +0 -11
- package/plugins/sop-assistant/index.d.mts +0 -9
- package/plugins/sop-assistant/index.mjs +0 -325
- package/plugins/sop-assistant/plugin.json +0 -10
- package/plugins/synapse-bridge/index.mjs +0 -484
- package/plugins/synapse-bridge/plugin.json +0 -29
- package/plugins/synapse-bridge/synapse-bridge.extreme.spec.mjs +0 -204
- package/plugins/synapse-bridge/synapseAgent.extreme.spec.mjs +0 -236
- package/plugins/synapse-bridge/synapseAgent.mjs +0 -254
- package/plugins/web-intel/index.mjs +0 -381
- package/plugins/web-intel/plugin.json +0 -28
- package/plugins/web-intel/sidecar.mjs +0 -134
- package/plugins/web-intel/web-intel.extreme.spec.ts +0 -263
- package/plugins/web-intel/wigoloClient.mjs +0 -145
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* monid-bridge — thin wrapper around the official Monid CLI (v0.1.6).
|
|
3
|
-
*
|
|
4
|
-
* Settings → Plugins → Monid (settings.monid): enabled, binaryPath, keyLabel
|
|
5
|
-
* API key is NEVER stored in settings.json (`monid keys add -k … -l …`).
|
|
6
|
-
*
|
|
7
|
-
* Agent tools: monid_health, monid_discover, monid_inspect, monid_run.
|
|
8
|
-
*
|
|
9
|
-
* Argv MUST match `monid <cmd> --help`:
|
|
10
|
-
* discover --query <q> [--limit n] [--min-score s] --json
|
|
11
|
-
* inspect --provider <p> --endpoint <e> --json
|
|
12
|
-
* run --provider <p> --endpoint <e> [--input json] [--query json] [--path json] --json
|
|
13
|
-
* Bare `discover <query>` / `run <tool>` are false positives (CLI rejects them).
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { createRequire } from 'node:module'
|
|
17
|
-
|
|
18
|
-
const require = createRequire(import.meta.url)
|
|
19
|
-
|
|
20
|
-
export function resolveConfig(ctx = {}, env = process.env) {
|
|
21
|
-
const s = (typeof ctx.getSettings === 'function' ? ctx.getSettings() : ctx.settings) || {}
|
|
22
|
-
const b = s.monid || {}
|
|
23
|
-
return {
|
|
24
|
-
enabled: b.enabled !== false,
|
|
25
|
-
binaryPath: b.binaryPath || env.MONID_BIN || 'monid',
|
|
26
|
-
keyLabel: b.keyLabel || 'rterm',
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function reqStr(v, name) {
|
|
31
|
-
const s = String(v ?? '').trim()
|
|
32
|
-
if (!s) throw new Error(`${name} is required`)
|
|
33
|
-
return s
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function optJsonFlag(argv, flag, value) {
|
|
37
|
-
if (value == null) return
|
|
38
|
-
const s = typeof value === 'string' ? value.trim() : JSON.stringify(value)
|
|
39
|
-
if (!s) return
|
|
40
|
-
argv.push(flag, s)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function optNumFlag(argv, flag, value) {
|
|
44
|
-
if (value == null || value === '') return
|
|
45
|
-
const n = Number(value)
|
|
46
|
-
if (!Number.isFinite(n)) throw new Error(`${flag} must be a number`)
|
|
47
|
-
argv.push(flag, String(n))
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Build argv AFTER the binary name.
|
|
52
|
-
* Always pass --json so agents get structured output (no ANSI).
|
|
53
|
-
*/
|
|
54
|
-
export function buildMonidArgv(sub, params = {}) {
|
|
55
|
-
switch (sub) {
|
|
56
|
-
case 'version':
|
|
57
|
-
return ['--version']
|
|
58
|
-
case 'keys-list':
|
|
59
|
-
return ['keys', 'list']
|
|
60
|
-
case 'whoami':
|
|
61
|
-
return ['whoami']
|
|
62
|
-
case 'discover': {
|
|
63
|
-
const q = reqStr(params.query, 'query')
|
|
64
|
-
const argv = ['discover', '--query', q]
|
|
65
|
-
optNumFlag(argv, '--limit', params.limit)
|
|
66
|
-
optNumFlag(argv, '--min-score', params.minScore ?? params['min-score'])
|
|
67
|
-
argv.push('--json')
|
|
68
|
-
return argv
|
|
69
|
-
}
|
|
70
|
-
case 'inspect': {
|
|
71
|
-
const provider = reqStr(params.provider, 'provider')
|
|
72
|
-
const endpoint = reqStr(params.endpoint, 'endpoint')
|
|
73
|
-
return ['inspect', '--provider', provider, '--endpoint', endpoint, '--json']
|
|
74
|
-
}
|
|
75
|
-
case 'run': {
|
|
76
|
-
// FN: agents may pass tool="provider/endpoint" from older docs.
|
|
77
|
-
let provider = String(params.provider || '').trim()
|
|
78
|
-
let endpoint = String(params.endpoint || '').trim()
|
|
79
|
-
const tool = String(params.tool || '').trim()
|
|
80
|
-
if ((!provider || !endpoint) && tool) {
|
|
81
|
-
const slash = tool.indexOf('/')
|
|
82
|
-
if (slash > 0) {
|
|
83
|
-
provider = provider || tool.slice(0, slash)
|
|
84
|
-
endpoint = endpoint || tool.slice(slash)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (!provider) throw new Error('monid_run needs provider (slug from discover)')
|
|
88
|
-
if (!endpoint) throw new Error('monid_run needs endpoint (path from discover, e.g. /news/search)')
|
|
89
|
-
if (!endpoint.startsWith('/')) {
|
|
90
|
-
throw new Error('endpoint must start with / (Monid endpoint path, not a free-form tool name)')
|
|
91
|
-
}
|
|
92
|
-
const argv = ['run', '--provider', provider, '--endpoint', endpoint]
|
|
93
|
-
const input = params.input ?? params.body
|
|
94
|
-
if (input != null && String(input).trim()) {
|
|
95
|
-
argv.push('--input', typeof input === 'string' ? input : JSON.stringify(input))
|
|
96
|
-
}
|
|
97
|
-
optJsonFlag(argv, '--query', params.queryParams ?? params.query)
|
|
98
|
-
optJsonFlag(argv, '--path', params.pathParams ?? params.path)
|
|
99
|
-
if (params.wait != null && params.wait !== false && params.wait !== '') {
|
|
100
|
-
argv.push('--wait')
|
|
101
|
-
if (params.wait !== true) argv.push(String(params.wait))
|
|
102
|
-
}
|
|
103
|
-
argv.push('--json')
|
|
104
|
-
return argv
|
|
105
|
-
}
|
|
106
|
-
default:
|
|
107
|
-
throw new Error(`unknown monid subcommand: ${sub}`)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** True if argv looks like the broken 3.3.8 shapes (for tests / guards). */
|
|
112
|
-
export function isLegacyBrokenArgv(argv) {
|
|
113
|
-
if (!Array.isArray(argv) || argv.length < 2) return false
|
|
114
|
-
if (argv[0] === 'discover' && argv[1] !== '--query' && !argv[1].startsWith('-')) return true
|
|
115
|
-
if (argv[0] === 'run' && argv[1] !== '--provider' && !String(argv[1]).startsWith('-')) return true
|
|
116
|
-
return false
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async function runMonid(ctx, cfg, argv) {
|
|
120
|
-
const env = { ...process.env, NO_COLOR: '1' }
|
|
121
|
-
if (typeof ctx.runCommand === 'function') {
|
|
122
|
-
const cmdline = [cfg.binaryPath, ...argv].map((a) => (/\s/.test(a) ? JSON.stringify(a) : a)).join(' ')
|
|
123
|
-
return ctx.runCommand({ command: cmdline, env })
|
|
124
|
-
}
|
|
125
|
-
if (typeof ctx.exec === 'function') {
|
|
126
|
-
const cmdline = [cfg.binaryPath, ...argv].map((a) => (/\s/.test(a) ? JSON.stringify(a) : a)).join(' ')
|
|
127
|
-
return ctx.exec(cmdline, { env })
|
|
128
|
-
}
|
|
129
|
-
const { execFile } = require('node:child_process')
|
|
130
|
-
return new Promise((resolve) => {
|
|
131
|
-
execFile(cfg.binaryPath, argv, { timeout: 120000, env }, (err, stdout, stderr) => {
|
|
132
|
-
resolve({
|
|
133
|
-
ok: !err,
|
|
134
|
-
exitCode: typeof err?.code === 'number' ? err.code : err ? 1 : 0,
|
|
135
|
-
stdout: String(stdout ?? ''),
|
|
136
|
-
stderr: String(stderr ?? ''),
|
|
137
|
-
error: err ? String(err.message) : undefined,
|
|
138
|
-
})
|
|
139
|
-
})
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async function guarded(fn, log) {
|
|
144
|
-
try {
|
|
145
|
-
return await fn()
|
|
146
|
-
} catch (e) {
|
|
147
|
-
const msg = e?.message ?? String(e)
|
|
148
|
-
log?.(`[monid] ${msg}`)
|
|
149
|
-
return {
|
|
150
|
-
error: msg,
|
|
151
|
-
hint: 'Install @monid-ai/cli (`npm i -g @monid-ai/cli`) and paste an API key in Settings → Plugins → Monid. discover needs --query; run needs --provider and --endpoint.',
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function register(ctx) {
|
|
157
|
-
const { registerTool, log } = ctx
|
|
158
|
-
const cfg = resolveConfig(ctx)
|
|
159
|
-
if (!cfg.enabled) {
|
|
160
|
-
log?.('[monid] disabled in settings.monid.enabled')
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
registerTool({
|
|
165
|
-
name: 'monid_health',
|
|
166
|
-
description: 'Check the Monid CLI is installed and list configured key labels (never the secret).',
|
|
167
|
-
params: {},
|
|
168
|
-
handler: async () =>
|
|
169
|
-
guarded(async () => {
|
|
170
|
-
const ver = await runMonid(ctx, cfg, buildMonidArgv('version'))
|
|
171
|
-
const keys = await runMonid(ctx, cfg, buildMonidArgv('keys-list'))
|
|
172
|
-
const who = await runMonid(ctx, cfg, buildMonidArgv('whoami'))
|
|
173
|
-
return { binaryPath: cfg.binaryPath, keyLabel: cfg.keyLabel, version: ver, keys, whoami: who }
|
|
174
|
-
}, log),
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
registerTool({
|
|
178
|
-
name: 'monid_discover',
|
|
179
|
-
description:
|
|
180
|
-
'Search Monid data endpoints with natural language (company news, people, enrichment). Uses `monid discover --query`. Do not pass a bare positional query.',
|
|
181
|
-
params: {
|
|
182
|
-
query: { type: 'string', description: 'Natural-language search (required)' },
|
|
183
|
-
limit: { type: 'number', description: 'Max results (max 50)', optional: true },
|
|
184
|
-
minScore: { type: 'number', description: 'Minimum relevance score', optional: true },
|
|
185
|
-
},
|
|
186
|
-
handler: async (p) =>
|
|
187
|
-
guarded(async () => {
|
|
188
|
-
const argv = buildMonidArgv('discover', p)
|
|
189
|
-
const r = await runMonid(ctx, cfg, argv)
|
|
190
|
-
return { argv: [cfg.binaryPath, ...argv], ...r }
|
|
191
|
-
}, log),
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
registerTool({
|
|
195
|
-
name: 'monid_inspect',
|
|
196
|
-
description: 'Get full details for one Monid endpoint (`monid inspect --provider --endpoint`).',
|
|
197
|
-
params: {
|
|
198
|
-
provider: { type: 'string', description: 'Provider slug (e.g. context.dev)' },
|
|
199
|
-
endpoint: { type: 'string', description: 'Endpoint path (e.g. /news/search)' },
|
|
200
|
-
},
|
|
201
|
-
handler: async (p) =>
|
|
202
|
-
guarded(async () => {
|
|
203
|
-
const argv = buildMonidArgv('inspect', p)
|
|
204
|
-
const r = await runMonid(ctx, cfg, argv)
|
|
205
|
-
return { argv: [cfg.binaryPath, ...argv], ...r }
|
|
206
|
-
}, log),
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
registerTool({
|
|
210
|
-
name: 'monid_run',
|
|
211
|
-
description:
|
|
212
|
-
'Execute a Monid endpoint (`monid run --provider --endpoint`). provider+endpoint from discover; optional JSON --input / --query / --path. Not `monid run <toolName>`.',
|
|
213
|
-
params: {
|
|
214
|
-
provider: { type: 'string', description: 'Provider slug', optional: true },
|
|
215
|
-
endpoint: { type: 'string', description: 'Endpoint path starting with /', optional: true },
|
|
216
|
-
tool: { type: 'string', description: 'Optional shorthand provider/endpoint', optional: true },
|
|
217
|
-
input: { type: 'string', description: 'Body JSON string', optional: true },
|
|
218
|
-
queryParams: { type: 'string', description: 'Query-parameters JSON string', optional: true },
|
|
219
|
-
pathParams: { type: 'string', description: 'Path-parameters JSON string', optional: true },
|
|
220
|
-
},
|
|
221
|
-
handler: async (p) =>
|
|
222
|
-
guarded(async () => {
|
|
223
|
-
const argv = buildMonidArgv('run', p)
|
|
224
|
-
const r = await runMonid(ctx, cfg, argv)
|
|
225
|
-
return { argv: [cfg.binaryPath, ...argv], ...r }
|
|
226
|
-
}, log),
|
|
227
|
-
})
|
|
228
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { buildMonidArgv, isLegacyBrokenArgv, resolveConfig } from './index.mjs'
|
|
2
|
-
|
|
3
|
-
const assert = (c, m) => {
|
|
4
|
-
if (!c) throw new Error(`assert failed: ${m}`)
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
// --- config ---
|
|
8
|
-
assert(resolveConfig({ settings: {} }).enabled === true, 'enabled default')
|
|
9
|
-
assert(resolveConfig({ settings: { monid: { enabled: false } } }).enabled === false, 'disabled')
|
|
10
|
-
assert(resolveConfig({ settings: { monid: { binaryPath: '/opt/monid' } } }).binaryPath === '/opt/monid', 'bin')
|
|
11
|
-
assert(resolveConfig({}, { MONID_BIN: 'custom-monid' }).binaryPath === 'custom-monid', 'env bin')
|
|
12
|
-
|
|
13
|
-
// --- FP: 3.3.8 positional argv must be detected as broken ---
|
|
14
|
-
assert(isLegacyBrokenArgv(['discover', 'company news']) === true, 'FP discover positional')
|
|
15
|
-
assert(isLegacyBrokenArgv(['run', 'foo']) === true, 'FP run positional')
|
|
16
|
-
assert(isLegacyBrokenArgv(['discover', '--query', 'company news']) === false, 'good discover')
|
|
17
|
-
assert(isLegacyBrokenArgv(['run', '--provider', 'x', '--endpoint', '/y']) === false, 'good run')
|
|
18
|
-
|
|
19
|
-
const disc = buildMonidArgv('discover', { query: 'company news', limit: 5 })
|
|
20
|
-
assert(!isLegacyBrokenArgv(disc), 'new discover not legacy')
|
|
21
|
-
assert(disc[0] === 'discover' && disc[1] === '--query' && disc[2] === 'company news', `disc=${disc}`)
|
|
22
|
-
assert(disc.includes('--limit') && disc.includes('5') && disc.includes('--json'), 'limit+json')
|
|
23
|
-
|
|
24
|
-
assert(buildMonidArgv('version').join(' ') === '--version', 'version')
|
|
25
|
-
assert(buildMonidArgv('keys-list').join(' ') === 'keys list', 'keys list')
|
|
26
|
-
assert(buildMonidArgv('whoami').join(' ') === 'whoami', 'whoami')
|
|
27
|
-
|
|
28
|
-
const insp = buildMonidArgv('inspect', { provider: 'context.dev', endpoint: '/news/search' })
|
|
29
|
-
assert(insp.join(' ') === 'inspect --provider context.dev --endpoint /news/search --json', insp.join(' '))
|
|
30
|
-
|
|
31
|
-
const run = buildMonidArgv('run', {
|
|
32
|
-
provider: 'context.dev',
|
|
33
|
-
endpoint: '/news/search',
|
|
34
|
-
input: '{"q":"acme"}',
|
|
35
|
-
})
|
|
36
|
-
assert(run[0] === 'run' && run.includes('--provider') && run.includes('context.dev'), 'run provider')
|
|
37
|
-
assert(run.includes('--endpoint') && run.includes('/news/search'), 'run endpoint')
|
|
38
|
-
assert(run.includes('--input') && run.includes('--json'), 'run input+json')
|
|
39
|
-
assert(!isLegacyBrokenArgv(run), 'new run not legacy')
|
|
40
|
-
|
|
41
|
-
// FN: tool="provider/endpoint" shorthand
|
|
42
|
-
const fromTool = buildMonidArgv('run', { tool: 'apollo/mixed_companies/search' })
|
|
43
|
-
assert(fromTool.includes('apollo') && fromTool.includes('/mixed_companies/search'), `fromTool=${fromTool}`)
|
|
44
|
-
|
|
45
|
-
// FN: queryParams / pathParams
|
|
46
|
-
const withQP = buildMonidArgv('run', {
|
|
47
|
-
provider: 'akta',
|
|
48
|
-
endpoint: '/v1/news',
|
|
49
|
-
queryParams: '{"q":"x"}',
|
|
50
|
-
pathParams: '{"id":"1"}',
|
|
51
|
-
})
|
|
52
|
-
assert(withQP.includes('--query') && withQP.includes('{"q":"x"}'), 'query flag')
|
|
53
|
-
assert(withQP.includes('--path') && withQP.includes('{"id":"1"}'), 'path flag')
|
|
54
|
-
|
|
55
|
-
// rejects
|
|
56
|
-
let threw = false
|
|
57
|
-
try { buildMonidArgv('discover', { query: ' ' }) } catch { threw = true }
|
|
58
|
-
assert(threw, 'empty query')
|
|
59
|
-
threw = false
|
|
60
|
-
try { buildMonidArgv('run', { tool: 'not-a-path' }) } catch { threw = true }
|
|
61
|
-
assert(threw, 'run without provider/endpoint')
|
|
62
|
-
threw = false
|
|
63
|
-
try { buildMonidArgv('run', { provider: 'x', endpoint: 'news' }) } catch { threw = true }
|
|
64
|
-
assert(threw, 'endpoint must start with /')
|
|
65
|
-
threw = false
|
|
66
|
-
try { buildMonidArgv('inspect', { provider: 'x' }) } catch { threw = true }
|
|
67
|
-
assert(threw, 'inspect needs endpoint')
|
|
68
|
-
threw = false
|
|
69
|
-
try { buildMonidArgv('nope') } catch { threw = true }
|
|
70
|
-
assert(threw, 'unknown sub')
|
|
71
|
-
threw = false
|
|
72
|
-
try { buildMonidArgv('discover', { query: 'q', limit: 'nope' }) } catch { threw = true }
|
|
73
|
-
assert(threw, 'bad limit')
|
|
74
|
-
|
|
75
|
-
console.log('monid-bridge: all cases passed')
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "monid-bridge",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "Thin Monid CLI bridge: paste an API key in Settings → Plugins; agents get monid_discover / monid_run / monid_health wrapping the official CLI. Does not reimplement TinyFish.",
|
|
5
|
-
"entry": "index.mjs",
|
|
6
|
-
"tools": [
|
|
7
|
-
"monid_health",
|
|
8
|
-
"monid_discover",
|
|
9
|
-
"monid_inspect",
|
|
10
|
-
"monid_run"
|
|
11
|
-
],
|
|
12
|
-
"permissions": [
|
|
13
|
-
"exec"
|
|
14
|
-
]
|
|
15
|
-
}
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* netdata-rterm plugin — Netdata integration for RTerm.
|
|
3
|
-
*
|
|
4
|
-
* Ingests Netdata alert webhooks (per the Netdata Cloud webhook schema),
|
|
5
|
-
* correlates anomalies with RTerm's metrics ledger + incident history for
|
|
6
|
-
* agent RCA, and fires triggers for auto-remediation playbooks.
|
|
7
|
-
*
|
|
8
|
-
* Netdata webhook alert payload fields (from Netdata docs):
|
|
9
|
-
* message, alert, info, chart, context, space, rooms, family, class,
|
|
10
|
-
* severity (warning|critical|clear), date (ISO8601), duration,
|
|
11
|
-
* additional_active_critical_alerts, additional_active_warning_alerts,
|
|
12
|
-
* alert_url
|
|
13
|
-
*
|
|
14
|
-
* Netdata webhook reachability payload fields:
|
|
15
|
-
* message, node, space, rooms, status (up|down), date, duration
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
// --- Pure: parse a Netdata webhook alert payload ---
|
|
19
|
-
export function parseNetdataAlert(payload) {
|
|
20
|
-
if (!payload || typeof payload !== 'object') return null
|
|
21
|
-
|
|
22
|
-
// Alert notification
|
|
23
|
-
if (payload.alert && payload.severity) {
|
|
24
|
-
return {
|
|
25
|
-
kind: 'alert',
|
|
26
|
-
alert: String(payload.alert),
|
|
27
|
-
message: String(payload.message ?? ''),
|
|
28
|
-
info: String(payload.info ?? ''),
|
|
29
|
-
chart: String(payload.chart ?? ''),
|
|
30
|
-
context: String(payload.context ?? ''),
|
|
31
|
-
space: String(payload.space ?? ''),
|
|
32
|
-
family: String(payload.family ?? ''),
|
|
33
|
-
class: String(payload.class ?? ''),
|
|
34
|
-
severity: String(payload.severity), // warning | critical | clear
|
|
35
|
-
date: String(payload.date ?? ''),
|
|
36
|
-
duration: String(payload.duration ?? ''),
|
|
37
|
-
alertUrl: String(payload.alert_url ?? ''),
|
|
38
|
-
additionalCritical: Number(payload.additional_active_critical_alerts ?? 0),
|
|
39
|
-
additionalWarning: Number(payload.additional_active_warning_alerts ?? 0),
|
|
40
|
-
// host is inferred from space/node; Netdata Cloud sends space name.
|
|
41
|
-
host: String(payload.space ?? 'unknown'),
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Reachability notification
|
|
46
|
-
if (payload.node && payload.status) {
|
|
47
|
-
return {
|
|
48
|
-
kind: 'reachability',
|
|
49
|
-
message: String(payload.message ?? ''),
|
|
50
|
-
node: String(payload.node),
|
|
51
|
-
space: String(payload.space ?? ''),
|
|
52
|
-
status: String(payload.status), // up | down
|
|
53
|
-
date: String(payload.date ?? ''),
|
|
54
|
-
duration: String(payload.duration ?? ''),
|
|
55
|
-
host: String(payload.node ?? 'unknown'),
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return null
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// --- Pure: map Netdata severity to RTerm alert severity ---
|
|
63
|
-
export function mapSeverity(netdataSeverity) {
|
|
64
|
-
const s = String(netdataSeverity).toLowerCase()
|
|
65
|
-
if (s === 'critical') return 'critical'
|
|
66
|
-
if (s === 'warning') return 'warning'
|
|
67
|
-
if (s === 'clear') return 'info' // alert cleared
|
|
68
|
-
return 'info'
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// --- Pure: build an RTerm alert fingerprint from a parsed Netdata alert ---
|
|
72
|
-
export function buildFingerprint(parsed) {
|
|
73
|
-
if (!parsed) return ''
|
|
74
|
-
if (parsed.kind === 'reachability') {
|
|
75
|
-
return `netdata:reachability:${parsed.host}:${parsed.status}`
|
|
76
|
-
}
|
|
77
|
-
return `netdata:${parsed.host}:${parsed.alert}:${parsed.severity}`
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// --- Pure: build a trigger event from a parsed Netdata alert ---
|
|
81
|
-
export function toTriggerEvent(parsed) {
|
|
82
|
-
if (!parsed) return null
|
|
83
|
-
const severity = parsed.kind === 'reachability'
|
|
84
|
-
? (parsed.status === 'down' ? 'critical' : 'info')
|
|
85
|
-
: mapSeverity(parsed.severity)
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
source: 'netdata',
|
|
89
|
-
fingerprint: buildFingerprint(parsed),
|
|
90
|
-
title: parsed.kind === 'reachability'
|
|
91
|
-
? `${parsed.host} ${parsed.status === 'down' ? 'is DOWN' : 'is UP'}`
|
|
92
|
-
: `[Netdata] ${parsed.alert} on ${parsed.host}`,
|
|
93
|
-
severity,
|
|
94
|
-
detail: parsed.message || parsed.info || '',
|
|
95
|
-
labels: {
|
|
96
|
-
host: parsed.host,
|
|
97
|
-
alert: parsed.alert ?? '',
|
|
98
|
-
chart: parsed.chart ?? '',
|
|
99
|
-
context: parsed.context ?? '',
|
|
100
|
-
netdata_severity: parsed.severity ?? parsed.status ?? '',
|
|
101
|
-
netdata_space: parsed.space ?? '',
|
|
102
|
-
},
|
|
103
|
-
at: parsed.date ? new Date(parsed.date).getTime() : Date.now(),
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// --- Pure: correlate a Netdata alert with RTerm's metrics + incidents ---
|
|
108
|
-
export function correlateWithRterm(parsed, metricsLedger, incidentLedger) {
|
|
109
|
-
if (!parsed) return { recentMetrics: null, openIncidents: [], correlation: '' }
|
|
110
|
-
|
|
111
|
-
const host = parsed.host
|
|
112
|
-
|
|
113
|
-
// Pull recent metrics for the host from the ledger.
|
|
114
|
-
let recentMetrics = null
|
|
115
|
-
if (metricsLedger && typeof metricsLedger.snapshot === 'function') {
|
|
116
|
-
try {
|
|
117
|
-
recentMetrics = metricsLedger.snapshot(host)
|
|
118
|
-
} catch { /* best-effort */ }
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Pull open incidents for the host.
|
|
122
|
-
let openIncidents = []
|
|
123
|
-
if (incidentLedger && typeof incidentLedger.list === 'function') {
|
|
124
|
-
try {
|
|
125
|
-
const all = incidentLedger.list()
|
|
126
|
-
openIncidents = all.filter(
|
|
127
|
-
(inc) => Array.isArray(inc.affected) && inc.affected.includes(host) && inc.status !== 'resolved'
|
|
128
|
-
)
|
|
129
|
-
} catch { /* best-effort */ }
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Build a correlation summary for the agent.
|
|
133
|
-
const parts = []
|
|
134
|
-
if (recentMetrics) {
|
|
135
|
-
parts.push(`Recent metrics for ${host}: ${JSON.stringify(recentMetrics).slice(0, 500)}`)
|
|
136
|
-
}
|
|
137
|
-
if (openIncidents.length > 0) {
|
|
138
|
-
parts.push(`${openIncidents.length} open incident(s) for ${host}: ${openIncidents.map((i) => i.title).join('; ')}`)
|
|
139
|
-
}
|
|
140
|
-
if (parsed.kind === 'alert' && parsed.additionalCritical > 0) {
|
|
141
|
-
parts.push(`${parsed.additionalCritical} additional critical alert(s) on the same node`)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const correlation = parts.length > 0
|
|
145
|
-
? `Netdata alert "${parsed.alert ?? parsed.message}" on ${host} correlates with: ${parts.join('. ')}.`
|
|
146
|
-
: `No prior RTerm context for ${host}.`
|
|
147
|
-
|
|
148
|
-
return { recentMetrics, openIncidents, correlation }
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// --- Plugin entry: register tools, triggers, panel ---
|
|
152
|
-
export function register(ctx) {
|
|
153
|
-
const { registerTool, registerTrigger, registerPanel, exec, readLedger, log } = ctx
|
|
154
|
-
|
|
155
|
-
// Tool: netdata_alert_summary — summarize recent Netdata alerts for a host.
|
|
156
|
-
registerTool({
|
|
157
|
-
name: 'netdata_alert_summary',
|
|
158
|
-
description: 'Summarize recent Netdata alerts for a host. Returns parsed alert details including severity, chart, context, and duration.',
|
|
159
|
-
params: { host: { type: 'string', description: 'Host name to summarize alerts for' } },
|
|
160
|
-
handler: async (params) => {
|
|
161
|
-
const host = params?.host ?? 'unknown'
|
|
162
|
-
log(`[netdata] alert summary requested for ${host}`)
|
|
163
|
-
return { host, summary: `No cached Netdata alerts for ${host}. Configure Netdata Cloud to send webhooks to RTerm's gateway.` }
|
|
164
|
-
},
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
// Tool: netdata_correlate — correlate a Netdata alert with RTerm's metrics + incidents.
|
|
168
|
-
registerTool({
|
|
169
|
-
name: 'netdata_correlate',
|
|
170
|
-
description: 'Correlate a Netdata alert with RTerm metrics ledger and incident history for root-cause analysis. Returns recent metrics, open incidents, and a correlation summary.',
|
|
171
|
-
params: {
|
|
172
|
-
alert: { type: 'object', description: 'Parsed Netdata alert payload (from webhook)' },
|
|
173
|
-
},
|
|
174
|
-
handler: async (params) => {
|
|
175
|
-
const parsed = parseNetdataAlert(params?.alert)
|
|
176
|
-
if (!parsed) return { error: 'Invalid Netdata alert payload' }
|
|
177
|
-
|
|
178
|
-
const metrics = readLedger ? readLedger('metrics') : null
|
|
179
|
-
const incidents = readLedger ? readLedger('incidents') : null
|
|
180
|
-
|
|
181
|
-
// readLedger returns ledger-like objects or plain data.
|
|
182
|
-
const metricsLedger = metrics && typeof metrics === 'object' && typeof metrics.snapshot === 'function' ? metrics : null
|
|
183
|
-
const incidentLedger = incidents && typeof incidents === 'object' && typeof incidents.list === 'function' ? incidents : null
|
|
184
|
-
|
|
185
|
-
const result = correlateWithRterm(parsed, metricsLedger, incidentLedger)
|
|
186
|
-
log(`[netdata] correlated alert "${parsed.alert ?? parsed.message}" on ${parsed.host}: ${result.correlation}`)
|
|
187
|
-
return { parsed, ...result }
|
|
188
|
-
},
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
// Trigger: netdata_critical_alert — fires when a Netdata webhook delivers a critical alert.
|
|
192
|
-
registerTrigger({
|
|
193
|
-
name: 'netdata_critical_alert',
|
|
194
|
-
description: 'Fires when Netdata sends a critical-severity alert via webhook. Use for auto-remediation playbooks.',
|
|
195
|
-
match: (event) => {
|
|
196
|
-
if (event?.source !== 'netdata') return false
|
|
197
|
-
return event.severity === 'critical'
|
|
198
|
-
},
|
|
199
|
-
action: 'run-playbook', // operator assigns a remediation playbook
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
// Trigger: netdata_warning_alert — fires when a Netdata webhook delivers a warning alert.
|
|
203
|
-
registerTrigger({
|
|
204
|
-
name: 'netdata_warning_alert',
|
|
205
|
-
description: 'Fires when Netdata sends a warning-severity alert via webhook. Use for investigation/diagnosis playbooks.',
|
|
206
|
-
match: (event) => {
|
|
207
|
-
if (event?.source !== 'netdata') return false
|
|
208
|
-
return event.severity === 'warning'
|
|
209
|
-
},
|
|
210
|
-
action: 'propose-change', // propose a MOP change for investigation
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
// Panel: netdata-alert-feed — dashboard panel showing recent Netdata alerts.
|
|
214
|
-
registerPanel({
|
|
215
|
-
name: 'netdata-alert-feed',
|
|
216
|
-
title: 'Netdata Alert Feed',
|
|
217
|
-
render: (data) => {
|
|
218
|
-
const alerts = Array.isArray(data) ? data : []
|
|
219
|
-
const rows = alerts.map((a) =>
|
|
220
|
-
`<tr><td>${a.host ?? ''}</td><td>${a.alert ?? a.message ?? ''}</td><td>${a.severity ?? a.status ?? ''}</td><td>${a.date ?? ''}</td></tr>`
|
|
221
|
-
).join('')
|
|
222
|
-
return `<div class="netdata-alert-feed"><h3>Netdata Alerts</h3><table><thead><tr><th>Host</th><th>Alert</th><th>Severity</th><th>Date</th></tr></thead><tbody>${rows}</tbody></table></div>`
|
|
223
|
-
},
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
log('[netdata] netdata-rterm plugin registered: 2 tools, 2 triggers, 1 panel')
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export default { register, parseNetdataAlert, mapSeverity, buildFingerprint, toTriggerEvent, correlateWithRterm }
|