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.
Files changed (55) hide show
  1. package/README.md +147 -78
  2. package/dist/cli/ui/ui.d.ts +1 -0
  3. package/dist/cli/ui/ui.js +19 -4
  4. package/dist/cli/ui/ui.js.map +1 -1
  5. package/dist/core/agent/AgentLoop.d.ts +6 -0
  6. package/dist/core/agent/AgentLoop.js +137 -15
  7. package/dist/core/agent/AgentLoop.js.map +1 -1
  8. package/dist/core/agent/CommandReferenceEngine.d.ts +2 -0
  9. package/dist/core/agent/CommandReferenceEngine.js +73 -4
  10. package/dist/core/agent/CommandReferenceEngine.js.map +1 -1
  11. package/dist/core/agent/HierarchicalAgentManager.d.ts +11 -0
  12. package/dist/core/agent/HierarchicalAgentManager.js +70 -0
  13. package/dist/core/agent/HierarchicalAgentManager.js.map +1 -0
  14. package/dist/core/agent/MultiAgentCoordinator.d.ts +5 -1
  15. package/dist/core/agent/MultiAgentCoordinator.js +55 -0
  16. package/dist/core/agent/MultiAgentCoordinator.js.map +1 -1
  17. package/dist/core/agent/PromptEngine.d.ts +1 -1
  18. package/dist/core/agent/PromptEngine.js +64 -48
  19. package/dist/core/agent/PromptEngine.js.map +1 -1
  20. package/dist/core/guardrails/AuditLogger.d.ts +15 -0
  21. package/dist/core/guardrails/AuditLogger.js +66 -0
  22. package/dist/core/guardrails/AuditLogger.js.map +1 -0
  23. package/dist/core/guardrails/CommandFirewall.js +24 -0
  24. package/dist/core/guardrails/CommandFirewall.js.map +1 -1
  25. package/dist/core/guardrails/PreExecutionValidator.d.ts +9 -0
  26. package/dist/core/guardrails/PreExecutionValidator.js +58 -0
  27. package/dist/core/guardrails/PreExecutionValidator.js.map +1 -0
  28. package/dist/core/rollback/StateDiff.d.ts +47 -0
  29. package/dist/core/rollback/StateDiff.js +121 -0
  30. package/dist/core/rollback/StateDiff.js.map +1 -0
  31. package/dist/core/rollback/TransactionManager.d.ts +2 -0
  32. package/dist/core/rollback/TransactionManager.js +113 -41
  33. package/dist/core/rollback/TransactionManager.js.map +1 -1
  34. package/dist/core/topology/TopologyDiscovery.d.ts +5 -0
  35. package/dist/core/topology/TopologyDiscovery.js +54 -0
  36. package/dist/core/topology/TopologyDiscovery.js.map +1 -0
  37. package/dist/index.js +95 -24
  38. package/dist/index.js.map +1 -1
  39. package/dist/infrastructure/llm/LLMClient.js +52 -2
  40. package/dist/infrastructure/llm/LLMClient.js.map +1 -1
  41. package/dist/infrastructure/protocols/BaseSession.js +1 -1
  42. package/dist/infrastructure/protocols/BaseSession.js.map +1 -1
  43. package/dist/infrastructure/protocols/CmlSession.d.ts +11 -0
  44. package/dist/infrastructure/protocols/CmlSession.js +60 -0
  45. package/dist/infrastructure/protocols/CmlSession.js.map +1 -0
  46. package/dist/infrastructure/protocols/MockSession.d.ts +33 -0
  47. package/dist/infrastructure/protocols/MockSession.js +427 -33
  48. package/dist/infrastructure/protocols/MockSession.js.map +1 -1
  49. package/dist/infrastructure/protocols/NetconfSession.d.ts +9 -0
  50. package/dist/infrastructure/protocols/NetconfSession.js +65 -0
  51. package/dist/infrastructure/protocols/NetconfSession.js.map +1 -0
  52. package/dist/infrastructure/protocols/PlinkSerial.js +107 -23
  53. package/dist/infrastructure/protocols/PlinkSerial.js.map +1 -1
  54. package/dist/shared/types.d.ts +12 -0
  55. 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;QAErC,OAAO;;;;;;EAMb,SAAS;;;;;;gCAMqB,mBAAmB,CAAC,CAAC,CAAC,gDAAgD,CAAC,CAAC,CAAC,0BAA0B;EACjI,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+OAkCwN,CAAC;IAC5O,CAAC;CACJ;AAvDD,oCAuDC"}
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":"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"}
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 cleanCmd = command.trim();
42
- const interfaceMatch = /^interface\s+([A-Za-z0-9\/\.\-]+)/i.exec(cleanCmd);
43
- if (interfaceMatch) {
44
- this.targetInterface = interfaceMatch[1];
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(cleanCmd)) {
47
- this.configChangeLog.push(cleanCmd);
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.configChangeLog.length} mutations...`);
147
+ console.warn(`[TransactionManager]: Executing manual inversion for ${this.mutationsWithContext.length} mutations...`);
86
148
  let rollbackSequence = ['configure terminal'];
87
- if (this.targetInterface) {
88
- rollbackSequence.push(`interface ${this.targetInterface}`);
89
- for (const cmd of [...this.configChangeLog].reverse()) {
90
- const clean = cmd.trim();
91
- const lower = clean.toLowerCase();
92
- if (lower.startsWith('ip address') || lower.startsWith('ip add')) {
93
- rollbackSequence.push('no ip address');
94
- }
95
- else if (lower.startsWith('shutdown')) {
96
- rollbackSequence.push('no shutdown');
97
- }
98
- else if (lower.startsWith('no shutdown')) {
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
- else {
115
- for (const cmd of [...this.configChangeLog].reverse()) {
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
- else {
122
- rollbackSequence.push(`no ${clean}`);
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;