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