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
|
@@ -7,11 +7,18 @@ export declare class MockSession extends BaseSession {
|
|
|
7
7
|
private shellEnabled;
|
|
8
8
|
private shellVariables;
|
|
9
9
|
private shellFunctions;
|
|
10
|
+
private routes;
|
|
11
|
+
private backupSnapshot;
|
|
12
|
+
private rollbackSnapshots;
|
|
10
13
|
private readonly commandPatterns;
|
|
14
|
+
private getStateFilePath;
|
|
15
|
+
private saveState;
|
|
16
|
+
private loadState;
|
|
11
17
|
constructor(deviceId?: string);
|
|
12
18
|
isShellEnabled(): boolean;
|
|
13
19
|
connect(): Promise<void>;
|
|
14
20
|
execute(command: string, timeoutMs?: number): Promise<string>;
|
|
21
|
+
executeInternal(command: string, timeoutMs?: number): Promise<string>;
|
|
15
22
|
executeBase(command: string, timeoutMs?: number): Promise<string>;
|
|
16
23
|
disconnect(): Promise<void>;
|
|
17
24
|
private updateMode;
|
|
@@ -38,6 +45,32 @@ export declare class MockSession extends BaseSession {
|
|
|
38
45
|
private handleShowIpInterfaceBrief;
|
|
39
46
|
private handleShowRunningConfig;
|
|
40
47
|
private handleShowVlanBrief;
|
|
48
|
+
private handleShowInterfaces;
|
|
49
|
+
private handleShowInterfacesStatus;
|
|
50
|
+
private handleShowIpRoute;
|
|
51
|
+
private handleCopyRunningConfig;
|
|
52
|
+
private handleConfigureReplace;
|
|
53
|
+
private handleIpRouteCommand;
|
|
41
54
|
private formatBadInterfaceParameter;
|
|
42
55
|
private resolveInterfaceName;
|
|
56
|
+
hasSnapshots(): boolean;
|
|
57
|
+
restoreToInitialSnapshot(): boolean;
|
|
58
|
+
restoreBackupSnapshot(): boolean;
|
|
59
|
+
private pushRollbackSnapshot;
|
|
60
|
+
private clearRollbackSnapshots;
|
|
61
|
+
private cloneSnapshot;
|
|
62
|
+
private restoreSnapshot;
|
|
63
|
+
private refreshConnectedRoutes;
|
|
64
|
+
private isDestinationReachable;
|
|
65
|
+
private computeNetworkAddress;
|
|
66
|
+
private isInSameSubnet;
|
|
67
|
+
private ipMatchesRoute;
|
|
68
|
+
private ipToIntParts;
|
|
69
|
+
private intPartsToIp;
|
|
70
|
+
private simulateLatency;
|
|
71
|
+
private substituteShellVariables;
|
|
72
|
+
private handleDirFlash;
|
|
73
|
+
private handleRouterOspf;
|
|
74
|
+
private handleIpDhcpPool;
|
|
75
|
+
private handleIpAccessList;
|
|
43
76
|
}
|
|
@@ -1,7 +1,42 @@
|
|
|
1
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
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.MockSession = void 0;
|
|
4
37
|
const BaseSession_1 = require("./BaseSession");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
5
40
|
class MockSession extends BaseSession_1.BaseSession {
|
|
6
41
|
deviceId;
|
|
7
42
|
interfaces = new Map();
|
|
@@ -10,7 +45,50 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
10
45
|
shellEnabled = false;
|
|
11
46
|
shellVariables = new Map();
|
|
12
47
|
shellFunctions = new Map();
|
|
48
|
+
routes = [];
|
|
49
|
+
backupSnapshot = null;
|
|
50
|
+
rollbackSnapshots = [];
|
|
13
51
|
commandPatterns;
|
|
52
|
+
getStateFilePath() {
|
|
53
|
+
return path.resolve(process.cwd(), `.mock-state-${this.deviceId.toLowerCase().replace(/[^a-z0-9]/g, '_')}.json`);
|
|
54
|
+
}
|
|
55
|
+
saveState() {
|
|
56
|
+
if (process.env.NODE_ENV === 'test')
|
|
57
|
+
return;
|
|
58
|
+
try {
|
|
59
|
+
const data = {
|
|
60
|
+
interfaces: Array.from(this.interfaces.entries()),
|
|
61
|
+
vlans: Array.from(this.vlans),
|
|
62
|
+
shellVariables: Array.from(this.shellVariables.entries()),
|
|
63
|
+
shellFunctions: Array.from(this.shellFunctions.entries()),
|
|
64
|
+
routes: this.routes,
|
|
65
|
+
state: this.state,
|
|
66
|
+
activeInterface: this.activeInterface
|
|
67
|
+
};
|
|
68
|
+
fs.writeFileSync(this.getStateFilePath(), JSON.stringify(data, null, 2), 'utf8');
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
loadState() {
|
|
74
|
+
if (process.env.NODE_ENV === 'test')
|
|
75
|
+
return;
|
|
76
|
+
try {
|
|
77
|
+
const filePath = this.getStateFilePath();
|
|
78
|
+
if (fs.existsSync(filePath)) {
|
|
79
|
+
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
80
|
+
this.interfaces = new Map(data.interfaces);
|
|
81
|
+
this.vlans = new Set(data.vlans);
|
|
82
|
+
this.shellVariables = new Map(data.shellVariables);
|
|
83
|
+
this.shellFunctions = new Map(data.shellFunctions);
|
|
84
|
+
this.routes = data.routes;
|
|
85
|
+
this.state = data.state;
|
|
86
|
+
this.activeInterface = data.activeInterface;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
}
|
|
91
|
+
}
|
|
14
92
|
constructor(deviceId = 'Switch') {
|
|
15
93
|
super();
|
|
16
94
|
this.deviceId = deviceId;
|
|
@@ -19,9 +97,29 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
19
97
|
hostname: this.deviceId,
|
|
20
98
|
prompt: `${this.deviceId}>`
|
|
21
99
|
};
|
|
22
|
-
this.interfaces.set('GigabitEthernet0/0', {
|
|
23
|
-
|
|
24
|
-
|
|
100
|
+
this.interfaces.set('GigabitEthernet0/0', {
|
|
101
|
+
ip: '192.168.1.254',
|
|
102
|
+
subnet: '255.255.255.0',
|
|
103
|
+
adminShutdown: false,
|
|
104
|
+
lineProtocolUp: true,
|
|
105
|
+
description: 'Management Uplink'
|
|
106
|
+
});
|
|
107
|
+
this.interfaces.set('GigabitEthernet0/1', {
|
|
108
|
+
ip: null,
|
|
109
|
+
subnet: null,
|
|
110
|
+
adminShutdown: true,
|
|
111
|
+
lineProtocolUp: false,
|
|
112
|
+
description: null
|
|
113
|
+
});
|
|
114
|
+
this.interfaces.set('GigabitEthernet0/2', {
|
|
115
|
+
ip: null,
|
|
116
|
+
subnet: null,
|
|
117
|
+
adminShutdown: true,
|
|
118
|
+
lineProtocolUp: false,
|
|
119
|
+
description: null
|
|
120
|
+
});
|
|
121
|
+
this.refreshConnectedRoutes();
|
|
122
|
+
this.loadState();
|
|
25
123
|
this.commandPatterns = [
|
|
26
124
|
{ pattern: ['configure', 'terminal'], action: () => this.transitionToGlobalConfig('Enter configuration commands, one per line. End with CNTL/Z.') },
|
|
27
125
|
{ pattern: ['conf', 't'], action: () => this.transitionToGlobalConfig('Enter configuration commands, one per line. End with CNTL/Z.') },
|
|
@@ -30,12 +128,16 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
30
128
|
{ pattern: ['terminal', 'shell'], action: () => this.toggleShell(true) },
|
|
31
129
|
{ pattern: ['shell', 'processing', 'full'], action: () => this.requireGlobalConfigAndToggleShell(true) },
|
|
32
130
|
{ pattern: ['no', 'shell', 'processing'], action: () => this.requireGlobalConfigAndToggleShell(false) },
|
|
131
|
+
{ pattern: ['copy', 'running-config'], action: (command) => this.handleCopyRunningConfig(command) },
|
|
132
|
+
{ pattern: ['configure', 'replace'], action: (command) => this.handleConfigureReplace(command) },
|
|
33
133
|
{ pattern: ['interface'], action: (command) => this.handleInterfaceCommand(command) },
|
|
34
134
|
{ pattern: ['vlan'], action: (command) => this.handleVlanCommand(command) },
|
|
135
|
+
{ pattern: ['switchport'], action: () => '' },
|
|
35
136
|
{ pattern: ['exit'], action: () => this.handleExitCommand() },
|
|
36
137
|
{ pattern: ['end'], action: () => this.handleEndCommand() },
|
|
37
138
|
{ pattern: ['ip', 'address'], action: (command) => this.handleIpAddressCommand(command) },
|
|
38
139
|
{ pattern: ['no', 'ip', 'address'], action: (command) => this.handleNoIpAddressCommand(command) },
|
|
140
|
+
{ pattern: ['ip', 'route'], action: (command) => this.handleIpRouteCommand(command) },
|
|
39
141
|
{ pattern: ['shutdown'], action: () => this.handleShutdownCommand() },
|
|
40
142
|
{ pattern: ['no', 'shutdown'], action: () => this.handleNoShutdownCommand() },
|
|
41
143
|
{ pattern: ['description'], action: (command) => this.handleDescriptionCommand(command) },
|
|
@@ -43,11 +145,29 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
43
145
|
{ pattern: ['show', 'ip', 'interface', 'brief'], action: () => this.handleShowIpInterfaceBrief() },
|
|
44
146
|
{ pattern: ['show', 'running-config'], action: () => this.handleShowRunningConfig() },
|
|
45
147
|
{ pattern: ['show', 'run'], action: () => this.handleShowRunningConfig() },
|
|
46
|
-
{ pattern: ['show', 'ip', 'route'], action: () => this.
|
|
148
|
+
{ pattern: ['show', 'ip', 'route'], action: () => this.handleShowIpRoute() },
|
|
47
149
|
{ pattern: ['show', 'vlan', 'brief'], action: () => this.handleShowVlanBrief() },
|
|
48
|
-
{ pattern: ['show', 'interfaces'], action: () => this.
|
|
49
|
-
{ pattern: ['show', 'interfaces', 'status'], action: () => this.
|
|
50
|
-
{ pattern: ['show', 'interfaces', 'brief'], action: () => this.
|
|
150
|
+
{ pattern: ['show', 'interfaces'], action: () => this.handleShowInterfaces() },
|
|
151
|
+
{ pattern: ['show', 'interfaces', 'status'], action: () => this.handleShowInterfacesStatus() },
|
|
152
|
+
{ pattern: ['show', 'interfaces', 'brief'], action: () => this.handleShowInterfacesStatus() },
|
|
153
|
+
{ pattern: ['dir', 'flash:'], action: () => this.handleDirFlash() },
|
|
154
|
+
{ pattern: ['dir'], action: () => this.handleDirFlash() },
|
|
155
|
+
{ pattern: ['router', 'ospf'], action: (command) => this.handleRouterOspf(command) },
|
|
156
|
+
{ pattern: ['network'], action: () => '' },
|
|
157
|
+
{ pattern: ['ip', 'ospf'], action: () => '' },
|
|
158
|
+
{ pattern: ['ip', 'dhcp', 'pool'], action: (command) => this.handleIpDhcpPool(command) },
|
|
159
|
+
{ pattern: ['ip', 'pool'], action: (command) => this.handleIpDhcpPool(command) },
|
|
160
|
+
{ pattern: ['default-router'], action: () => '' },
|
|
161
|
+
{ pattern: ['dns-server'], action: () => '' },
|
|
162
|
+
{ pattern: ['ip', 'dhcp', 'excluded-address'], action: () => '' },
|
|
163
|
+
{ pattern: ['access-list'], action: () => '' },
|
|
164
|
+
{ pattern: ['ip', 'access-list'], action: (command) => this.handleIpAccessList(command) },
|
|
165
|
+
{ pattern: ['ip', 'access-group'], action: () => '' },
|
|
166
|
+
{ pattern: ['permit'], action: () => '' },
|
|
167
|
+
{ pattern: ['deny'], action: () => '' },
|
|
168
|
+
{ pattern: ['ip', 'nat', 'inside', 'source'], action: () => '' },
|
|
169
|
+
{ pattern: ['ip', 'nat', 'inside'], action: () => '' },
|
|
170
|
+
{ pattern: ['ip', 'nat', 'outside'], action: () => '' }
|
|
51
171
|
];
|
|
52
172
|
}
|
|
53
173
|
isShellEnabled() {
|
|
@@ -58,6 +178,11 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
58
178
|
return Promise.resolve();
|
|
59
179
|
}
|
|
60
180
|
async execute(command, timeoutMs) {
|
|
181
|
+
const result = await this.executeInternal(command, timeoutMs);
|
|
182
|
+
this.saveState();
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
async executeInternal(command, timeoutMs) {
|
|
61
186
|
let clean = command.trim();
|
|
62
187
|
let lower = clean.toLowerCase();
|
|
63
188
|
if (this.shellEnabled) {
|
|
@@ -97,10 +222,7 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
97
222
|
}
|
|
98
223
|
}
|
|
99
224
|
if (this.shellEnabled) {
|
|
100
|
-
clean =
|
|
101
|
-
const cleanName = name.startsWith('{') ? name.slice(1, -1) : name;
|
|
102
|
-
return this.shellVariables.get(cleanName) || '';
|
|
103
|
-
});
|
|
225
|
+
clean = this.substituteShellVariables(clean);
|
|
104
226
|
lower = clean.toLowerCase();
|
|
105
227
|
}
|
|
106
228
|
let baseCommand = clean;
|
|
@@ -184,6 +306,7 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
184
306
|
const body = this.shellFunctions.get(lower);
|
|
185
307
|
return this.execute(body, timeoutMs);
|
|
186
308
|
}
|
|
309
|
+
await this.simulateLatency(clean, timeoutMs);
|
|
187
310
|
const parsed = this.matchCommand(clean);
|
|
188
311
|
if (parsed) {
|
|
189
312
|
const handled = parsed.action(clean, lower);
|
|
@@ -204,18 +327,7 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
204
327
|
const dest = clean.split(/\s+/)[1];
|
|
205
328
|
if (!dest)
|
|
206
329
|
return this.formatIncompleteCommand(clean);
|
|
207
|
-
|
|
208
|
-
if (dest === '127.0.0.1' || dest === '8.8.8.8' || dest === '192.168.1.254') {
|
|
209
|
-
pingSuccess = true;
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
for (const config of this.interfaces.values()) {
|
|
213
|
-
if (config.ip === dest && !config.shutdown) {
|
|
214
|
-
pingSuccess = true;
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
330
|
+
const pingSuccess = this.isDestinationReachable(dest);
|
|
219
331
|
let output = `Type escape sequence to abort.\nSending 5, 100-byte ICMP Echos to ${dest}, timeout is 2 seconds:\n`;
|
|
220
332
|
if (pingSuccess) {
|
|
221
333
|
output += `!!!!!\nSuccess rate is 100 percent (5/5), round-trip min/avg/max = 1/3/12 ms`;
|
|
@@ -336,17 +448,23 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
336
448
|
if (parts.length < 2) {
|
|
337
449
|
return this.formatIncompleteCommand(command);
|
|
338
450
|
}
|
|
339
|
-
const intName = parts
|
|
451
|
+
const intName = parts.slice(1).join('');
|
|
340
452
|
const resolvedName = this.resolveInterfaceName(intName);
|
|
341
453
|
if (!resolvedName) {
|
|
342
|
-
return this.formatBadInterfaceParameter(command,
|
|
454
|
+
return this.formatBadInterfaceParameter(command, parts[1]);
|
|
343
455
|
}
|
|
344
456
|
if (!this.interfaces.has(resolvedName)) {
|
|
345
457
|
if (resolvedName.toLowerCase().startsWith('loopback') || resolvedName.toLowerCase().startsWith('vlan')) {
|
|
346
|
-
this.interfaces.set(resolvedName, {
|
|
458
|
+
this.interfaces.set(resolvedName, {
|
|
459
|
+
ip: null,
|
|
460
|
+
subnet: null,
|
|
461
|
+
adminShutdown: false,
|
|
462
|
+
lineProtocolUp: true,
|
|
463
|
+
description: null
|
|
464
|
+
});
|
|
347
465
|
}
|
|
348
466
|
else {
|
|
349
|
-
return this.formatBadInterfaceParameter(command,
|
|
467
|
+
return this.formatBadInterfaceParameter(command, parts[1]);
|
|
350
468
|
}
|
|
351
469
|
}
|
|
352
470
|
this.activeInterface = resolvedName;
|
|
@@ -366,6 +484,10 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
366
484
|
return '';
|
|
367
485
|
}
|
|
368
486
|
handleExitCommand() {
|
|
487
|
+
if (this.state.prompt.includes('(') && !this.state.prompt.includes('(config-if)')) {
|
|
488
|
+
this.updateMode('GLOBAL_CONFIG');
|
|
489
|
+
return '';
|
|
490
|
+
}
|
|
369
491
|
if (this.state.currentMode === 'INTERFACE_CONFIG') {
|
|
370
492
|
this.activeInterface = null;
|
|
371
493
|
this.updateMode('GLOBAL_CONFIG');
|
|
@@ -382,7 +504,7 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
382
504
|
return '% Connection closed.';
|
|
383
505
|
}
|
|
384
506
|
handleEndCommand() {
|
|
385
|
-
if (this.state.currentMode === 'GLOBAL_CONFIG' || this.state.currentMode === 'INTERFACE_CONFIG') {
|
|
507
|
+
if (this.state.currentMode === 'GLOBAL_CONFIG' || this.state.currentMode === 'INTERFACE_CONFIG' || this.state.prompt.includes(')')) {
|
|
386
508
|
this.activeInterface = null;
|
|
387
509
|
this.updateMode('PRIVILEGED_EXEC');
|
|
388
510
|
return '';
|
|
@@ -391,6 +513,7 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
391
513
|
}
|
|
392
514
|
handleIpAddressCommand(command) {
|
|
393
515
|
if (this.state.currentMode === 'INTERFACE_CONFIG' && this.activeInterface) {
|
|
516
|
+
this.pushRollbackSnapshot();
|
|
394
517
|
const parts = command.trim().split(/\s+/);
|
|
395
518
|
const ip = parts[2];
|
|
396
519
|
const subnet = parts[3];
|
|
@@ -400,37 +523,48 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
400
523
|
const intf = this.interfaces.get(this.activeInterface);
|
|
401
524
|
intf.ip = ip;
|
|
402
525
|
intf.subnet = subnet;
|
|
526
|
+
intf.lineProtocolUp = !intf.adminShutdown;
|
|
527
|
+
this.refreshConnectedRoutes();
|
|
403
528
|
return '';
|
|
404
529
|
}
|
|
405
530
|
return this.formatInvalidInput(command, 0);
|
|
406
531
|
}
|
|
407
532
|
handleNoIpAddressCommand(command) {
|
|
408
533
|
if (this.state.currentMode === 'INTERFACE_CONFIG' && this.activeInterface) {
|
|
534
|
+
this.pushRollbackSnapshot();
|
|
409
535
|
const intf = this.interfaces.get(this.activeInterface);
|
|
410
536
|
intf.ip = null;
|
|
411
537
|
intf.subnet = null;
|
|
538
|
+
this.refreshConnectedRoutes();
|
|
412
539
|
return '';
|
|
413
540
|
}
|
|
414
541
|
return this.formatInvalidInput(command, 0);
|
|
415
542
|
}
|
|
416
543
|
handleShutdownCommand() {
|
|
417
544
|
if (this.state.currentMode === 'INTERFACE_CONFIG' && this.activeInterface) {
|
|
545
|
+
this.pushRollbackSnapshot();
|
|
418
546
|
const intf = this.interfaces.get(this.activeInterface);
|
|
419
|
-
intf.
|
|
547
|
+
intf.adminShutdown = true;
|
|
548
|
+
intf.lineProtocolUp = false;
|
|
549
|
+
this.refreshConnectedRoutes();
|
|
420
550
|
return '';
|
|
421
551
|
}
|
|
422
552
|
return this.formatInvalidInput('shutdown', 0);
|
|
423
553
|
}
|
|
424
554
|
handleNoShutdownCommand() {
|
|
425
555
|
if (this.state.currentMode === 'INTERFACE_CONFIG' && this.activeInterface) {
|
|
556
|
+
this.pushRollbackSnapshot();
|
|
426
557
|
const intf = this.interfaces.get(this.activeInterface);
|
|
427
|
-
intf.
|
|
558
|
+
intf.adminShutdown = false;
|
|
559
|
+
intf.lineProtocolUp = true;
|
|
560
|
+
this.refreshConnectedRoutes();
|
|
428
561
|
return '';
|
|
429
562
|
}
|
|
430
563
|
return this.formatInvalidInput('no shutdown', 0);
|
|
431
564
|
}
|
|
432
565
|
handleDescriptionCommand(command) {
|
|
433
566
|
if (this.state.currentMode === 'INTERFACE_CONFIG' && this.activeInterface) {
|
|
567
|
+
this.pushRollbackSnapshot();
|
|
434
568
|
const desc = command.trim().substring(command.trim().indexOf(' ') + 1).trim();
|
|
435
569
|
const intf = this.interfaces.get(this.activeInterface);
|
|
436
570
|
intf.description = desc;
|
|
@@ -440,6 +574,7 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
440
574
|
}
|
|
441
575
|
handleNoDescriptionCommand() {
|
|
442
576
|
if (this.state.currentMode === 'INTERFACE_CONFIG' && this.activeInterface) {
|
|
577
|
+
this.pushRollbackSnapshot();
|
|
443
578
|
const intf = this.interfaces.get(this.activeInterface);
|
|
444
579
|
intf.description = null;
|
|
445
580
|
return '';
|
|
@@ -450,8 +585,8 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
450
585
|
let output = 'Interface IP-Address OK? Method Status Protocol\n';
|
|
451
586
|
for (const [name, config] of this.interfaces.entries()) {
|
|
452
587
|
const ip = config.ip || 'unassigned';
|
|
453
|
-
const status = config.
|
|
454
|
-
const proto = config.
|
|
588
|
+
const status = config.adminShutdown ? 'administratively down' : 'up';
|
|
589
|
+
const proto = config.lineProtocolUp ? 'up' : 'down';
|
|
455
590
|
output += `${name.padEnd(26)} ${ip.padEnd(15)} YES manual ${status.padEnd(21)} ${proto}\n`;
|
|
456
591
|
}
|
|
457
592
|
return output;
|
|
@@ -464,10 +599,13 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
464
599
|
output += `\n description ${config.description}`;
|
|
465
600
|
if (config.ip)
|
|
466
601
|
output += `\n ip address ${config.ip} ${config.subnet}`;
|
|
467
|
-
if (config.
|
|
602
|
+
if (config.adminShutdown)
|
|
468
603
|
output += '\n shutdown';
|
|
469
604
|
output += '\n!';
|
|
470
605
|
}
|
|
606
|
+
for (const route of this.routes) {
|
|
607
|
+
output += `\nip route ${route.network} ${route.mask} ${route.nextHop || route.outgoingInterface || 'null'}`;
|
|
608
|
+
}
|
|
471
609
|
return output;
|
|
472
610
|
}
|
|
473
611
|
handleShowVlanBrief() {
|
|
@@ -477,6 +615,71 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
477
615
|
}
|
|
478
616
|
return output;
|
|
479
617
|
}
|
|
618
|
+
handleShowInterfaces() {
|
|
619
|
+
let output = '';
|
|
620
|
+
for (const [name, config] of this.interfaces.entries()) {
|
|
621
|
+
const status = config.adminShutdown ? 'administratively down' : 'up';
|
|
622
|
+
const proto = config.lineProtocolUp ? 'up' : 'down';
|
|
623
|
+
output += `${name} is ${status}, line protocol is ${proto}\n`;
|
|
624
|
+
}
|
|
625
|
+
return output.trim();
|
|
626
|
+
}
|
|
627
|
+
handleShowInterfacesStatus() {
|
|
628
|
+
let output = 'Port Name Status Vlan Duplex Speed Type\n';
|
|
629
|
+
for (const [name, config] of this.interfaces.entries()) {
|
|
630
|
+
const status = config.adminShutdown ? 'disabled' : (config.lineProtocolUp ? 'connected' : 'notconnect');
|
|
631
|
+
output += `${name.padEnd(9)} ${(config.description || '').padEnd(18)} ${status.padEnd(12)} 1 auto auto 10/100/1000BaseTX\n`;
|
|
632
|
+
}
|
|
633
|
+
return output.trim();
|
|
634
|
+
}
|
|
635
|
+
handleShowIpRoute() {
|
|
636
|
+
let output = 'Codes: C - connected, S - static\n\n';
|
|
637
|
+
for (const route of this.routes) {
|
|
638
|
+
const code = route.connected ? 'C' : 'S';
|
|
639
|
+
const target = route.connected ? route.network : `${route.network} [1/0] via ${route.nextHop || route.outgoingInterface || ''}`;
|
|
640
|
+
output += `${code} ${target}\n`;
|
|
641
|
+
}
|
|
642
|
+
return output.trim();
|
|
643
|
+
}
|
|
644
|
+
handleCopyRunningConfig(command) {
|
|
645
|
+
if (this.state.currentMode !== 'PRIVILEGED_EXEC') {
|
|
646
|
+
return this.formatInvalidInput(command, 0);
|
|
647
|
+
}
|
|
648
|
+
this.backupSnapshot = this.cloneSnapshot();
|
|
649
|
+
this.pushRollbackSnapshot();
|
|
650
|
+
return 'Copy complete, 1584 bytes copied in 0.000 secs (0 bytes/sec)';
|
|
651
|
+
}
|
|
652
|
+
handleConfigureReplace(command) {
|
|
653
|
+
if (this.state.currentMode !== 'PRIVILEGED_EXEC') {
|
|
654
|
+
return this.formatInvalidInput(command, 0);
|
|
655
|
+
}
|
|
656
|
+
if (!this.backupSnapshot) {
|
|
657
|
+
return '% No backup configuration available.';
|
|
658
|
+
}
|
|
659
|
+
this.restoreSnapshot(this.backupSnapshot);
|
|
660
|
+
this.clearRollbackSnapshots();
|
|
661
|
+
return 'Configure replace completed successfully.';
|
|
662
|
+
}
|
|
663
|
+
handleIpRouteCommand(command) {
|
|
664
|
+
if (this.state.currentMode !== 'GLOBAL_CONFIG' && this.state.currentMode !== 'INTERFACE_CONFIG') {
|
|
665
|
+
return this.formatInvalidInput(command, 0);
|
|
666
|
+
}
|
|
667
|
+
const parts = command.trim().split(/\s+/);
|
|
668
|
+
if (parts.length < 5) {
|
|
669
|
+
return this.formatIncompleteCommand(command);
|
|
670
|
+
}
|
|
671
|
+
this.pushRollbackSnapshot();
|
|
672
|
+
const [, , network, mask, nextHop] = parts;
|
|
673
|
+
const route = {
|
|
674
|
+
network,
|
|
675
|
+
mask,
|
|
676
|
+
nextHop: nextHop || null,
|
|
677
|
+
outgoingInterface: null,
|
|
678
|
+
connected: false
|
|
679
|
+
};
|
|
680
|
+
this.routes.push(route);
|
|
681
|
+
return '';
|
|
682
|
+
}
|
|
480
683
|
formatBadInterfaceParameter(command, intName) {
|
|
481
684
|
const caretIndex = Math.max(0, command.toLowerCase().indexOf(intName.toLowerCase()));
|
|
482
685
|
return `${command}\n${' '.repeat(caretIndex)}^\n% Bad interface parameter: ${intName}`;
|
|
@@ -500,6 +703,197 @@ class MockSession extends BaseSession_1.BaseSession {
|
|
|
500
703
|
}
|
|
501
704
|
return null;
|
|
502
705
|
}
|
|
706
|
+
hasSnapshots() {
|
|
707
|
+
return this.rollbackSnapshots.length > 0;
|
|
708
|
+
}
|
|
709
|
+
restoreToInitialSnapshot() {
|
|
710
|
+
const snapshot = this.rollbackSnapshots[0];
|
|
711
|
+
if (!snapshot) {
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
this.restoreSnapshot(snapshot);
|
|
715
|
+
this.clearRollbackSnapshots();
|
|
716
|
+
return true;
|
|
717
|
+
}
|
|
718
|
+
restoreBackupSnapshot() {
|
|
719
|
+
if (!this.backupSnapshot) {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
this.restoreSnapshot(this.backupSnapshot);
|
|
723
|
+
this.clearRollbackSnapshots();
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
pushRollbackSnapshot() {
|
|
727
|
+
this.rollbackSnapshots.push(this.cloneSnapshot());
|
|
728
|
+
}
|
|
729
|
+
clearRollbackSnapshots() {
|
|
730
|
+
this.rollbackSnapshots = [];
|
|
731
|
+
}
|
|
732
|
+
cloneSnapshot() {
|
|
733
|
+
return {
|
|
734
|
+
state: this.getState(),
|
|
735
|
+
interfaces: new Map(Array.from(this.interfaces.entries()).map(([name, config]) => [name, { ...config }])),
|
|
736
|
+
activeInterface: this.activeInterface,
|
|
737
|
+
vlans: new Set(this.vlans),
|
|
738
|
+
shellEnabled: this.shellEnabled,
|
|
739
|
+
shellVariables: new Map(this.shellVariables),
|
|
740
|
+
shellFunctions: new Map(this.shellFunctions),
|
|
741
|
+
routes: this.routes.map(route => ({ ...route }))
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
restoreSnapshot(snapshot) {
|
|
745
|
+
this.state = { ...snapshot.state };
|
|
746
|
+
this.interfaces = new Map(Array.from(snapshot.interfaces.entries()).map(([name, config]) => [name, { ...config }]));
|
|
747
|
+
this.activeInterface = snapshot.activeInterface;
|
|
748
|
+
this.vlans = new Set(snapshot.vlans);
|
|
749
|
+
this.shellEnabled = snapshot.shellEnabled;
|
|
750
|
+
this.shellVariables = new Map(snapshot.shellVariables);
|
|
751
|
+
this.shellFunctions = new Map(snapshot.shellFunctions);
|
|
752
|
+
this.routes = snapshot.routes.map(route => ({ ...route }));
|
|
753
|
+
}
|
|
754
|
+
refreshConnectedRoutes() {
|
|
755
|
+
const connectedRoutes = new Map();
|
|
756
|
+
for (const [name, config] of this.interfaces.entries()) {
|
|
757
|
+
if (config.ip && config.subnet) {
|
|
758
|
+
const network = this.computeNetworkAddress(config.ip, config.subnet);
|
|
759
|
+
const key = `${network}/${config.subnet}`;
|
|
760
|
+
connectedRoutes.set(key, {
|
|
761
|
+
network,
|
|
762
|
+
mask: config.subnet,
|
|
763
|
+
nextHop: null,
|
|
764
|
+
outgoingInterface: name,
|
|
765
|
+
connected: true
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
const staticRoutes = this.routes.filter(route => !route.connected);
|
|
770
|
+
this.routes = [...connectedRoutes.values(), ...staticRoutes];
|
|
771
|
+
}
|
|
772
|
+
isDestinationReachable(dest) {
|
|
773
|
+
if (dest === '127.0.0.1' || dest === '8.8.8.8') {
|
|
774
|
+
return true;
|
|
775
|
+
}
|
|
776
|
+
for (const [name, config] of this.interfaces.entries()) {
|
|
777
|
+
if (!config.ip || !config.subnet || config.adminShutdown || !config.lineProtocolUp) {
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
if (config.ip === dest) {
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
if (this.isInSameSubnet(dest, config.ip, config.subnet)) {
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
for (const route of this.routes) {
|
|
788
|
+
if (route.connected) {
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
if (this.ipMatchesRoute(dest, route.network, route.mask)) {
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
computeNetworkAddress(ip, mask) {
|
|
798
|
+
const ipParts = this.ipToIntParts(ip);
|
|
799
|
+
const maskParts = this.ipToIntParts(mask);
|
|
800
|
+
return this.intPartsToIp(ipParts.map((octet, idx) => octet & maskParts[idx]));
|
|
801
|
+
}
|
|
802
|
+
isInSameSubnet(ip, interfaceIp, mask) {
|
|
803
|
+
return this.computeNetworkAddress(ip, mask) === this.computeNetworkAddress(interfaceIp, mask);
|
|
804
|
+
}
|
|
805
|
+
ipMatchesRoute(ip, network, mask) {
|
|
806
|
+
return this.computeNetworkAddress(ip, mask) === network;
|
|
807
|
+
}
|
|
808
|
+
ipToIntParts(ip) {
|
|
809
|
+
return ip.split('.').map(part => parseInt(part, 10));
|
|
810
|
+
}
|
|
811
|
+
intPartsToIp(parts) {
|
|
812
|
+
return parts.map(part => Math.max(0, Math.min(255, part))).join('.');
|
|
813
|
+
}
|
|
814
|
+
async simulateLatency(command, timeoutMs) {
|
|
815
|
+
const normalized = command.toLowerCase();
|
|
816
|
+
let delayMs = 0;
|
|
817
|
+
if (normalized.startsWith('copy ')) {
|
|
818
|
+
delayMs = 120;
|
|
819
|
+
}
|
|
820
|
+
else if (normalized.startsWith('ping ')) {
|
|
821
|
+
delayMs = 45;
|
|
822
|
+
}
|
|
823
|
+
else if (normalized.startsWith('show ')) {
|
|
824
|
+
delayMs = 20;
|
|
825
|
+
}
|
|
826
|
+
if (timeoutMs && delayMs > timeoutMs) {
|
|
827
|
+
await new Promise(resolve => setTimeout(resolve, timeoutMs));
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
if (delayMs > 0) {
|
|
831
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
substituteShellVariables(command) {
|
|
835
|
+
let output = '';
|
|
836
|
+
for (let index = 0; index < command.length; index++) {
|
|
837
|
+
const char = command[index];
|
|
838
|
+
if (char !== '$') {
|
|
839
|
+
output += char;
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
const next = command[index + 1];
|
|
843
|
+
if (next === '{') {
|
|
844
|
+
const endBrace = command.indexOf('}', index + 2);
|
|
845
|
+
if (endBrace !== -1) {
|
|
846
|
+
const name = command.slice(index + 2, endBrace);
|
|
847
|
+
output += this.shellVariables.get(name) || '';
|
|
848
|
+
index = endBrace;
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
let nameEnd = index + 1;
|
|
853
|
+
while (nameEnd < command.length && /[A-Za-z0-9_]/.test(command[nameEnd])) {
|
|
854
|
+
nameEnd++;
|
|
855
|
+
}
|
|
856
|
+
if (nameEnd > index + 1) {
|
|
857
|
+
const name = command.slice(index + 1, nameEnd);
|
|
858
|
+
output += this.shellVariables.get(name) || '';
|
|
859
|
+
index = nameEnd - 1;
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
output += '$';
|
|
863
|
+
}
|
|
864
|
+
return output;
|
|
865
|
+
}
|
|
866
|
+
handleDirFlash() {
|
|
867
|
+
return `Directory of flash:/
|
|
868
|
+
|
|
869
|
+
1 -rw- 1584 May 27 2026 10:30:00 +00:00 backup-agent.cfg
|
|
870
|
+
|
|
871
|
+
15728640 bytes total (15727056 bytes free)`;
|
|
872
|
+
}
|
|
873
|
+
handleRouterOspf(command) {
|
|
874
|
+
if (this.state.currentMode !== 'GLOBAL_CONFIG') {
|
|
875
|
+
return '% Command rejected: Place in Global Config mode first.';
|
|
876
|
+
}
|
|
877
|
+
this.updateMode('GLOBAL_CONFIG');
|
|
878
|
+
this.state.prompt = `${this.state.hostname}(config-router)#`;
|
|
879
|
+
return '';
|
|
880
|
+
}
|
|
881
|
+
handleIpDhcpPool(command) {
|
|
882
|
+
if (this.state.currentMode !== 'GLOBAL_CONFIG') {
|
|
883
|
+
return '% Command rejected: Place in Global Config mode first.';
|
|
884
|
+
}
|
|
885
|
+
this.updateMode('GLOBAL_CONFIG');
|
|
886
|
+
this.state.prompt = `${this.state.hostname}(dhcp-config)#`;
|
|
887
|
+
return '';
|
|
888
|
+
}
|
|
889
|
+
handleIpAccessList(command) {
|
|
890
|
+
if (this.state.currentMode !== 'GLOBAL_CONFIG') {
|
|
891
|
+
return '% Command rejected: Place in Global Config mode first.';
|
|
892
|
+
}
|
|
893
|
+
this.updateMode('GLOBAL_CONFIG');
|
|
894
|
+
this.state.prompt = `${this.state.hostname}(config-ext-nacl)#`;
|
|
895
|
+
return '';
|
|
896
|
+
}
|
|
503
897
|
}
|
|
504
898
|
exports.MockSession = MockSession;
|
|
505
899
|
//# sourceMappingURL=MockSession.js.map
|