rterm-backend 2.8.9 → 2.9.0

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/package.json CHANGED
@@ -1,19 +1,14 @@
1
1
  {
2
2
  "name": "rterm-backend",
3
- "version": "2.8.9",
4
- "description": "RTerm headless backend — run RTerm-as-a-service (AI agent, SSH/WinRM/Serial/local terminals, fleet orchestration, advanced automation with event-driven triggers + NATS event mesh, scheduled automation, SRE observability, Netdata integration, AWS APerf performance deep-dive, plugin system) and drive it over a WebSocket JSON-RPC gateway. No desktop UI required.",
5
- "license": "Apache-2.0",
6
- "type": "module",
3
+ "version": "2.9.0",
4
+ "description": "RTerm headless backend — run RTerm-as-a-service (AI agent, SSH/WinRM/Serial/local terminals, fleet orchestration, advanced automation with event-driven triggers + NATS event mesh, scheduled automation, SRE observability, Netdata integration, AWS APerf performance deep-dive, plugin system, governance/audit, Prometheus/OTel metrics export, secrets vault, on-call paging, AI cost budgets, GitOps, cloud inventory).",
5
+ "main": "bin/gybackend.js",
7
6
  "bin": {
8
7
  "gybackend": "bin/gybackend.cjs"
9
8
  },
10
- "main": "bin/gybackend.js",
11
- "files": [
12
- "bin/",
13
- "plugins/",
14
- "README.md",
15
- "LICENSE.md"
16
- ],
9
+ "scripts": {
10
+ "start": "node bin/gybackend.cjs"
11
+ },
17
12
  "engines": {
18
13
  "node": ">=18"
19
14
  },
@@ -22,17 +17,10 @@
22
17
  "linux",
23
18
  "win32"
24
19
  ],
25
- "dependencies": {
26
- "@nats-io/transport-node": "^3.4.0",
27
- "better-sqlite3": "^12.11.1",
28
- "cpu-features": "^0.0.10",
29
- "node-pty": "^1.2.0-beta.3",
30
- "ssh2": "^1.17.0",
31
- "tree-sitter-bash": "^0.25.1",
32
- "web-tree-sitter": "^0.26.3"
33
- },
34
- "optionalDependencies": {
35
- "serialport": "^13.0.0"
20
+ "license": "Apache-2.0",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/DrOlu/RTerm.git"
36
24
  },
37
25
  "keywords": [
38
26
  "rterm",
@@ -58,13 +46,12 @@
58
46
  "observability",
59
47
  "netdata",
60
48
  "aperf",
61
- "monitoring"
62
- ],
63
- "author": "Hyperspace Technologies <rterm@hyperspace.ng>",
64
- "homepage": "https://github.com/DrOlu/RTerm",
65
- "repository": {
66
- "type": "git",
67
- "url": "git+https://github.com/DrOlu/RTerm.git"
68
- },
69
- "bugs": "https://github.com/DrOlu/RTerm/issues"
49
+ "monitoring",
50
+ "prometheus",
51
+ "opentelemetry",
52
+ "secrets",
53
+ "on-call",
54
+ "gitops",
55
+ "cloud-inventory"
56
+ ]
70
57
  }
@@ -5,5 +5,5 @@ export function buildNatsStatusCommand(): string
5
5
  export function buildKafkaLagCommand(): string
6
6
  export function parsePipelineHealth(output: string): { status: string; jobs: any[]; running: number; failed: number }
7
7
  export function buildStrCase(txnId: string, decision: string, indicators?: string[], assignedTo?: string): any
8
- export function buildDecisionSummary(decisions: Array<{ decision: string }>): { total: number; blocks: number; reviews: number; approves: number; blockRate: number; reviewRate: number; approveRate: number }
8
+ export function buildDecisionSummary(decisions: Array<{ decision: string }> | null | undefined): { total: number; blocks: number; reviews: number; approves: number; blockRate: number; reviewRate: number; approveRate: number }
9
9
  export default any
@@ -53,10 +53,11 @@ export function buildStrCase(txnId, decision, indicators, assignedTo) {
53
53
 
54
54
  // --- Pure: build a decision summary ---
55
55
  export function buildDecisionSummary(decisions) {
56
- const total = decisions.length
57
- const blocks = decisions.filter((d) => d.decision === 'BLOCK').length
58
- const reviews = decisions.filter((d) => d.decision === 'REVIEW').length
59
- const approves = decisions.filter((d) => d.decision === 'APPROVE').length
56
+ const list = Array.isArray(decisions) ? decisions : []
57
+ const total = list.length
58
+ const blocks = list.filter((d) => d.decision === 'BLOCK').length
59
+ const reviews = list.filter((d) => d.decision === 'REVIEW').length
60
+ const approves = list.filter((d) => d.decision === 'APPROVE').length
60
61
  return {
61
62
  total,
62
63
  blocks,
@@ -4,7 +4,7 @@ export function buildUserInfoCommand(username: string, os?: string): string
4
4
  export function buildUserGroupsCommand(username: string, os?: string): string
5
5
  export function buildDisableUserCommand(username: string, os?: string): string
6
6
  export function buildAccessReviewCommand(os?: string): string
7
- export function parseUserInfo(output: string, os?: string): { username: string; groups: string[]; enabled: boolean; locked: boolean }
8
- export function parseAccessReview(output: string, os?: string): Array<{ username: string; groups?: string[]; enabled?: boolean }>
9
- export function isPrivileged(userInfo: { groups: string[] }, privilegedGroups?: string[]): boolean
7
+ export function parseUserInfo(output: string | null | undefined, os?: string | null | undefined): { username: string; groups: string[]; enabled: boolean; locked: boolean }
8
+ export function parseAccessReview(output: string | null | undefined, os?: string | null | undefined): Array<{ username: string; groups?: string[]; enabled?: boolean }>
9
+ export function isPrivileged(userInfo: { groups: string[] } | null | undefined, privilegedGroups?: string[] | null | undefined): boolean
10
10
  export default any
@@ -46,13 +46,14 @@ export function buildAccessReviewCommand(os = 'linux') {
46
46
 
47
47
  // --- Pure: parse user info output ---
48
48
  export function parseUserInfo(output, os = 'linux') {
49
- const o = String(os).toLowerCase()
49
+ const o = String(os ?? 'linux').toLowerCase()
50
+ const text = String(output ?? '')
50
51
  const info = { username: '', groups: [], enabled: true, locked: false }
51
52
 
52
53
  if (o === 'windows' || o === 'win32') {
53
- const nameMatch = output.match(/Name\s+:\s+(\S+)/)
54
- const enabledMatch = output.match(/Enabled\s+:\s+(True|False)/i)
55
- const lockedMatch = output.match(/LockedOut\s+:\s+(True|False)/i)
54
+ const nameMatch = text.match(/Name\s+:\s+(\S+)/)
55
+ const enabledMatch = text.match(/Enabled\s+:\s+(True|False)/i)
56
+ const lockedMatch = text.match(/LockedOut\s+:\s+(True|False)/i)
56
57
  if (nameMatch) info.username = nameMatch[1]
57
58
  if (enabledMatch) info.enabled = enabledMatch[1].toLowerCase() === 'true'
58
59
  if (lockedMatch) info.locked = lockedMatch[1].toLowerCase() === 'true'
@@ -60,7 +61,7 @@ export function parseUserInfo(output, os = 'linux') {
60
61
  }
61
62
 
62
63
  // Linux: parse id + groups + passwd -S output
63
- const lines = output.split(/\r?\n/)
64
+ const lines = text.split(/\r?\n/)
64
65
  for (const line of lines) {
65
66
  const l = line.trim()
66
67
  const idMatch = l.match(/uid=\d+\((\S+)\)/)
@@ -80,11 +81,12 @@ export function parseUserInfo(output, os = 'linux') {
80
81
 
81
82
  // --- Pure: parse access review output into a user list ---
82
83
  export function parseAccessReview(output, os = 'linux') {
83
- const o = String(os).toLowerCase()
84
+ const o = String(os ?? 'linux').toLowerCase()
85
+ const text = String(output ?? '')
84
86
  const users = []
85
87
 
86
88
  if (o === 'windows' || o === 'win32') {
87
- const lines = output.split(/\r?\n/)
89
+ const lines = text.split(/\r?\n/)
88
90
  for (const line of lines) {
89
91
  const l = line.trim()
90
92
  const match = l.match(/^(\S+)\s+(True|False)\s+/)
@@ -96,7 +98,7 @@ export function parseAccessReview(output, os = 'linux') {
96
98
  }
97
99
 
98
100
  // Linux: parse "user: group1 group2" lines
99
- const lines = output.split(/\r?\n/)
101
+ const lines = text.split(/\r?\n/)
100
102
  for (const line of lines) {
101
103
  const l = line.trim()
102
104
  const match = l.match(/^(\S+):\s*(.*)/)
@@ -109,7 +111,9 @@ export function parseAccessReview(output, os = 'linux') {
109
111
 
110
112
  // --- Pure: check if a user has privileged access ---
111
113
  export function isPrivileged(userInfo, privilegedGroups = ['sudo', 'wheel', 'admin', 'root', 'Administrators']) {
112
- return userInfo.groups.some((g) => privilegedGroups.some((pg) => g.toLowerCase().includes(pg.toLowerCase())))
114
+ const groups = Array.isArray(userInfo?.groups) ? userInfo.groups : []
115
+ const privs = Array.isArray(privilegedGroups) ? privilegedGroups : []
116
+ return groups.some((g) => privs.some((pg) => String(g).toLowerCase().includes(String(pg).toLowerCase())))
113
117
  }
114
118
 
115
119
  // --- Plugin entry ---
@@ -4,7 +4,7 @@ export function buildPatchStatusCommand(os: string): string
4
4
  export function buildPatchApplyCommand(os: string, opts?: { severity?: string; dryRun?: boolean }): string
5
5
  export function buildPrePatchCheckCommand(os: string): string
6
6
  export function buildPostPatchCheckCommand(os: string): string
7
- export function parsePatchStatus(output: string, os: string): { patches: Array<{ id: string; title: string; severity: string; os: string }>; summary: { total: number; critical: number; security: number; recommended: number } }
7
+ export function parsePatchStatus(output: string | null | undefined, os: string | null | undefined): { patches: Array<{ id: string; title: string; severity: string; os: string }>; summary: { total: number; critical: number; security: number; recommended: number } }
8
8
  export function buildPatchPlan(host: string, os: string, patchStatus: any, opts?: { severity?: string }): any
9
- export function buildComplianceReport(hostStatuses: Record<string, any>): any
9
+ export function buildComplianceReport(hostStatuses: Record<string, any> | null | undefined): any
10
10
  export default any
@@ -70,8 +70,8 @@ export function buildPostPatchCheckCommand(os) {
70
70
 
71
71
  // --- Pure: parse patch status output into structured results ---
72
72
  export function parsePatchStatus(output, os) {
73
- const o = String(os).toLowerCase()
74
- const lines = output.split(/\r?\n/).filter((l) => l.trim().length > 0)
73
+ const o = String(os ?? '').toLowerCase()
74
+ const lines = String(output ?? '').split(/\r?\n/).filter((l) => l.trim().length > 0)
75
75
  const patches = []
76
76
  let summary = { total: 0, critical: 0, security: 0, recommended: 0 }
77
77
 
@@ -151,7 +151,7 @@ export function buildPatchPlan(host, os, patchStatus, opts = {}) {
151
151
 
152
152
  // --- Pure: build a compliance report for a fleet ---
153
153
  export function buildComplianceReport(hostStatuses) {
154
- const hosts = Object.entries(hostStatuses).map(([host, status]) => ({
154
+ const hosts = Object.entries(hostStatuses ?? {}).map(([host, status]) => ({
155
155
  host,
156
156
  os: status.os ?? 'unknown',
157
157
  totalPatches: status.summary?.total ?? 0,
@@ -5,5 +5,5 @@ export function routeRequest(request: any): { route: string; risk: string; reaso
5
5
  export function buildRequestId(): string
6
6
  export function buildApprovalRecord(requestId: string, approvedBy: string, rationale: string, decision: string): any
7
7
  export function buildQueueEntry(request: any, requestId: string): any
8
- export function filterQueue(queue: any[], filter?: any): any[]
8
+ export function filterQueue(queue: any[] | null | undefined, filter?: any): any[]
9
9
  export default any
@@ -9,9 +9,10 @@
9
9
 
10
10
  // --- Pure: classify a request by risk ---
11
11
  export function classifyRequest(request) {
12
- const type = String(request.type ?? '').toLowerCase()
13
- const urgency = String(request.urgency ?? 'low').toLowerCase()
14
- const target = String(request.target ?? '').toLowerCase()
12
+ const req = request ?? {}
13
+ const type = String(req.type ?? '').toLowerCase()
14
+ const urgency = String(req.urgency ?? 'low').toLowerCase()
15
+ const target = String(req.target ?? '').toLowerCase()
15
16
 
16
17
  // Destructive operations = high risk
17
18
  if (['delete', 'drop', 'purge', 'format', 'destroy'].some((w) => type.includes(w))) return 'high'
@@ -32,8 +33,9 @@ export function classifyRequest(request) {
32
33
 
33
34
  // --- Pure: route a request based on risk ---
34
35
  export function routeRequest(request) {
35
- const risk = classifyRequest(request)
36
- const urgency = String(request.urgency ?? 'low').toLowerCase()
36
+ const req = request ?? {}
37
+ const risk = classifyRequest(req)
38
+ const urgency = String(req.urgency ?? 'low').toLowerCase()
37
39
 
38
40
  if (risk === 'low') return { route: 'auto_approve', risk, reason: 'low-risk read-only operation' }
39
41
  if (risk === 'medium') {
@@ -63,18 +65,19 @@ export function buildApprovalRecord(requestId, approvedBy, rationale, decision)
63
65
 
64
66
  // --- Pure: build a request queue entry ---
65
67
  export function buildQueueEntry(request, requestId) {
66
- const { route, risk, reason } = routeRequest(request)
68
+ const req = request ?? {}
69
+ const { route, risk, reason } = routeRequest(req)
67
70
  return {
68
71
  id: requestId,
69
- type: request.type,
70
- target: request.target,
71
- justification: request.justification ?? '',
72
- urgency: request.urgency ?? 'low',
72
+ type: req.type,
73
+ target: req.target,
74
+ justification: req.justification ?? '',
75
+ urgency: req.urgency ?? 'low',
73
76
  risk,
74
77
  route,
75
78
  routeReason: reason,
76
79
  status: route === 'auto_approve' ? 'auto_approved' : 'pending',
77
- submittedBy: request.submittedBy ?? 'unknown',
80
+ submittedBy: req.submittedBy ?? 'unknown',
78
81
  submittedAt: Date.now(),
79
82
  ...(route === 'auto_approve' ? { approvedAt: Date.now(), approvedBy: 'system' } : {}),
80
83
  }
@@ -82,11 +85,12 @@ export function buildQueueEntry(request, requestId) {
82
85
 
83
86
  // --- Pure: filter the request queue ---
84
87
  export function filterQueue(queue, filter = {}) {
85
- let out = [...queue]
86
- if (filter.status) out = out.filter((r) => r.status === filter.status)
87
- if (filter.risk) out = out.filter((r) => r.risk === filter.risk)
88
- if (filter.urgency) out = out.filter((r) => r.urgency === filter.urgency)
89
- if (filter.target) out = out.filter((r) => r.target.includes(filter.target))
88
+ let out = Array.isArray(queue) ? [...queue] : []
89
+ const f = filter ?? {}
90
+ if (f.status) out = out.filter((r) => r.status === f.status)
91
+ if (f.risk) out = out.filter((r) => r.risk === f.risk)
92
+ if (f.urgency) out = out.filter((r) => r.urgency === f.urgency)
93
+ if (f.target) out = out.filter((r) => String(r.target ?? '').includes(f.target))
90
94
  return out
91
95
  }
92
96
 
@@ -214,8 +214,8 @@ export function searchIamPolicies(query, policies = IAM_POLICIES) {
214
214
 
215
215
  // --- Pure: build the command for a SOP step (with variable substitution) ---
216
216
  export function buildStepCommand(step, vars = {}) {
217
- let cmd = step.command
218
- for (const [key, value] of Object.entries(vars)) {
217
+ let cmd = String(step?.command ?? '')
218
+ for (const [key, value] of Object.entries(vars ?? {})) {
219
219
  cmd = cmd.replace(new RegExp(`\\{${key}\\}`, 'g'), String(value))
220
220
  }
221
221
  return cmd