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.
Files changed (59) hide show
  1. package/README.md +169 -74
  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 +7 -0
  6. package/dist/core/agent/AgentLoop.js +177 -18
  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 +64 -5
  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 +130 -54
  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 +247 -31
  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 +26 -0
  44. package/dist/infrastructure/protocols/CmlSession.js +448 -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 +433 -35
  48. package/dist/infrastructure/protocols/MockSession.js.map +1 -1
  49. package/dist/infrastructure/protocols/NetconfSession.d.ts +72 -0
  50. package/dist/infrastructure/protocols/NetconfSession.js +489 -0
  51. package/dist/infrastructure/protocols/NetconfSession.js.map +1 -0
  52. package/dist/infrastructure/protocols/PlinkSerial.js +112 -28
  53. package/dist/infrastructure/protocols/PlinkSerial.js.map +1 -1
  54. package/dist/infrastructure/protocols/SshSession.js +5 -5
  55. package/dist/infrastructure/protocols/SshSession.js.map +1 -1
  56. package/dist/infrastructure/protocols/TelnetSession.js +8 -8
  57. package/dist/infrastructure/protocols/TelnetSession.js.map +1 -1
  58. package/dist/shared/types.d.ts +12 -0
  59. 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', { ip: '192.168.1.254', subnet: '255.255.255.0', shutdown: false, description: 'Management Uplink' });
23
- this.interfaces.set('GigabitEthernet0/1', { ip: null, subnet: null, shutdown: true, description: null });
24
- this.interfaces.set('GigabitEthernet0/2', { ip: null, subnet: null, shutdown: true, description: null });
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.formatInvalidInput('show ip route', 0) },
152
+ { pattern: ['show', 'ip', 'route'], action: () => this.handleShowIpRoute() },
47
153
  { pattern: ['show', 'vlan', 'brief'], action: () => this.handleShowVlanBrief() },
48
- { pattern: ['show', 'interfaces'], action: () => this.formatInvalidInput('show interfaces', 0) },
49
- { pattern: ['show', 'interfaces', 'status'], action: () => this.formatInvalidInput('show interfaces status', 0) },
50
- { pattern: ['show', 'interfaces', 'brief'], action: () => this.formatInvalidInput('show interfaces brief', 0) }
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(`[MockSession - ${this.deviceId}]: Simulated session established.`);
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 = clean.replace(/\$(\w+|\{\w+\})/g, (match, name) => {
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
- let pingSuccess = false;
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(`[MockSession - ${this.deviceId}]: Session disconnected.`);
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[1];
455
+ const intName = parts.slice(1).join('');
340
456
  const resolvedName = this.resolveInterfaceName(intName);
341
457
  if (!resolvedName) {
342
- return this.formatBadInterfaceParameter(command, intName);
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, { ip: null, subnet: null, shutdown: false, description: null });
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, intName);
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.shutdown = true;
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.shutdown = false;
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.shutdown ? 'administratively down' : 'up';
454
- const proto = config.shutdown ? 'down' : 'up';
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.shutdown)
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