ciscollm-cli 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +147 -78
  2. package/dist/cli/ui/ui.d.ts +1 -0
  3. package/dist/cli/ui/ui.js +19 -4
  4. package/dist/cli/ui/ui.js.map +1 -1
  5. package/dist/core/agent/AgentLoop.d.ts +6 -0
  6. package/dist/core/agent/AgentLoop.js +137 -15
  7. package/dist/core/agent/AgentLoop.js.map +1 -1
  8. package/dist/core/agent/CommandReferenceEngine.d.ts +2 -0
  9. package/dist/core/agent/CommandReferenceEngine.js +73 -4
  10. package/dist/core/agent/CommandReferenceEngine.js.map +1 -1
  11. package/dist/core/agent/HierarchicalAgentManager.d.ts +11 -0
  12. package/dist/core/agent/HierarchicalAgentManager.js +70 -0
  13. package/dist/core/agent/HierarchicalAgentManager.js.map +1 -0
  14. package/dist/core/agent/MultiAgentCoordinator.d.ts +5 -1
  15. package/dist/core/agent/MultiAgentCoordinator.js +55 -0
  16. package/dist/core/agent/MultiAgentCoordinator.js.map +1 -1
  17. package/dist/core/agent/PromptEngine.d.ts +1 -1
  18. package/dist/core/agent/PromptEngine.js +64 -48
  19. package/dist/core/agent/PromptEngine.js.map +1 -1
  20. package/dist/core/guardrails/AuditLogger.d.ts +15 -0
  21. package/dist/core/guardrails/AuditLogger.js +66 -0
  22. package/dist/core/guardrails/AuditLogger.js.map +1 -0
  23. package/dist/core/guardrails/CommandFirewall.js +24 -0
  24. package/dist/core/guardrails/CommandFirewall.js.map +1 -1
  25. package/dist/core/guardrails/PreExecutionValidator.d.ts +9 -0
  26. package/dist/core/guardrails/PreExecutionValidator.js +58 -0
  27. package/dist/core/guardrails/PreExecutionValidator.js.map +1 -0
  28. package/dist/core/rollback/StateDiff.d.ts +47 -0
  29. package/dist/core/rollback/StateDiff.js +121 -0
  30. package/dist/core/rollback/StateDiff.js.map +1 -0
  31. package/dist/core/rollback/TransactionManager.d.ts +2 -0
  32. package/dist/core/rollback/TransactionManager.js +113 -41
  33. package/dist/core/rollback/TransactionManager.js.map +1 -1
  34. package/dist/core/topology/TopologyDiscovery.d.ts +5 -0
  35. package/dist/core/topology/TopologyDiscovery.js +54 -0
  36. package/dist/core/topology/TopologyDiscovery.js.map +1 -0
  37. package/dist/index.js +95 -24
  38. package/dist/index.js.map +1 -1
  39. package/dist/infrastructure/llm/LLMClient.js +52 -2
  40. package/dist/infrastructure/llm/LLMClient.js.map +1 -1
  41. package/dist/infrastructure/protocols/BaseSession.js +1 -1
  42. package/dist/infrastructure/protocols/BaseSession.js.map +1 -1
  43. package/dist/infrastructure/protocols/CmlSession.d.ts +11 -0
  44. package/dist/infrastructure/protocols/CmlSession.js +60 -0
  45. package/dist/infrastructure/protocols/CmlSession.js.map +1 -0
  46. package/dist/infrastructure/protocols/MockSession.d.ts +33 -0
  47. package/dist/infrastructure/protocols/MockSession.js +427 -33
  48. package/dist/infrastructure/protocols/MockSession.js.map +1 -1
  49. package/dist/infrastructure/protocols/NetconfSession.d.ts +9 -0
  50. package/dist/infrastructure/protocols/NetconfSession.js +65 -0
  51. package/dist/infrastructure/protocols/NetconfSession.js.map +1 -0
  52. package/dist/infrastructure/protocols/PlinkSerial.js +107 -23
  53. package/dist/infrastructure/protocols/PlinkSerial.js.map +1 -1
  54. package/dist/shared/types.d.ts +12 -0
  55. package/package.json +2 -2
@@ -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', { 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 });
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.formatInvalidInput('show ip route', 0) },
148
+ { pattern: ['show', 'ip', 'route'], action: () => this.handleShowIpRoute() },
47
149
  { 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) }
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 = clean.replace(/\$(\w+|\{\w+\})/g, (match, name) => {
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
- 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
- }
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[1];
451
+ const intName = parts.slice(1).join('');
340
452
  const resolvedName = this.resolveInterfaceName(intName);
341
453
  if (!resolvedName) {
342
- return this.formatBadInterfaceParameter(command, intName);
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, { ip: null, subnet: null, shutdown: false, description: null });
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, intName);
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.shutdown = true;
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.shutdown = false;
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.shutdown ? 'administratively down' : 'up';
454
- const proto = config.shutdown ? 'down' : 'up';
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.shutdown)
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