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
package/README.md CHANGED
@@ -1,112 +1,181 @@
1
1
  # ciscollm-cli
2
2
 
3
- `ciscollm-cli` is a Cisco IOS automation agent CLI for configuration, troubleshooting, and simulation. It supports tool-calling with an LLM, safety guardrails, rollback handling, and a mock switch/router environment for offline testing.
4
-
5
- ## Installation
6
-
7
- Install the published package globally:
3
+ `ciscollm-cli` is a premium, autonomous Cisco IOS automation agent CLI designed for network configuration, troubleshooting, and simulation. By leveraging LLM tool-calling capabilities, it allows engineers to manage local, remote, or simulated Cisco hardware safely and efficiently.
4
+
5
+ The CLI features an **Interactive Setup Wizard**, **Multi-Device Coordination**, **Safety Guardrails (Command Firewall)**, **Automatic Configuration Rollbacks**, **Mock Device Simulation**, and **Strict Command Reference Validation**.
6
+
7
+ ---
8
+
9
+ ## 🚀 Key Features
10
+
11
+ ### 1. ⚙️ Interactive Setup Wizard
12
+ If `ciscollm run` is executed without a `--goal` parameter, the CLI automatically launches a step-by-step interactive setup wizard (using `inquirer`). The wizard guides the user through:
13
+ * **LLM Provider Selection:** Local (Ollama / LM Studio) or Cloud (OpenRouter).
14
+ * **Local LLM Settings:** Service type (Ollama/LM Studio), API endpoint URL, and model name.
15
+ * **Cloud LLM Settings:** OpenRouter API Key, model name, and endpoint.
16
+ * **Connection Protocol:** Serial (with automated COM port scanning), SSH, Telnet, or Mock simulation.
17
+ * **Connection Details:** COM port and Baud rate (for serial), or Host target, Port, Username, and Password (for SSH/Telnet).
18
+ * **Configuration Goal:** Prompting for the network task the agent needs to achieve.
19
+ * **Configuration Summary:** Displays a summary of the setup and prompts for final confirmation before starting execution.
20
+
21
+ ### 2. 🔀 Multi-Device & Multi-Agent Coordination
22
+ Manage configurations across multiple Cisco hardware devices simultaneously. The `--com` (for serial) and `--host` (for SSH/Telnet) flags accept comma-separated inputs (e.g. `--com COM3,COM4` or `--host 10.0.0.1,10.0.0.2`). The internal `MultiAgentCoordinator` manages all connections in parallel, tracks status, and handles clean disconnections upon exit.
23
+
24
+ ### 3. 🛡️ Command Firewall & Safety Guardrails
25
+ To prevent accidental lockouts, service disruptions, or losing device access, the built-in `CommandFirewall` monitors all LLM-generated commands.
26
+ * **Blocked Operations:**
27
+ * Removing default static routes (`no ip route 0.0.0.0...`) which can break management access.
28
+ * Disabling AAA authentication (`no aaa new-model`) or zeroizing crypto keys (`crypto key zeroize`).
29
+ * Deleting access lists or access groups (`no access-list`, `no ip access-group`).
30
+ * Shutting down active protected interfaces (e.g. `GigabitEthernet0/0`, `GigabitEthernet0/1`, `GigabitEthernet1/0`, `Vlan1`).
31
+ * Removing configured IP addresses on protected interfaces (`no ip address`).
32
+ * **Human-in-the-Loop Validation:** High-risk commands trigger a warning prompt, requiring the operator to manually authorize the execution.
33
+ * **Non-Interactive Mode:** Running with `--non-interactive` (or setting environment variable `CISCOLLM_NON_INTERACTIVE=true`) automatically rejects all blocked/high-risk commands.
34
+
35
+ ### 4. 🔄 Transaction Rollback & Inversion Manager
36
+ If a configuration step fails or the agent encounters command errors, the `TransactionManager` restores the device state:
37
+ * **Atomic Backup:** Before modifying config, the agent attempts to back up the current running-config to `flash:backup-agent.cfg`.
38
+ * **Atomic Configuration Replace:** Rollbacks prioritize replacing the configuration atomically using `configure replace flash:backup-agent.cfg force`.
39
+ * **Command Inversion Fallback:** If flash storage is unreachable, it builds an inverse command sequence in reverse order (e.g. `ip address ...` -> `no ip address`, `shutdown` -> `no shutdown`, `no shutdown` -> `shutdown`, `description ...` -> `no description`) and executes them sequentially in their respective submodes.
40
+
41
+ ### 5. 📚 Strict Command Reference Validation
42
+ Enforces compliance against an official Cisco IOS Command Reference index:
43
+ * **Strict Validation Mode:** Enabled via `--strict-command-ref` (or setting `CISCOLLM_STRICT_COMMAND_REF=true`). The agent will block any command not listed in the command-reference index.
44
+ * **PDF Command Indexer:** The engine reads `cf_command_ref.pdf`, extracts structural commands, and caches them in `.cache/cf_command_ref.index.json` to speed up startup times.
45
+ * **Fuzzy Command Family Expansion:** Automatically expands shortened commands (e.g. `sh` -> `show`, `conf t` -> `configure terminal`, `int gig0/1` -> `interface GigabitEthernet0/1`) to check they match valid command families in the index.
46
+ * **Reference Telemetry:** Telemetry logs detail the warmup time, source (PDF, cache, or memory), and matched command count. Can be disabled with `--no-ref-telemetry`.
47
+
48
+ ### 6. 🧪 Mock Device Simulation
49
+ Mock mode (`--protocol mock`) provides a stateful simulation of a Cisco IOS device, allowing offline development and testing. It features:
50
+ * **Stateful Database:** Maintains interfaces, VLANs, shell variables, shell functions, and static routing tables, saved locally under `.mock-state-<device>.json`.
51
+ * **Interactive CLI Simulator:** Simulates user and privileged EXEC modes, configuration modes, VLAN databases, DHCP pools, OSPF routers, and IP routing tables.
52
+ * **Output Filtering (Pipes):** Supports standard IOS output piping such as `| include`, `| grep`, `| exclude`, and `| begin`.
53
+ * **Network Testing:** Simulates latency and ICMP ping responses.
54
+
55
+ ### 7. 🛡️ Pre-Execution Safety Validation (Dry-Run Check)
56
+ Before executing any state-mutating command, the agent runs a dry-run check against the known network topology:
57
+ * **Topology Awareness:** Analyzes active physical/logical links between core, distribution, and access segments.
58
+ * **Accidental Disruption Prevention:** Detects and intercepts commands that could accidentally shut down critical uplink ports or neighbor nodes, ensuring continuous uptime.
59
+
60
+ ### 8. 📊 Live Configuration State Diff Engine
61
+ Maintains deep visibility of system modifications:
62
+ * **Before/After Snapshots:** Takes memory-efficient snapshots of device interfaces, IP addresses, subnets, routing tables, and active VLAN databases before and after executing any command.
63
+ * **Visual Colorized Diffs:** Automatically outputs a structured difference report highlighting additions in green, removals in red, and updates/modifications in yellow.
64
+
65
+ ### 9. 🪵 Continuous Enterprise Audit Trails
66
+ Ensures accountability for automated activities:
67
+ * **Detailed Logs:** Generates structured records containing the timestamp, target device, active agent role, LLM reasoning thoughts, executed commands, and final output status.
68
+ * **Local Audit Store:** Persists all interactions locally to `audit.log` for easy integration with standard security information and event management (SIEM) systems.
69
+
70
+ ### 10. 🔀 Hierarchical Network Swarms
71
+ Supports role-specific command delegation and intelligence:
72
+ * **Role Routing:** Multi-agent coordinator routes tasks to specialized personalities—**Core Agent**, **Distribution Agent**, and **Access Agent**—matching the logical tier of the configuration task.
73
+ * **RBAC Constraints:** Restricts operations according to the `--rbac-role` parameter. The `read_only` role safely blocks any modifying actions and logs violations to the audit log.
74
+
75
+ ### 11. 🔌 NETCONF & CML Simulation Adapters
76
+ Extends sandbox capabilities beyond local mock devices:
77
+ * **Cisco Modeling Labs (CML):** Provides sessions to interact directly with digital twin network simulations.
78
+ * **NETCONF XML Sessions:** Supports programmatic configuration using structured XML RPC calls and YANG schemas.
79
+
80
+ ---
81
+
82
+ ## 📦 Installation
83
+
84
+ To install `ciscollm-cli` globally from npm:
8
85
 
9
86
  ```bash
10
87
  npm install -g ciscollm-cli
11
88
  ```
12
89
 
13
- After installation, the `ciscollm` command becomes available in your terminal.
90
+ Once installed, the global executable `ciscollm` becomes available.
14
91
 
15
- ## Quick Start
92
+ ---
16
93
 
17
- Show the available commands and flags:
94
+ ## 🛠️ CLI Usage & Options
18
95
 
19
96
  ```bash
20
- ciscollm run --help
97
+ ciscollm run [options]
21
98
  ```
22
99
 
23
- Run the agent in mock mode for a safe offline simulation:
24
-
100
+ ### Options Table
101
+
102
+ | Option / Flag | Alias | Description | Default Value |
103
+ |---|---|---|---|
104
+ | `-g, --goal <intent>` | - | The goal of the configuration/troubleshooting task. If not specified, launches the Interactive Setup Wizard. | - |
105
+ | `--protocol <type>` | - | Connection protocol (`serial`, `ssh`, `telnet`, `mock`, `netconf`, `cml`). | `serial` |
106
+ | `--provider <type>` | - | LLM provider mode (`local`, `cloud`). | `local` |
107
+ | `--local-type <type>` | - | Local LLM server flavor (`ollama`, `lmstudio`). | `ollama` |
108
+ | `--model <name>` | - | Name of the LLM model to compile. | - |
109
+ | `--endpoint <url>` | - | The LLM API endpoint URL. | - |
110
+ | `--api-key <key>` | - | API key for the cloud provider (OpenRouter). | - |
111
+ | `-c, --com <ports>` | - | COM Port(s), comma-separated (e.g., `COM3` or `COM3,COM4`). | - |
112
+ | `-b, --baud <rate>` | - | Serial transmission baud rate. | `9600` |
113
+ | `--host <address>` | - | Target IP address or hostname (comma-separated for multi-device). | - |
114
+ | `--port <port>` | - | Target connection port. | - |
115
+ | `-u, --username <name>` | - | Device login username. | - |
116
+ | `-p, --password <pass>` | - | Device login password. | - |
117
+ | `--strict-command-ref` | - | Block commands not found in the `cf_command_ref.pdf` index. | `false` |
118
+ | `--no-ref-telemetry` | - | Disable command-reference warmup telemetry logs. | `false` |
119
+ | `--non-interactive` | - | Run without interactive prompts (auto-rejects dangerous commands). | `false` |
120
+ | `--rbac-role <role>` | - | Specify the Active Agent RBAC authorization role (`admin`, `read_only`). | `admin` |
121
+
122
+ ---
123
+
124
+ ## 💡 Usage Examples
125
+
126
+ ### 1. Launching the Interactive Setup Wizard
127
+ Start the interactive CLI configuration process:
25
128
  ```bash
26
- ciscollm run --protocol mock --goal "Review LAN IP allocation for 192.168.1.0/24"
129
+ ciscollm run
27
130
  ```
28
131
 
29
- ## What It Does
30
-
31
- The CLI takes a user goal, sends it to the selected LLM provider, and lets the agent choose Cisco IOS tool calls step by step.
32
-
33
- It can:
34
- - inspect and modify Cisco-style configuration
35
- - validate commands with guardrails
36
- - roll back failed configuration changes
37
- - simulate interfaces, IP addresses, shell commands, and ping checks in mock mode
38
-
39
- ## Common Usage
40
-
41
- Start a mock session for learning or testing:
42
-
132
+ ### 2. Running a Quick Mock Simulation
43
133
  ```bash
44
- ciscollm run --protocol mock --goal "Configure 192.168.1.1/24 on LAN A for 25 hosts"
134
+ ciscollm run --protocol mock --goal "Configure GigabitEthernet0/1 with IP 192.168.2.1/24 and interface description 'LAN B'"
45
135
  ```
46
136
 
47
- Use a local LLM endpoint:
48
-
137
+ ### 3. Local Model (Ollama)
49
138
  ```bash
50
- ciscollm run --provider local --local-type lmstudio --endpoint http://127.0.0.1:1234/v1 --protocol mock --goal "Open privileged exec and review IP design"
139
+ ciscollm run --provider local --local-type ollama --endpoint http://127.0.0.1:11434/v1 --model qwen3.5-4b --protocol mock --goal "Show IP routing table"
51
140
  ```
52
141
 
53
- Use cloud inference with an API key:
54
-
142
+ ### 4. Cloud Inference via OpenRouter
55
143
  ```bash
56
- ciscollm run --provider cloud --api-key YOUR_OPENROUTER_KEY --protocol mock --goal "Check interface status"
144
+ ciscollm run --provider cloud --api-key YOUR_OPENROUTER_API_KEY --protocol mock --goal "Verify interface states"
57
145
  ```
58
146
 
59
- Enable strict command-reference enforcement:
60
-
61
- ```bash
62
- ciscollm run --strict-command-ref --protocol mock --goal "Review LAN design"
63
- ```
64
-
65
- Disable startup telemetry if you want quieter output:
66
-
147
+ ### 5. Enforcing Strict Validation Mode
67
148
  ```bash
68
- ciscollm run --no-ref-telemetry --protocol mock --goal "Review LAN design"
149
+ ciscollm run --strict-command-ref --protocol mock --goal "Configure router ospf 1 and advertise network 192.168.1.0/24"
69
150
  ```
70
151
 
71
- ## Command Options
152
+ ---
72
153
 
73
- - `--protocol <type>`: Selects the device connection mode. Supported values: `serial`, `ssh`, `telnet`, `mock`.
74
- - `--provider <type>`: Selects the LLM provider. Supported values: `local`, `cloud`.
75
- - `--local-type <type>`: Chooses the local LLM server flavor. Supported values: `ollama`, `lmstudio`.
76
- - `--endpoint <url>`: Sets the LLM API endpoint.
77
- - `--model <name>`: Sets the model name to use.
78
- - `--strict-command-ref`: Blocks commands that are not found in the command-reference index.
79
- - `--no-ref-telemetry`: Turns off command-reference startup telemetry.
80
- - `--goal <intent>`: Describes the configuration or troubleshooting task.
154
+ ## 💻 Development & Contribution
81
155
 
82
- ## Local LLM Notes
156
+ Follow these steps to set up the project locally for development:
83
157
 
84
- If you use a local provider, make sure the endpoint is already running before starting the CLI.
85
-
86
- - Ollama usually listens on `http://127.0.0.1:11434/v1`
87
- - LM Studio usually listens on `http://127.0.0.1:1234/v1`
88
-
89
- If the endpoint is unavailable, the CLI will stop early with a clear preflight error.
90
-
91
- ## Mock Mode
92
-
93
- Mock mode is designed for safe offline testing.
94
-
95
- It simulates:
96
- - device connection and prompt modes
97
- - interface configuration
98
- - ping behavior
99
- - command parsing errors
100
- - shell variable and function behavior
101
-
102
- This is the recommended mode when you want to test agent behavior without hardware.
103
-
104
- ## Troubleshooting
158
+ ### 1. Clone & Install Dependencies
159
+ ```bash
160
+ git clone https://github.com/ThemeHackers/ciscollm-cli.git
161
+ cd ciscollm-cli
162
+ npm install
163
+ ```
105
164
 
106
- - If you see `ENEEDAUTH`, you are not logged into npm with an account that can publish or access the package.
107
- - If the CLI says the LLM endpoint is unreachable, start the local server or update `--endpoint`.
108
- - If a command is blocked in strict mode, it was not matched in the command-reference index.
165
+ ### 2. Build the Project
166
+ Compile the TypeScript code to target JavaScript inside `dist/`:
167
+ ```bash
168
+ npm run build
169
+ ```
109
170
 
110
- ## Package Name
171
+ ### 3. Run Development Build
172
+ Run the CLI locally from source code:
173
+ ```bash
174
+ npm start -- run --protocol mock --goal "Show running config"
175
+ ```
111
176
 
112
- The package name on npm is `ciscollm-cli`, and it exposes the `ciscollm` executable through the package `bin` entry.
177
+ ### 4. Run Unit Tests
178
+ Validate features including the Command Firewall, Transaction Manager, and Error Analyzer:
179
+ ```bash
180
+ npm run test
181
+ ```
@@ -6,5 +6,6 @@ export declare const logger: {
6
6
  error: (msg: string) => void;
7
7
  critical: (msg: string) => void;
8
8
  heading: (msg: string) => void;
9
+ reasoning: (msg: string) => void;
9
10
  };
10
11
  export declare function createSpinner(text: string): ora.Ora;
package/dist/cli/ui/ui.js CHANGED
@@ -8,12 +8,27 @@ exports.createSpinner = createSpinner;
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
9
  const ora_1 = __importDefault(require("ora"));
10
10
  exports.logger = {
11
- info: (msg) => console.log(chalk_1.default.blue('ℹ ') + msg),
12
- success: (msg) => console.log(chalk_1.default.green('✔ ') + chalk_1.default.bold(msg)),
11
+ info: (msg) => console.log(chalk_1.default.cyan('ℹ ') + msg),
12
+ success: (msg) => console.log(chalk_1.default.green('✔ ') + chalk_1.default.bold.green(msg)),
13
13
  warn: (msg) => console.warn(chalk_1.default.yellow('⚠ ') + chalk_1.default.yellow(msg)),
14
14
  error: (msg) => console.error(chalk_1.default.red('✖ ') + chalk_1.default.red.bold(msg)),
15
- critical: (msg) => console.error(chalk_1.default.bgRed.white.bold(' CRITICAL ') + ' ' + chalk_1.default.red(msg)),
16
- heading: (msg) => console.log('\n' + chalk_1.default.cyan.bold.underline(msg) + '\n')
15
+ critical: (msg) => {
16
+ console.error('\n' + chalk_1.default.bgRed.black.bold(' ⚡ CRITICAL ERROR ') + ' ' + chalk_1.default.red.bold(msg) + '\n');
17
+ },
18
+ heading: (msg) => {
19
+ const line = '━'.repeat(msg.length + 6);
20
+ console.log('\n' + chalk_1.default.magenta.bold(` ┏${line}┓`));
21
+ console.log(chalk_1.default.magenta.bold(` ┃ ${chalk_1.default.white.bold(msg)} ┃`));
22
+ console.log(chalk_1.default.magenta.bold(` ┗${line}┛`) + '\n');
23
+ },
24
+ reasoning: (msg) => {
25
+ const border = chalk_1.default.blue('│');
26
+ console.log('\n' + chalk_1.default.blue('┌─── 🤖 Agent Reasoning Thought Process ───────────────────────'));
27
+ msg.trim().split('\n').forEach(line => {
28
+ console.log(`${border} ${chalk_1.default.gray.italic(line)}`);
29
+ });
30
+ console.log(chalk_1.default.blue('└─────────────────────────────────────────────────────────────') + '\n');
31
+ }
17
32
  };
18
33
  function createSpinner(text) {
19
34
  return (0, ora_1.default)({
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../../src/cli/ui/ui.ts"],"names":[],"mappings":";;;;;;AAYA,sCAMC;AAlBD,kDAA0B;AAC1B,8CAAsB;AAET,QAAA,MAAM,GAAG;IAClB,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;IAC1D,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,eAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1E,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,eAAK,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,eAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3E,KAAK,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,eAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,eAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5E,QAAQ,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,eAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,GAAG,GAAG,eAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACrG,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,eAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;CACtF,CAAC;AAEF,SAAgB,aAAa,CAAC,IAAY;IACtC,OAAO,IAAA,aAAG,EAAC;QACP,IAAI;QACJ,KAAK,EAAE,MAAM;QACb,OAAO,EAAE,MAAM;KAClB,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../../src/cli/ui/ui.ts"],"names":[],"mappings":";;;;;;AA2BA,sCAMC;AAjCD,kDAA0B;AAC1B,8CAAsB;AAET,QAAA,MAAM,GAAG;IAClB,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;IAC1D,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,eAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,eAAK,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,eAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3E,KAAK,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,eAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,eAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5E,QAAQ,EAAE,CAAC,GAAW,EAAE,EAAE;QACtB,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,eAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,GAAG,GAAG,eAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC1G,CAAC;IACD,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE;QACrB,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,eAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,eAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QACpE,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC1D,CAAC;IACD,SAAS,EAAE,CAAC,GAAW,EAAE,EAAE;QACvB,MAAM,MAAM,GAAG,eAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,eAAK,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC,CAAC;QAClG,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YAClC,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,KAAK,eAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,gEAAgE,CAAC,GAAG,IAAI,CAAC,CAAC;IACrG,CAAC;CACJ,CAAC;AAEF,SAAgB,aAAa,CAAC,IAAY;IACtC,OAAO,IAAA,aAAG,EAAC;QACP,IAAI;QACJ,KAAK,EAAE,MAAM;QACb,OAAO,EAAE,MAAM;KAClB,CAAC,CAAC;AACP,CAAC"}
@@ -3,6 +3,7 @@ import { MultiAgentCoordinator } from './MultiAgentCoordinator';
3
3
  type AgentLoopOptions = {
4
4
  strictReferenceMode?: boolean;
5
5
  referenceTelemetry?: boolean;
6
+ rbacRole?: string;
6
7
  };
7
8
  export declare class CiscoAgentLoop {
8
9
  private llmClient;
@@ -15,17 +16,22 @@ export declare class CiscoAgentLoop {
15
16
  private commandReferenceEngine;
16
17
  private strictReferenceMode;
17
18
  private referenceTelemetry;
19
+ private rbacRole;
18
20
  private options;
19
21
  private validationNudgeCount;
22
+ private lastTopologyDiscoveryAt;
23
+ private readonly topologyRefreshIntervalMs;
20
24
  constructor(llmClient: LLMClient, coordinator: MultiAgentCoordinator, options?: AgentLoopOptions);
21
25
  private applyOptions;
22
26
  run(userGoal: string): Promise<void>;
27
+ private buildTopologyInfoString;
23
28
  private triggerAutomaticValidationPing;
24
29
  private resolveValidationDestination;
25
30
  private buildStateInfoString;
26
31
  private findLastMutatedDevice;
27
32
  private resolveTargetDevice;
28
33
  private handleExecuteCommandCall;
34
+ private captureDeviceSnapshot;
29
35
  private handleEnableIosShellCall;
30
36
  private handleDefineShellVariableCall;
31
37
  private handleExecuteShellLoopCall;
@@ -13,6 +13,10 @@ const ToolDefinitions_1 = require("../../infrastructure/llm/ToolDefinitions");
13
13
  const child_process_1 = require("child_process");
14
14
  const ui_1 = require("../../cli/ui/ui");
15
15
  const chalk_1 = __importDefault(require("chalk"));
16
+ const PreExecutionValidator_1 = require("../guardrails/PreExecutionValidator");
17
+ const AuditLogger_1 = require("../guardrails/AuditLogger");
18
+ const HierarchicalAgentManager_1 = require("./HierarchicalAgentManager");
19
+ const StateDiff_1 = require("../rollback/StateDiff");
16
20
  class CiscoAgentLoop {
17
21
  llmClient;
18
22
  coordinator;
@@ -24,8 +28,11 @@ class CiscoAgentLoop {
24
28
  commandReferenceEngine = CommandReferenceEngine_1.CommandReferenceEngine.getInstance();
25
29
  strictReferenceMode = false;
26
30
  referenceTelemetry = true;
31
+ rbacRole = 'admin';
27
32
  options;
28
33
  validationNudgeCount = 0;
34
+ lastTopologyDiscoveryAt = 0;
35
+ topologyRefreshIntervalMs = 15000;
29
36
  constructor(llmClient, coordinator, options = {}) {
30
37
  this.llmClient = llmClient;
31
38
  this.coordinator = coordinator;
@@ -39,6 +46,9 @@ class CiscoAgentLoop {
39
46
  if (typeof options.strictReferenceMode === 'boolean') {
40
47
  this.commandReferenceEngine.setStrictMode(options.strictReferenceMode);
41
48
  }
49
+ if (options.rbacRole) {
50
+ this.rbacRole = options.rbacRole.toLowerCase();
51
+ }
42
52
  }
43
53
  async run(userGoal) {
44
54
  const backupSpinner = (0, ui_1.createSpinner)('Initializing device configuration backups to flash...').start();
@@ -72,9 +82,10 @@ class CiscoAgentLoop {
72
82
  refSpinner.warn('Cisco command reference hints unavailable. Continuing with base policy.');
73
83
  }
74
84
  const stateInfo = this.buildStateInfoString();
85
+ const topologyInfo = await this.buildTopologyInfoString();
75
86
  this.messages.push({
76
87
  role: 'system',
77
- content: PromptEngine_1.PromptEngine.getSystemPrompt(stateInfo, this.commandHints, this.strictReferenceMode)
88
+ content: PromptEngine_1.PromptEngine.getSystemPrompt(stateInfo, this.commandHints, this.strictReferenceMode, topologyInfo)
78
89
  });
79
90
  this.messages.push({ role: 'user', content: userGoal });
80
91
  let dynamicLoopActive = true;
@@ -83,9 +94,10 @@ class CiscoAgentLoop {
83
94
  while (dynamicLoopActive && executionDepth < MAX_STEPS) {
84
95
  executionDepth++;
85
96
  const updatedStateInfo = this.buildStateInfoString();
97
+ const updatedTopologyInfo = await this.buildTopologyInfoString();
86
98
  this.messages[0] = {
87
99
  role: 'system',
88
- content: PromptEngine_1.PromptEngine.getSystemPrompt(updatedStateInfo, this.commandHints, this.strictReferenceMode)
100
+ content: PromptEngine_1.PromptEngine.getSystemPrompt(updatedStateInfo, this.commandHints, this.strictReferenceMode, updatedTopologyInfo)
89
101
  };
90
102
  let shellEnabled = false;
91
103
  for (const session of this.coordinator.getSessions().values()) {
@@ -109,11 +121,7 @@ class CiscoAgentLoop {
109
121
  modelSpinner.succeed(`[Step ${executionDepth}/${MAX_STEPS}] Thinking complete.`);
110
122
  const thoughts = response.reasoning_content || response.content;
111
123
  if (thoughts && thoughts.trim()) {
112
- console.log(chalk_1.default.gray(' ' + '─'.repeat(40)));
113
- thoughts.trim().split('\n').forEach(line => {
114
- console.log(chalk_1.default.dim(` | ${line}`));
115
- });
116
- console.log(chalk_1.default.gray(' ' + '─'.repeat(40)));
124
+ ui_1.logger.reasoning(thoughts);
117
125
  }
118
126
  }
119
127
  catch (err) {
@@ -173,6 +181,21 @@ class CiscoAgentLoop {
173
181
  ui_1.logger.warn('Maximum loop steps limit reached.');
174
182
  }
175
183
  }
184
+ async buildTopologyInfoString() {
185
+ const now = Date.now();
186
+ if (now - this.lastTopologyDiscoveryAt > this.topologyRefreshIntervalMs) {
187
+ await this.coordinator.discoverTopology();
188
+ this.lastTopologyDiscoveryAt = now;
189
+ }
190
+ const topology = this.coordinator.getTopology();
191
+ if (topology.links.length === 0) {
192
+ return `Discovered at: ${topology.discoveredAt}\nNodes: ${topology.nodes.join(', ') || '(none)'}\nLinks: none`;
193
+ }
194
+ const links = topology.links
195
+ .map(link => `- ${link.localDeviceId} [${link.localInterface}] <-> ${link.remoteDeviceId} [${link.remoteInterface}] via ${link.protocol.toUpperCase()}`)
196
+ .join('\n');
197
+ return `Discovered at: ${topology.discoveredAt}\nNodes: ${topology.nodes.join(', ')}\n${links}`;
198
+ }
176
199
  async triggerAutomaticValidationPing(deviceId) {
177
200
  const destination = await this.resolveValidationDestination(deviceId);
178
201
  const fallbackCall = {
@@ -206,7 +229,6 @@ class CiscoAgentLoop {
206
229
  }
207
230
  }
208
231
  catch {
209
- // Fall through to safe default.
210
232
  }
211
233
  return '127.0.0.1';
212
234
  }
@@ -257,6 +279,26 @@ class CiscoAgentLoop {
257
279
  const targetCommand = args.command;
258
280
  const requestedDevice = args.device;
259
281
  const cleanCommand = targetCommand.trim();
282
+ const assignedRole = HierarchicalAgentManager_1.HierarchicalAgentManager.routeCommand(cleanCommand);
283
+ if (this.rbacRole === 'read_only') {
284
+ const lower = cleanCommand.toLowerCase();
285
+ const allowedReadOnlyPatterns = [/^show\s+/i, /^ping\s+/i, /^dir\s+/i, /^terminal\s+/i, /^exit$/i, /^end$/i];
286
+ const isAllowedReadOnly = allowedReadOnlyPatterns.some(p => p.test(lower));
287
+ if (!isAllowedReadOnly) {
288
+ ui_1.logger.warn(`[RBAC BLOCK] Read-Only role blocked command: "${cleanCommand}"`);
289
+ this.injectToolResponse(call.id, 'execute_ios_command', `CRITICAL ERROR: RBAC policy violation. Your current role is READ_ONLY and you are blocked from executing modifying command "${cleanCommand}".`);
290
+ AuditLogger_1.AuditLogger.log({
291
+ timestamp: new Date().toISOString(),
292
+ deviceId: requestedDevice || 'unknown',
293
+ role: this.rbacRole,
294
+ thought: this.messages[this.messages.length - 1]?.content || '',
295
+ command: cleanCommand,
296
+ status: 'BLOCKED',
297
+ reason: 'RBAC role is READ_ONLY'
298
+ });
299
+ return;
300
+ }
301
+ }
260
302
  const commandValidation = await this.commandReferenceEngine.validateCommand(cleanCommand);
261
303
  if (this.strictReferenceMode && !commandValidation.allowed) {
262
304
  const suggestions = commandValidation.suggestions.length
@@ -273,6 +315,26 @@ class CiscoAgentLoop {
273
315
  this.injectToolResponse(call.id, 'execute_ios_command', `Error: ${err.message}`);
274
316
  return;
275
317
  }
318
+ const tx = this.transactions.get(targetDeviceId);
319
+ const currentInterface = tx ? tx.targetInterface : null;
320
+ const dryRunCheck = PreExecutionValidator_1.PreExecutionValidator.validateCommand(cleanCommand, targetDeviceId, this.coordinator.getTopology(), currentInterface);
321
+ if (dryRunCheck.warnLevel === 'CRITICAL' || !dryRunCheck.safe) {
322
+ ui_1.logger.warn(`[PRE-EXECUTION WARNING] Critical risk detected: ${dryRunCheck.reason}`);
323
+ const allowed = await this.firewall.verifyWithHuman(cleanCommand, dryRunCheck.reason || 'High risk context');
324
+ if (!allowed) {
325
+ this.injectToolResponse(call.id, 'execute_ios_command', `CRITICAL ERROR: Pre-execution validation blocked this command. Reason: ${dryRunCheck.reason}`);
326
+ AuditLogger_1.AuditLogger.log({
327
+ timestamp: new Date().toISOString(),
328
+ deviceId: targetDeviceId,
329
+ role: this.rbacRole,
330
+ thought: this.messages[this.messages.length - 1]?.content || '',
331
+ command: cleanCommand,
332
+ status: 'BLOCKED',
333
+ reason: dryRunCheck.reason
334
+ });
335
+ return;
336
+ }
337
+ }
276
338
  const lastCmdInfo = this.lastCommandPerDevice[targetDeviceId];
277
339
  if (lastCmdInfo && lastCmdInfo.command === cleanCommand) {
278
340
  lastCmdInfo.count++;
@@ -285,8 +347,6 @@ class CiscoAgentLoop {
285
347
  this.injectToolResponse(call.id, 'execute_ios_command', `CRITICAL ERROR: Loop check block. You have run "${cleanCommand}" multiple times with errors. Re-verify your settings before retrying.`);
286
348
  return;
287
349
  }
288
- const tx = this.transactions.get(targetDeviceId);
289
- const currentInterface = tx ? tx.targetInterface : null;
290
350
  const firewallResult = this.firewall.checkCommand(cleanCommand, currentInterface);
291
351
  if (firewallResult.dangerous) {
292
352
  const allowed = await this.firewall.verifyWithHuman(cleanCommand, firewallResult.reason || 'High-risk token');
@@ -298,10 +358,10 @@ class CiscoAgentLoop {
298
358
  if (tx) {
299
359
  tx.trackMutation(cleanCommand);
300
360
  }
301
- const cmdSpinner = (0, ui_1.createSpinner)(`[${targetDeviceId}] Executing command: "${cleanCommand}"...`).start();
361
+ const snapshotA = await this.captureDeviceSnapshot(targetDeviceId);
362
+ const cmdSpinner = (0, ui_1.createSpinner)(`[${targetDeviceId}] [Agent: ${assignedRole}] Executing command: "${cleanCommand}"...`).start();
302
363
  try {
303
364
  const session = this.coordinator.getSession(targetDeviceId);
304
- // Normalize context for read-only inspection commands to avoid mode-related failures.
305
365
  const state = session.getState();
306
366
  if (/^show\s+/i.test(cleanCommand) && (state.currentMode === 'GLOBAL_CONFIG' || state.currentMode === 'INTERFACE_CONFIG')) {
307
367
  await session.execute('end');
@@ -317,11 +377,38 @@ class CiscoAgentLoop {
317
377
  rollbackLogs = await tx.executeRollback(session);
318
378
  }
319
379
  rbSpinner.succeed(`[${targetDeviceId}] Rollback execution complete.`);
380
+ AuditLogger_1.AuditLogger.log({
381
+ timestamp: new Date().toISOString(),
382
+ deviceId: targetDeviceId,
383
+ role: this.rbacRole,
384
+ thought: this.messages[this.messages.length - 2]?.content || '',
385
+ command: cleanCommand,
386
+ status: 'ROLLBACK',
387
+ reason: `Rollback triggered by error ${verification.errorType}. Reverted state successfully.`
388
+ });
320
389
  this.injectToolResponse(call.id, 'execute_ios_command', `IOS Error [${verification.errorType}]:\n${processedOutput}\n\nAutomated configuration rollback executed:\n${rollbackLogs}`);
321
390
  }
322
391
  else {
323
392
  cmdSpinner.succeed(`[${targetDeviceId}] Command completed: "${cleanCommand}"`);
324
- this.injectToolResponse(call.id, 'execute_ios_command', processedOutput);
393
+ const snapshotB = await this.captureDeviceSnapshot(targetDeviceId);
394
+ let diffSummary = '';
395
+ if (snapshotA && snapshotB) {
396
+ const diffResult = StateDiff_1.StateDiff.diff(snapshotA, snapshotB);
397
+ diffSummary = StateDiff_1.StateDiff.renderDiff(diffResult);
398
+ if (diffSummary && diffSummary !== 'No configuration differences detected.') {
399
+ ui_1.logger.info(`[State Diff for ${targetDeviceId}]:\n${diffSummary}`);
400
+ }
401
+ }
402
+ AuditLogger_1.AuditLogger.log({
403
+ timestamp: new Date().toISOString(),
404
+ deviceId: targetDeviceId,
405
+ role: this.rbacRole,
406
+ thought: this.messages[this.messages.length - 2]?.content || '',
407
+ command: cleanCommand,
408
+ status: 'SUCCESS',
409
+ outputSnippet: `${processedOutput}${diffSummary ? `\nState Changes:\n${diffSummary}` : ''}`
410
+ });
411
+ this.injectToolResponse(call.id, 'execute_ios_command', processedOutput + (diffSummary ? `\n\n[State Diff Configured]:\n${diffSummary}` : ''));
325
412
  }
326
413
  }
327
414
  catch (error) {
@@ -329,6 +416,41 @@ class CiscoAgentLoop {
329
416
  this.injectToolResponse(call.id, 'execute_ios_command', `Hardware Session Fault: ${error.message}`);
330
417
  }
331
418
  }
419
+ async captureDeviceSnapshot(deviceId) {
420
+ const session = this.coordinator.getSession(deviceId);
421
+ if (!session)
422
+ return null;
423
+ if (typeof session.interfaces !== 'undefined') {
424
+ const mock = session;
425
+ const interfaceList = [];
426
+ for (const [name, conf] of mock.interfaces.entries()) {
427
+ interfaceList.push({
428
+ name,
429
+ ip: conf.ip,
430
+ subnet: conf.subnet,
431
+ adminShutdown: conf.adminShutdown,
432
+ lineProtocolUp: conf.lineProtocolUp,
433
+ description: conf.description
434
+ });
435
+ }
436
+ return {
437
+ deviceId,
438
+ timestamp: new Date().toISOString(),
439
+ sessionState: session.getState(),
440
+ interfaces: interfaceList,
441
+ routes: [...mock.routes],
442
+ vlans: Array.from(mock.vlans)
443
+ };
444
+ }
445
+ return {
446
+ deviceId,
447
+ timestamp: new Date().toISOString(),
448
+ sessionState: session.getState(),
449
+ interfaces: [],
450
+ routes: [],
451
+ vlans: []
452
+ };
453
+ }
332
454
  async handleEnableIosShellCall(call) {
333
455
  let args;
334
456
  try {
@@ -523,8 +645,8 @@ class CiscoAgentLoop {
523
645
  async pingFromHost(destination) {
524
646
  return new Promise((resolve) => {
525
647
  const isWindows = process.platform === 'win32';
526
- const cmd = isWindows ? `ping -n 4 ${destination}` : `ping -c 4 ${destination}`;
527
- (0, child_process_1.exec)(cmd, (error, stdout, stderr) => {
648
+ const pingArgs = isWindows ? ['-n', '4', destination] : ['-c', '4', destination];
649
+ (0, child_process_1.execFile)('ping', pingArgs, (error, stdout, stderr) => {
528
650
  if (error) {
529
651
  resolve(`PING FAILED:\n${stdout || stderr || error.message}`);
530
652
  return;