ciscollm-cli 1.0.1 → 1.0.2
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 +147 -78
- package/dist/cli/ui/ui.d.ts +1 -0
- package/dist/cli/ui/ui.js +19 -4
- package/dist/cli/ui/ui.js.map +1 -1
- package/dist/core/agent/AgentLoop.d.ts +6 -0
- package/dist/core/agent/AgentLoop.js +137 -15
- package/dist/core/agent/AgentLoop.js.map +1 -1
- package/dist/core/agent/CommandReferenceEngine.d.ts +2 -0
- package/dist/core/agent/CommandReferenceEngine.js +73 -4
- package/dist/core/agent/CommandReferenceEngine.js.map +1 -1
- package/dist/core/agent/HierarchicalAgentManager.d.ts +11 -0
- package/dist/core/agent/HierarchicalAgentManager.js +70 -0
- package/dist/core/agent/HierarchicalAgentManager.js.map +1 -0
- package/dist/core/agent/MultiAgentCoordinator.d.ts +5 -1
- package/dist/core/agent/MultiAgentCoordinator.js +55 -0
- package/dist/core/agent/MultiAgentCoordinator.js.map +1 -1
- package/dist/core/agent/PromptEngine.d.ts +1 -1
- package/dist/core/agent/PromptEngine.js +64 -48
- package/dist/core/agent/PromptEngine.js.map +1 -1
- package/dist/core/guardrails/AuditLogger.d.ts +15 -0
- package/dist/core/guardrails/AuditLogger.js +66 -0
- package/dist/core/guardrails/AuditLogger.js.map +1 -0
- package/dist/core/guardrails/CommandFirewall.js +24 -0
- package/dist/core/guardrails/CommandFirewall.js.map +1 -1
- package/dist/core/guardrails/PreExecutionValidator.d.ts +9 -0
- package/dist/core/guardrails/PreExecutionValidator.js +58 -0
- package/dist/core/guardrails/PreExecutionValidator.js.map +1 -0
- package/dist/core/rollback/StateDiff.d.ts +47 -0
- package/dist/core/rollback/StateDiff.js +121 -0
- package/dist/core/rollback/StateDiff.js.map +1 -0
- package/dist/core/rollback/TransactionManager.d.ts +2 -0
- package/dist/core/rollback/TransactionManager.js +113 -41
- package/dist/core/rollback/TransactionManager.js.map +1 -1
- package/dist/core/topology/TopologyDiscovery.d.ts +5 -0
- package/dist/core/topology/TopologyDiscovery.js +54 -0
- package/dist/core/topology/TopologyDiscovery.js.map +1 -0
- package/dist/index.js +95 -24
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/llm/LLMClient.js +52 -2
- package/dist/infrastructure/llm/LLMClient.js.map +1 -1
- package/dist/infrastructure/protocols/BaseSession.js +1 -1
- package/dist/infrastructure/protocols/BaseSession.js.map +1 -1
- package/dist/infrastructure/protocols/CmlSession.d.ts +11 -0
- package/dist/infrastructure/protocols/CmlSession.js +60 -0
- package/dist/infrastructure/protocols/CmlSession.js.map +1 -0
- package/dist/infrastructure/protocols/MockSession.d.ts +33 -0
- package/dist/infrastructure/protocols/MockSession.js +427 -33
- package/dist/infrastructure/protocols/MockSession.js.map +1 -1
- package/dist/infrastructure/protocols/NetconfSession.d.ts +9 -0
- package/dist/infrastructure/protocols/NetconfSession.js +65 -0
- package/dist/infrastructure/protocols/NetconfSession.js.map +1 -0
- package/dist/infrastructure/protocols/PlinkSerial.js +107 -23
- package/dist/infrastructure/protocols/PlinkSerial.js.map +1 -1
- package/dist/shared/types.d.ts +12 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PromptEngine.js","sourceRoot":"","sources":["../../../src/core/agent/PromptEngine.ts"],"names":[],"mappings":";;;AAAA,MAAa,YAAY;IACf,MAAM,CAAC,eAAe,CACvB,SAAiB,EACjB,wBAAgC,+BAA+B,EAC/D,sBAA+B,KAAK;
|
|
1
|
+
{"version":3,"file":"PromptEngine.js","sourceRoot":"","sources":["../../../src/core/agent/PromptEngine.ts"],"names":[],"mappings":";;;AAAA,MAAa,YAAY;IACf,MAAM,CAAC,eAAe,CACvB,SAAiB,EACjB,wBAAgC,+BAA+B,EAC/D,sBAA+B,KAAK,EACpC,eAAuB,8BAA8B;QAEtD,OAAO;;;;;;;;;;;;;;;;EAgBb,SAAS;;;;;;gCAMqB,mBAAmB,CAAC,CAAC,CAAC,gDAAgD,CAAC,CAAC,CAAC,0BAA0B;EACjI,qBAAqB;;;;;EAKrB,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+OAmCiO,CAAC;IAC5O,CAAC;CACJ;AAxED,oCAwEC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface AuditLogEntry {
|
|
2
|
+
timestamp: string;
|
|
3
|
+
deviceId: string;
|
|
4
|
+
role: string;
|
|
5
|
+
thought?: string;
|
|
6
|
+
command: string;
|
|
7
|
+
status: 'SUCCESS' | 'FAILED' | 'BLOCKED' | 'ROLLBACK';
|
|
8
|
+
outputSnippet?: string;
|
|
9
|
+
reason?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class AuditLogger {
|
|
12
|
+
private static logFile;
|
|
13
|
+
static setLogFile(filePath: string): void;
|
|
14
|
+
static log(entry: AuditLogEntry): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AuditLogger = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
class AuditLogger {
|
|
40
|
+
static logFile = path.resolve(process.cwd(), 'audit.log');
|
|
41
|
+
static setLogFile(filePath) {
|
|
42
|
+
this.logFile = path.resolve(process.cwd(), filePath);
|
|
43
|
+
}
|
|
44
|
+
static log(entry) {
|
|
45
|
+
const formattedTimestamp = entry.timestamp || new Date().toISOString();
|
|
46
|
+
const outputCleaned = entry.outputSnippet
|
|
47
|
+
? entry.outputSnippet.replace(/\r?\n/g, ' ').substring(0, 100) + '...'
|
|
48
|
+
: '';
|
|
49
|
+
const thoughtCleaned = entry.thought
|
|
50
|
+
? entry.thought.replace(/\r?\n/g, ' ').substring(0, 150) + '...'
|
|
51
|
+
: '';
|
|
52
|
+
const logMessage = `[${formattedTimestamp}] [Device: ${entry.deviceId}] [Role: ${entry.role.toUpperCase()}] [Status: ${entry.status}]
|
|
53
|
+
Thought: ${thoughtCleaned || 'N/A'}
|
|
54
|
+
Command: "${entry.command}"
|
|
55
|
+
Result: ${outputCleaned || entry.reason || 'N/A'}
|
|
56
|
+
--------------------------------------------------------------------------------\n`;
|
|
57
|
+
try {
|
|
58
|
+
fs.appendFileSync(this.logFile, logMessage, 'utf8');
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
console.error('[AuditLogger Error]: Failed to write to audit log:', e);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.AuditLogger = AuditLogger;
|
|
66
|
+
//# sourceMappingURL=AuditLogger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuditLogger.js","sourceRoot":"","sources":["../../../src/core/guardrails/AuditLogger.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAyB;AACzB,2CAA6B;AAa7B,MAAa,WAAW;IACZ,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,CAAC,CAAC;IAE3D,MAAM,CAAC,UAAU,CAAC,QAAgB;QACrC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAC;IACzD,CAAC;IAGM,MAAM,CAAC,GAAG,CAAC,KAAoB;QAClC,MAAM,kBAAkB,GAAG,KAAK,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACvE,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa;YACrC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK;YACtE,CAAC,CAAC,EAAE,CAAC;QACT,MAAM,cAAc,GAAG,KAAK,CAAC,OAAO;YAChC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK;YAChE,CAAC,CAAC,EAAE,CAAC;QAET,MAAM,UAAU,GAAG,IAAI,kBAAkB,cAAc,KAAK,CAAC,QAAQ,YAAY,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,KAAK,CAAC,MAAM;aAC9H,cAAc,IAAI,KAAK;cACtB,KAAK,CAAC,OAAO;aACd,aAAa,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK;mFACgC,CAAC;QAE5E,IAAI,CAAC;YACD,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;QACxD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,oDAAoD,EAAE,CAAC,CAAC,CAAC;QAC3E,CAAC;IACL,CAAC;;AA5BL,kCA6BC"}
|
|
@@ -47,6 +47,24 @@ class CommandFirewall {
|
|
|
47
47
|
}
|
|
48
48
|
checkCommand(command, currentInterfaceContext) {
|
|
49
49
|
const normalized = command.toLowerCase().trim();
|
|
50
|
+
if (normalized.startsWith('no ip route 0.0.0.0') || normalized.startsWith('no ip route 0.0.0.0 0.0.0.0')) {
|
|
51
|
+
return {
|
|
52
|
+
dangerous: true,
|
|
53
|
+
reason: 'Attempting to remove the default static route (0.0.0.0/0) which may sever SSH/telnet connectivity.'
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (normalized.startsWith('no aaa new-model') || normalized.startsWith('crypto key zeroize')) {
|
|
57
|
+
return {
|
|
58
|
+
dangerous: true,
|
|
59
|
+
reason: 'Attempting to disable AAA security or zeroize crypto keys, which can lock out admin access.'
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (normalized.startsWith('no access-list') || normalized.startsWith('no ip access-group')) {
|
|
63
|
+
return {
|
|
64
|
+
dangerous: true,
|
|
65
|
+
reason: 'Attempting to remove or disable an access-list which could expose or lock the management interface.'
|
|
66
|
+
};
|
|
67
|
+
}
|
|
50
68
|
const matchedDestructive = constants_1.DESTRUCTIVE_TOKENS.find(token => normalized.startsWith(token) || normalized.includes(` ${token}`));
|
|
51
69
|
if (matchedDestructive) {
|
|
52
70
|
return {
|
|
@@ -89,6 +107,12 @@ class CommandFirewall {
|
|
|
89
107
|
p.startsWith(interfaceName));
|
|
90
108
|
}
|
|
91
109
|
async verifyWithHuman(command, reason) {
|
|
110
|
+
const isNonInteractive = process.env.CISCOLLM_NON_INTERACTIVE === 'true';
|
|
111
|
+
if (isNonInteractive) {
|
|
112
|
+
console.warn('\n' + chalk_1.default.bold.red(`⚠️ [GUARDRAIL BLOCK]: Non-interactive mode active. Automatically rejecting high-risk command: "${command}"`));
|
|
113
|
+
console.warn(`- Protection Rule Match: ${chalk_1.default.cyan(reason)}\n`);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
92
116
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
93
117
|
return new Promise((resolve) => {
|
|
94
118
|
console.warn('\n' + chalk_1.default.bold.red('⚠️ [GUARDRAIL WARNING]: High-Risk Command Blocked'));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommandFirewall.js","sourceRoot":"","sources":["../../../src/core/guardrails/CommandFirewall.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,mDAAqC;AACrC,kDAA0B;AAC1B,sDAA0F;AAE1F,MAAa,eAAe;IAChB,mBAAmB,CAAW;IAEtC,YAAY,sBAAgC,wCAA4B;QACpE,IAAI,CAAC,mBAAmB,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IACpF,CAAC;IAGM,YAAY,CAAC,OAAe,EAAE,uBAAsC;QACvE,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;QAGhD,MAAM,kBAAkB,GAAG,8BAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CACvD,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,KAAK,EAAE,CAAC,CACnE,CAAC;QACF,IAAI,kBAAkB,EAAE,CAAC;YACrB,OAAO;gBACH,SAAS,EAAE,IAAI;gBACf,MAAM,EAAE,kCAAkC,kBAAkB,IAAI;aACnE,CAAC;QACN,CAAC;
|
|
1
|
+
{"version":3,"file":"CommandFirewall.js","sourceRoot":"","sources":["../../../src/core/guardrails/CommandFirewall.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { NetworkTopology } from '../../shared/types';
|
|
2
|
+
export interface ValidationResult {
|
|
3
|
+
safe: boolean;
|
|
4
|
+
warnLevel: 'INFO' | 'WARNING' | 'CRITICAL';
|
|
5
|
+
reason?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class PreExecutionValidator {
|
|
8
|
+
static validateCommand(command: string, deviceId: string, topology: NetworkTopology, currentInterfaceContext: string | null): ValidationResult;
|
|
9
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PreExecutionValidator = void 0;
|
|
4
|
+
class PreExecutionValidator {
|
|
5
|
+
static validateCommand(command, deviceId, topology, currentInterfaceContext) {
|
|
6
|
+
const normalized = command.toLowerCase().trim();
|
|
7
|
+
if (normalized.startsWith('no ip route 0.0.0.0') || normalized.startsWith('no ip route 0.0.0.0 0.0.0.0')) {
|
|
8
|
+
return {
|
|
9
|
+
safe: false,
|
|
10
|
+
warnLevel: 'CRITICAL',
|
|
11
|
+
reason: `Command attempts to remove the default gateway route. This will disconnect remote SSH/Telnet sessions.`
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (normalized.startsWith('router ospf') || normalized.startsWith('router bgp') || normalized.startsWith('router rip')) {
|
|
15
|
+
return {
|
|
16
|
+
safe: true,
|
|
17
|
+
warnLevel: 'WARNING',
|
|
18
|
+
reason: `Entering dynamic routing configuration mode. Incorrect network declarations can disrupt routing convergence across the backbone.`
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const interfaceMatch = /^interface\s+([A-Za-z0-9\/\.\-]+)/i.exec(command.trim());
|
|
22
|
+
let targetedInterface = interfaceMatch ? interfaceMatch[1].toLowerCase().trim() : null;
|
|
23
|
+
if (currentInterfaceContext && !targetedInterface) {
|
|
24
|
+
targetedInterface = currentInterfaceContext.toLowerCase().trim();
|
|
25
|
+
}
|
|
26
|
+
if (targetedInterface && normalized.includes('shutdown')) {
|
|
27
|
+
const connectedLink = topology.links.find(link => (link.localDeviceId.toLowerCase() === deviceId.toLowerCase() && link.localInterface.toLowerCase() === targetedInterface) ||
|
|
28
|
+
(link.remoteDeviceId.toLowerCase() === deviceId.toLowerCase() && link.remoteInterface.toLowerCase() === targetedInterface));
|
|
29
|
+
if (connectedLink) {
|
|
30
|
+
const neighborId = connectedLink.localDeviceId.toLowerCase() === deviceId.toLowerCase()
|
|
31
|
+
? connectedLink.remoteDeviceId
|
|
32
|
+
: connectedLink.localDeviceId;
|
|
33
|
+
return {
|
|
34
|
+
safe: false,
|
|
35
|
+
warnLevel: 'CRITICAL',
|
|
36
|
+
reason: `Interface "${targetedInterface}" is actively connected to neighbor device "${neighborId}" via ${connectedLink.protocol.toUpperCase()}. Shutting it down will break network topology adjacency.`
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (targetedInterface && (normalized.startsWith('no ip address') || normalized.startsWith('ip address'))) {
|
|
41
|
+
const connectedLink = topology.links.find(link => (link.localDeviceId.toLowerCase() === deviceId.toLowerCase() && link.localInterface.toLowerCase() === targetedInterface) ||
|
|
42
|
+
(link.remoteDeviceId.toLowerCase() === deviceId.toLowerCase() && link.remoteInterface.toLowerCase() === targetedInterface));
|
|
43
|
+
if (connectedLink && normalized.startsWith('no ip address')) {
|
|
44
|
+
return {
|
|
45
|
+
safe: false,
|
|
46
|
+
warnLevel: 'CRITICAL',
|
|
47
|
+
reason: `Removing IP address from "${targetedInterface}", which is an active network uplink to neighbor "${connectedLink.localDeviceId === deviceId ? connectedLink.remoteDeviceId : connectedLink.localDeviceId}".`
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
safe: true,
|
|
53
|
+
warnLevel: 'INFO'
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.PreExecutionValidator = PreExecutionValidator;
|
|
58
|
+
//# sourceMappingURL=PreExecutionValidator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PreExecutionValidator.js","sourceRoot":"","sources":["../../../src/core/guardrails/PreExecutionValidator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { SessionState } from '../../shared/types';
|
|
2
|
+
export interface InterfaceStateSnapshot {
|
|
3
|
+
name: string;
|
|
4
|
+
ip: string | null;
|
|
5
|
+
subnet: string | null;
|
|
6
|
+
adminShutdown: boolean;
|
|
7
|
+
lineProtocolUp: boolean;
|
|
8
|
+
description: string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface RoutingStateSnapshot {
|
|
11
|
+
network: string;
|
|
12
|
+
mask: string;
|
|
13
|
+
nextHop: string | null;
|
|
14
|
+
}
|
|
15
|
+
export interface DeviceStateSnapshot {
|
|
16
|
+
deviceId: string;
|
|
17
|
+
timestamp: string;
|
|
18
|
+
sessionState: SessionState;
|
|
19
|
+
interfaces: InterfaceStateSnapshot[];
|
|
20
|
+
routes: RoutingStateSnapshot[];
|
|
21
|
+
vlans: number[];
|
|
22
|
+
}
|
|
23
|
+
export interface StateDiffResult {
|
|
24
|
+
modifiedInterfaces: Array<{
|
|
25
|
+
name: string;
|
|
26
|
+
changes: Array<{
|
|
27
|
+
field: string;
|
|
28
|
+
before: any;
|
|
29
|
+
after: any;
|
|
30
|
+
}>;
|
|
31
|
+
}>;
|
|
32
|
+
addedRoutes: RoutingStateSnapshot[];
|
|
33
|
+
removedRoutes: RoutingStateSnapshot[];
|
|
34
|
+
addedVlans: number[];
|
|
35
|
+
removedVlans: number[];
|
|
36
|
+
hostnameChanged: {
|
|
37
|
+
before: string;
|
|
38
|
+
after: string;
|
|
39
|
+
} | null;
|
|
40
|
+
}
|
|
41
|
+
export declare class StateDiff {
|
|
42
|
+
static diff(before: DeviceStateSnapshot, after: DeviceStateSnapshot): StateDiffResult;
|
|
43
|
+
/**
|
|
44
|
+
* Helper to render the diff output as a readable table/string.
|
|
45
|
+
*/
|
|
46
|
+
static renderDiff(diff: StateDiffResult): string;
|
|
47
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.StateDiff = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
class StateDiff {
|
|
9
|
+
static diff(before, after) {
|
|
10
|
+
const result = {
|
|
11
|
+
modifiedInterfaces: [],
|
|
12
|
+
addedRoutes: [],
|
|
13
|
+
removedRoutes: [],
|
|
14
|
+
addedVlans: [],
|
|
15
|
+
removedVlans: [],
|
|
16
|
+
hostnameChanged: null
|
|
17
|
+
};
|
|
18
|
+
if (before.sessionState.hostname !== after.sessionState.hostname) {
|
|
19
|
+
result.hostnameChanged = {
|
|
20
|
+
before: before.sessionState.hostname,
|
|
21
|
+
after: after.sessionState.hostname
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
for (const afterIntf of after.interfaces) {
|
|
25
|
+
const beforeIntf = before.interfaces.find(i => i.name === afterIntf.name);
|
|
26
|
+
if (!beforeIntf) {
|
|
27
|
+
result.modifiedInterfaces.push({
|
|
28
|
+
name: afterIntf.name,
|
|
29
|
+
changes: [{ field: 'created', before: null, after: true }]
|
|
30
|
+
});
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const changes = [];
|
|
34
|
+
if (beforeIntf.ip !== afterIntf.ip)
|
|
35
|
+
changes.push({ field: 'ip', before: beforeIntf.ip, after: afterIntf.ip });
|
|
36
|
+
if (beforeIntf.subnet !== afterIntf.subnet)
|
|
37
|
+
changes.push({ field: 'subnet', before: beforeIntf.subnet, after: afterIntf.subnet });
|
|
38
|
+
if (beforeIntf.adminShutdown !== afterIntf.adminShutdown)
|
|
39
|
+
changes.push({ field: 'adminShutdown', before: beforeIntf.adminShutdown, after: afterIntf.adminShutdown });
|
|
40
|
+
if (beforeIntf.description !== afterIntf.description)
|
|
41
|
+
changes.push({ field: 'description', before: beforeIntf.description, after: afterIntf.description });
|
|
42
|
+
if (changes.length > 0) {
|
|
43
|
+
result.modifiedInterfaces.push({ name: afterIntf.name, changes });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const afterRoute of after.routes) {
|
|
47
|
+
const exists = before.routes.some(r => r.network === afterRoute.network && r.mask === afterRoute.mask && r.nextHop === afterRoute.nextHop);
|
|
48
|
+
if (!exists) {
|
|
49
|
+
result.addedRoutes.push(afterRoute);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const beforeRoute of before.routes) {
|
|
53
|
+
const exists = after.routes.some(r => r.network === beforeRoute.network && r.mask === beforeRoute.mask && r.nextHop === beforeRoute.nextHop);
|
|
54
|
+
if (!exists) {
|
|
55
|
+
result.removedRoutes.push(beforeRoute);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
for (const vlan of after.vlans) {
|
|
59
|
+
if (!before.vlans.includes(vlan)) {
|
|
60
|
+
result.addedVlans.push(vlan);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const vlan of before.vlans) {
|
|
64
|
+
if (!after.vlans.includes(vlan)) {
|
|
65
|
+
result.removedVlans.push(vlan);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Helper to render the diff output as a readable table/string.
|
|
72
|
+
*/
|
|
73
|
+
static renderDiff(diff) {
|
|
74
|
+
const lines = [];
|
|
75
|
+
if (diff.hostnameChanged) {
|
|
76
|
+
lines.push(chalk_1.default.cyan(` Device Hostname Changed: `) + chalk_1.default.red(`"${diff.hostnameChanged.before}"`) + chalk_1.default.gray(' ➔ ') + chalk_1.default.green(`"${diff.hostnameChanged.after}"`));
|
|
77
|
+
}
|
|
78
|
+
if (diff.modifiedInterfaces.length > 0) {
|
|
79
|
+
lines.push(chalk_1.default.cyan(' Interface State Changes:'));
|
|
80
|
+
for (const item of diff.modifiedInterfaces) {
|
|
81
|
+
lines.push(chalk_1.default.bold.yellow(` * ${item.name}:`));
|
|
82
|
+
for (const change of item.changes) {
|
|
83
|
+
const beforeVal = change.before ?? 'unassigned';
|
|
84
|
+
const afterVal = change.after ?? 'unassigned';
|
|
85
|
+
let formattedLine = '';
|
|
86
|
+
if (change.field === 'adminShutdown') {
|
|
87
|
+
formattedLine = ` - shutdown: ` + (change.before ? chalk_1.default.red('YES') : chalk_1.default.green('NO')) + chalk_1.default.gray(' ➔ ') + (change.after ? chalk_1.default.red('YES') : chalk_1.default.green('NO'));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
formattedLine = ` - ${change.field}: ` + chalk_1.default.red(`"${beforeVal}"`) + chalk_1.default.gray(' ➔ ') + chalk_1.default.green(`"${afterVal}"`);
|
|
91
|
+
}
|
|
92
|
+
lines.push(formattedLine);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (diff.addedRoutes.length > 0) {
|
|
97
|
+
lines.push(chalk_1.default.green(' Routes Added (+)'));
|
|
98
|
+
for (const r of diff.addedRoutes) {
|
|
99
|
+
lines.push(chalk_1.default.green(` + ip route ${r.network} ${r.mask} ${r.nextHop || ''}`));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (diff.removedRoutes.length > 0) {
|
|
103
|
+
lines.push(chalk_1.default.red(' Routes Removed (-)'));
|
|
104
|
+
for (const r of diff.removedRoutes) {
|
|
105
|
+
lines.push(chalk_1.default.red(` - ip route ${r.network} ${r.mask} ${r.nextHop || ''}`));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (diff.addedVlans.length > 0) {
|
|
109
|
+
lines.push(chalk_1.default.green(` VLANs Added (+): ${diff.addedVlans.join(', ')}`));
|
|
110
|
+
}
|
|
111
|
+
if (diff.removedVlans.length > 0) {
|
|
112
|
+
lines.push(chalk_1.default.red(` VLANs Removed (-): ${diff.removedVlans.join(', ')}`));
|
|
113
|
+
}
|
|
114
|
+
if (lines.length === 0) {
|
|
115
|
+
return ' No configuration differences detected.';
|
|
116
|
+
}
|
|
117
|
+
return lines.join('\n');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
exports.StateDiff = StateDiff;
|
|
121
|
+
//# sourceMappingURL=StateDiff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StateDiff.js","sourceRoot":"","sources":["../../../src/core/rollback/StateDiff.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { BaseSession } from '../../infrastructure/protocols/BaseSession';
|
|
2
2
|
export declare class TransactionManager {
|
|
3
3
|
private configChangeLog;
|
|
4
|
+
private mutationsWithContext;
|
|
5
|
+
private contextStack;
|
|
4
6
|
private targetInterface;
|
|
5
7
|
private backupCreated;
|
|
6
8
|
private readonly backupFilename;
|
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.TransactionManager = void 0;
|
|
4
4
|
class TransactionManager {
|
|
5
5
|
configChangeLog = [];
|
|
6
|
+
mutationsWithContext = [];
|
|
7
|
+
contextStack = [];
|
|
6
8
|
targetInterface = null;
|
|
7
9
|
backupCreated = false;
|
|
8
10
|
backupFilename = 'flash:backup-agent.cfg';
|
|
@@ -13,6 +15,12 @@ class TransactionManager {
|
|
|
13
15
|
await session.execute('enable');
|
|
14
16
|
}
|
|
15
17
|
try {
|
|
18
|
+
console.log('[TransactionManager]: Checking flash storage reachability...');
|
|
19
|
+
const flashCheck = await session.execute('dir flash:');
|
|
20
|
+
if (flashCheck.includes('% Invalid') || flashCheck.includes('No such file') || flashCheck.includes('Error')) {
|
|
21
|
+
console.warn('[TransactionManager Warning]: Flash storage is not accessible or not found. Skipping backup creation.');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
16
24
|
console.log('[TransactionManager]: Creating device running-config backup on flash...');
|
|
17
25
|
const rawOutput = await session.execute(`copy running-config ${this.backupFilename}`);
|
|
18
26
|
if (rawOutput.includes('Destination filename') || rawOutput.includes('?')) {
|
|
@@ -38,13 +46,52 @@ class TransactionManager {
|
|
|
38
46
|
}
|
|
39
47
|
}
|
|
40
48
|
trackMutation(command) {
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
|
|
49
|
+
const clean = command.trim();
|
|
50
|
+
const lower = clean.toLowerCase();
|
|
51
|
+
if (lower.startsWith('interface ') || lower.startsWith('int ')) {
|
|
52
|
+
const parts = clean.split(/\s+/);
|
|
53
|
+
if (parts.length >= 2) {
|
|
54
|
+
if (this.contextStack.length > 0 && this.contextStack[this.contextStack.length - 1].startsWith('interface')) {
|
|
55
|
+
this.contextStack.pop();
|
|
56
|
+
}
|
|
57
|
+
this.contextStack.push(`interface ${parts[1]}`);
|
|
58
|
+
this.targetInterface = parts[1];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else if (lower.startsWith('router ')) {
|
|
62
|
+
if (this.contextStack.length > 0 && this.contextStack[this.contextStack.length - 1].startsWith('router')) {
|
|
63
|
+
this.contextStack.pop();
|
|
64
|
+
}
|
|
65
|
+
this.contextStack.push(clean);
|
|
66
|
+
}
|
|
67
|
+
else if (lower.startsWith('line ')) {
|
|
68
|
+
if (this.contextStack.length > 0 && this.contextStack[this.contextStack.length - 1].startsWith('line')) {
|
|
69
|
+
this.contextStack.pop();
|
|
70
|
+
}
|
|
71
|
+
this.contextStack.push(clean);
|
|
72
|
+
}
|
|
73
|
+
else if (lower.startsWith('vlan ') && !lower.startsWith('no vlan ')) {
|
|
74
|
+
if (this.contextStack.length > 0 && this.contextStack[this.contextStack.length - 1].startsWith('vlan')) {
|
|
75
|
+
this.contextStack.pop();
|
|
76
|
+
}
|
|
77
|
+
this.contextStack.push(clean);
|
|
78
|
+
}
|
|
79
|
+
else if (lower === 'exit') {
|
|
80
|
+
this.contextStack.pop();
|
|
81
|
+
if (this.contextStack.length === 0 || !this.contextStack[this.contextStack.length - 1].startsWith('interface')) {
|
|
82
|
+
this.targetInterface = null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if (lower === 'end') {
|
|
86
|
+
this.contextStack = [];
|
|
87
|
+
this.targetInterface = null;
|
|
45
88
|
}
|
|
46
|
-
if (this.isMutational(
|
|
47
|
-
this.configChangeLog.push(
|
|
89
|
+
if (this.isMutational(clean)) {
|
|
90
|
+
this.configChangeLog.push(clean);
|
|
91
|
+
this.mutationsWithContext.push({
|
|
92
|
+
command: clean,
|
|
93
|
+
contextStack: [...this.contextStack]
|
|
94
|
+
});
|
|
48
95
|
}
|
|
49
96
|
}
|
|
50
97
|
isMutational(command) {
|
|
@@ -56,10 +103,25 @@ class TransactionManager {
|
|
|
56
103
|
!lower.startsWith('configure terminal') &&
|
|
57
104
|
!lower.startsWith('conf t') &&
|
|
58
105
|
!lower.startsWith('copy') &&
|
|
59
|
-
!lower.startsWith('interface')
|
|
106
|
+
!lower.startsWith('interface') &&
|
|
107
|
+
!lower.startsWith('int ') &&
|
|
108
|
+
!lower.startsWith('router ') &&
|
|
109
|
+
!lower.startsWith('line ') &&
|
|
110
|
+
!lower.startsWith('vlan ');
|
|
60
111
|
}
|
|
61
112
|
async executeRollback(session) {
|
|
62
113
|
console.warn('[TransactionManager]: Safety rollback triggered!');
|
|
114
|
+
const snapshotCapableSession = session;
|
|
115
|
+
if (snapshotCapableSession.restoreBackupSnapshot?.()) {
|
|
116
|
+
console.log('[TransactionManager]: Mock backup restore completed successfully.');
|
|
117
|
+
this.clear();
|
|
118
|
+
return 'Mock backup restore completed successfully.';
|
|
119
|
+
}
|
|
120
|
+
if (snapshotCapableSession.hasSnapshots?.() && snapshotCapableSession.restoreToInitialSnapshot?.()) {
|
|
121
|
+
console.log('[TransactionManager]: Mock snapshot restore completed successfully.');
|
|
122
|
+
this.clear();
|
|
123
|
+
return 'Mock snapshot restore completed successfully.';
|
|
124
|
+
}
|
|
63
125
|
if (this.backupCreated) {
|
|
64
126
|
try {
|
|
65
127
|
console.warn(`[TransactionManager]: Restoring running configuration atomically using ${this.backupFilename}...`);
|
|
@@ -82,46 +144,54 @@ class TransactionManager {
|
|
|
82
144
|
console.warn(`[TransactionManager Warning]: Atomic replace failed: ${err.message}. Falling back to command inversion.`);
|
|
83
145
|
}
|
|
84
146
|
}
|
|
85
|
-
console.warn(`[TransactionManager]: Executing manual inversion for ${this.
|
|
147
|
+
console.warn(`[TransactionManager]: Executing manual inversion for ${this.mutationsWithContext.length} mutations...`);
|
|
86
148
|
let rollbackSequence = ['configure terminal'];
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
rollbackSequence.push('shutdown');
|
|
100
|
-
}
|
|
101
|
-
else if (lower.startsWith('description')) {
|
|
102
|
-
rollbackSequence.push('no description');
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
if (lower.startsWith('no ')) {
|
|
106
|
-
rollbackSequence.push(clean.substring(3));
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
rollbackSequence.push(`no ${clean}`);
|
|
149
|
+
let currentRollbackContext = [];
|
|
150
|
+
for (const item of [...this.mutationsWithContext].reverse()) {
|
|
151
|
+
const targetStack = item.contextStack;
|
|
152
|
+
let needsReentry = false;
|
|
153
|
+
if (currentRollbackContext.length !== targetStack.length) {
|
|
154
|
+
needsReentry = true;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
for (let i = 0; i < targetStack.length; i++) {
|
|
158
|
+
if (currentRollbackContext[i] !== targetStack[i]) {
|
|
159
|
+
needsReentry = true;
|
|
160
|
+
break;
|
|
110
161
|
}
|
|
111
162
|
}
|
|
112
163
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const clean = cmd.trim();
|
|
117
|
-
const lower = clean.toLowerCase();
|
|
118
|
-
if (lower.startsWith('no ')) {
|
|
119
|
-
rollbackSequence.push(clean.substring(3));
|
|
164
|
+
if (needsReentry) {
|
|
165
|
+
if (currentRollbackContext.length > 0) {
|
|
166
|
+
rollbackSequence.push('exit');
|
|
120
167
|
}
|
|
121
|
-
|
|
122
|
-
rollbackSequence.push(
|
|
168
|
+
for (const submode of targetStack) {
|
|
169
|
+
rollbackSequence.push(submode);
|
|
123
170
|
}
|
|
171
|
+
currentRollbackContext = [...targetStack];
|
|
172
|
+
}
|
|
173
|
+
const clean = item.command;
|
|
174
|
+
const lower = clean.toLowerCase();
|
|
175
|
+
let inverseCmd = '';
|
|
176
|
+
if (lower.startsWith('ip address') || lower.startsWith('ip add')) {
|
|
177
|
+
inverseCmd = 'no ip address';
|
|
178
|
+
}
|
|
179
|
+
else if (lower.startsWith('shutdown')) {
|
|
180
|
+
inverseCmd = 'no shutdown';
|
|
181
|
+
}
|
|
182
|
+
else if (lower.startsWith('no shutdown')) {
|
|
183
|
+
inverseCmd = 'shutdown';
|
|
184
|
+
}
|
|
185
|
+
else if (lower.startsWith('description')) {
|
|
186
|
+
inverseCmd = 'no description';
|
|
187
|
+
}
|
|
188
|
+
else if (lower.startsWith('no ')) {
|
|
189
|
+
inverseCmd = clean.substring(3);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
inverseCmd = `no ${clean}`;
|
|
124
193
|
}
|
|
194
|
+
rollbackSequence.push(inverseCmd);
|
|
125
195
|
}
|
|
126
196
|
rollbackSequence.push('end');
|
|
127
197
|
let summary = '';
|
|
@@ -134,10 +204,12 @@ class TransactionManager {
|
|
|
134
204
|
}
|
|
135
205
|
clear() {
|
|
136
206
|
this.configChangeLog = [];
|
|
207
|
+
this.mutationsWithContext = [];
|
|
208
|
+
this.contextStack = [];
|
|
137
209
|
this.targetInterface = null;
|
|
138
210
|
}
|
|
139
211
|
hasMutations() {
|
|
140
|
-
return this.configChangeLog.length > 0;
|
|
212
|
+
return this.configChangeLog.length > 0 || this.mutationsWithContext.length > 0;
|
|
141
213
|
}
|
|
142
214
|
}
|
|
143
215
|
exports.TransactionManager = TransactionManager;
|