neuralos 3.3.7 → 3.3.9

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/bin/gybackend.cjs CHANGED
@@ -377393,6 +377393,7 @@ function pickBackendSnapshot(raw) {
377393
377393
  nats: raw.nats,
377394
377394
  synapse: raw.synapse,
377395
377395
  numbat: raw.numbat,
377396
+ monid: raw.monid,
377396
377397
  gateway: raw.gateway,
377397
377398
  layout: raw.layout,
377398
377399
  recursionLimit: raw.recursionLimit,
@@ -377476,6 +377477,7 @@ function normalizeBackendSettings(settings) {
377476
377477
  next.nats = normalizeNatsSettings(next.nats);
377477
377478
  next.synapse = normalizeSynapseSettings(next.synapse);
377478
377479
  next.numbat = normalizeNumbatSettings(next.numbat);
377480
+ next.monid = normalizeMonidSettings(next.monid);
377479
377481
  next.schemaVersion = BACKEND_SETTINGS_SCHEMA_VERSION;
377480
377482
  return next;
377481
377483
  }
@@ -377730,6 +377732,16 @@ function normalizeNumbatSettings(raw) {
377730
377732
  ...minSeverity ? { minSeverity } : {}
377731
377733
  };
377732
377734
  }
377735
+ function normalizeMonidSettings(raw) {
377736
+ const src = isObject5(raw) ? raw : {};
377737
+ const binaryPath = typeof src.binaryPath === "string" && src.binaryPath.trim() ? src.binaryPath.trim() : void 0;
377738
+ const keyLabel = typeof src.keyLabel === "string" && src.keyLabel.trim() ? src.keyLabel.trim() : void 0;
377739
+ return {
377740
+ enabled: src.enabled !== false,
377741
+ ...binaryPath ? { binaryPath } : {},
377742
+ ...keyLabel ? { keyLabel } : {}
377743
+ };
377744
+ }
377733
377745
  function migrateBackendToV3(settings) {
377734
377746
  const next = { ...settings };
377735
377747
  delete next.language;
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neuralos",
3
- "version": "3.3.7",
3
+ "version": "3.3.9",
4
4
  "description": "AI-native terminal & agentic-AI operations platform for Forward Deployed Engineers & SREs: AIOps closed-loop remediation, AI SRE, self-healing infrastructure, runbook automation, ChatOps; executes over SSH/WinRM/serial under policy with tamper-evident audit.",
5
5
  "keywords": [
6
6
  "forward-deployed-engineer",
@@ -0,0 +1,228 @@
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
+ }
@@ -0,0 +1,75 @@
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')
@@ -0,0 +1,15 @@
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
+ }
Binary file