cisco-axl 2.0.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +26 -0
- package/.github/FUNDING.yml +1 -0
- package/.github/workflows/ci.yml +1 -1
- package/.github/workflows/release.yml +6 -6
- package/README.md +50 -272
- package/claude-hooks.json +30 -0
- package/cli/commands/add.js +15 -5
- package/cli/commands/config.js +17 -23
- package/cli/commands/doctor.js +134 -0
- package/cli/commands/execute.js +15 -5
- package/cli/commands/remove.js +1 -1
- package/cli/commands/sql.js +1 -1
- package/cli/commands/update.js +15 -5
- package/cli/index.js +15 -2
- package/cli/utils/config.js +19 -4
- package/cli/utils/confirm.js +34 -0
- package/cli/utils/connection.js +5 -1
- package/cli/utils/readonly.js +13 -18
- package/cli/utils/stdin.js +20 -0
- package/cli/utils/wordlist.js +9 -0
- package/docs/api.md +191 -0
- package/docs/claude-code-hooks.md +85 -0
- package/docs/cli.md +109 -0
- package/package.json +3 -2
- package/skills/cisco-axl-cli/SKILL.md +61 -11
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cisco-axl",
|
|
3
|
+
"description": "Cisco CUCM AXL CLI skills for provisioning phones, lines, route patterns, partitions, CSS, and more via the Administrative XML API",
|
|
4
|
+
"version": "2.0.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Jeremy Worden",
|
|
7
|
+
"url": "https://github.com/sieteunoseis"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/sieteunoseis/cisco-axl#readme",
|
|
10
|
+
"repository": "https://github.com/sieteunoseis/cisco-axl",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cisco",
|
|
14
|
+
"cucm",
|
|
15
|
+
"axl",
|
|
16
|
+
"unified-communications",
|
|
17
|
+
"voip",
|
|
18
|
+
"telephony",
|
|
19
|
+
"provisioning",
|
|
20
|
+
"soap",
|
|
21
|
+
"callmanager",
|
|
22
|
+
"cli",
|
|
23
|
+
"automation",
|
|
24
|
+
"bulk-provisioning"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
buy_me_a_coffee: automatebldrs
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -3,11 +3,11 @@ name: Release
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
5
|
tags:
|
|
6
|
-
-
|
|
6
|
+
- "v*"
|
|
7
7
|
|
|
8
8
|
permissions:
|
|
9
|
-
id-token: write
|
|
10
|
-
contents: write
|
|
9
|
+
id-token: write # Required for OIDC authentication
|
|
10
|
+
contents: write # Required for creating GitHub Release
|
|
11
11
|
|
|
12
12
|
jobs:
|
|
13
13
|
publish:
|
|
@@ -19,8 +19,8 @@ jobs:
|
|
|
19
19
|
- name: Setup Node.js
|
|
20
20
|
uses: actions/setup-node@v4
|
|
21
21
|
with:
|
|
22
|
-
node-version:
|
|
23
|
-
registry-url:
|
|
22
|
+
node-version: "22"
|
|
23
|
+
registry-url: "https://registry.npmjs.org"
|
|
24
24
|
|
|
25
25
|
- name: Update npm
|
|
26
26
|
run: npm install -g npm@latest
|
|
@@ -41,7 +41,7 @@ jobs:
|
|
|
41
41
|
run: node -e "const axlService = require('./dist/index.js'); console.log('CJS require OK');"
|
|
42
42
|
|
|
43
43
|
- name: Publish to npm
|
|
44
|
-
run: npm publish --access public
|
|
44
|
+
run: npm publish --access public --provenance
|
|
45
45
|
|
|
46
46
|
- name: Create GitHub Release
|
|
47
47
|
uses: softprops/action-gh-release@v2
|
package/README.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Cisco AXL Library & CLI
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/cisco-axl)
|
|
4
|
+
[](https://github.com/sieteunoseis/cisco-axl/actions/workflows/ci.yml)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](https://skills.sh/sieteunoseis/cisco-axl)
|
|
8
|
+
[](https://buymeacoffee.com/automatebldrs)
|
|
9
|
+
|
|
3
10
|
A JavaScript library and CLI to interact with Cisco CUCM via AXL SOAP API. Dynamically discovers all AXL operations from the WSDL schema — any operation for your specified version is available without static definitions.
|
|
4
11
|
|
|
5
12
|
Administrative XML (AXL) information can be found at:
|
|
@@ -26,7 +33,7 @@ npx cisco-axl --help
|
|
|
26
33
|
### AI Agent Skills
|
|
27
34
|
|
|
28
35
|
```bash
|
|
29
|
-
npx
|
|
36
|
+
npx skills add sieteunoseis/cisco-axl
|
|
30
37
|
```
|
|
31
38
|
|
|
32
39
|
## Requirements
|
|
@@ -35,11 +42,7 @@ If you are using self-signed certificates on Cisco VOS products you may need to
|
|
|
35
42
|
|
|
36
43
|
Supported CUCM versions: `11.0`, `11.5`, `12.0`, `12.5`, `14.0`, `15.0`
|
|
37
44
|
|
|
38
|
-
##
|
|
39
|
-
|
|
40
|
-
The CLI provides full AXL access from the command line — CRUD operations, SQL queries, operation discovery, bulk provisioning from CSV, and a raw execute escape hatch for any AXL operation.
|
|
41
|
-
|
|
42
|
-
### Quick Start
|
|
45
|
+
## Quick Start
|
|
43
46
|
|
|
44
47
|
```bash
|
|
45
48
|
# Configure a cluster
|
|
@@ -52,27 +55,31 @@ cisco-axl config test
|
|
|
52
55
|
cisco-axl list Phone --search "name=SEP%"
|
|
53
56
|
|
|
54
57
|
# Get a specific phone
|
|
55
|
-
cisco-axl get Phone SEP001122334455
|
|
58
|
+
cisco-axl get Phone SEP001122334455
|
|
56
59
|
|
|
57
60
|
# SQL query
|
|
58
61
|
cisco-axl sql query "SELECT name, description FROM device WHERE name LIKE 'SEP%'"
|
|
62
|
+
```
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
cisco-axl operations --filter phone
|
|
62
|
-
cisco-axl operations --type action --filter phone
|
|
63
|
-
|
|
64
|
-
# Describe what tags an operation needs
|
|
65
|
-
cisco-axl describe getPhone --detailed
|
|
64
|
+
## Configuration
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
cisco-axl
|
|
66
|
+
```bash
|
|
67
|
+
cisco-axl config add <name> --host <host> --username <user> --password <pass> --cucm-version <ver> --insecure
|
|
68
|
+
cisco-axl config use <name> # switch active cluster
|
|
69
|
+
cisco-axl config list # list all clusters
|
|
70
|
+
cisco-axl config show # show active cluster (masks passwords)
|
|
71
|
+
cisco-axl config remove <name> # remove a cluster
|
|
72
|
+
cisco-axl config test # test connectivity
|
|
69
73
|
```
|
|
70
74
|
|
|
71
|
-
|
|
75
|
+
Auth precedence: CLI flags > env vars (`CUCM_HOST`, `CUCM_USERNAME`, `CUCM_PASSWORD`, `CUCM_VERSION`) > config file.
|
|
76
|
+
|
|
77
|
+
Config stored at `~/.cisco-axl/config.json`. Supports [ss-cli](https://github.com/sieteunoseis/ss-cli) `<ss:ID:field>` placeholders.
|
|
78
|
+
|
|
79
|
+
## CLI Commands
|
|
72
80
|
|
|
73
81
|
| Command | Description |
|
|
74
82
|
|---------|-------------|
|
|
75
|
-
| `config add/use/list/show/remove/test` | Manage multi-cluster configurations |
|
|
76
83
|
| `get <type> <identifier>` | Get a single item |
|
|
77
84
|
| `list <type>` | List items with search, pagination, returned tags |
|
|
78
85
|
| `add <type>` | Add an item (inline JSON, template, or bulk CSV) |
|
|
@@ -80,281 +87,52 @@ cisco-axl execute doLdapSync --tags '{"name":"LDAP_Main"}'
|
|
|
80
87
|
| `remove <type> <identifier>` | Remove an item |
|
|
81
88
|
| `sql query/update` | Execute SQL against CUCM |
|
|
82
89
|
| `execute <operation>` | Run any raw AXL operation |
|
|
83
|
-
| `operations` | List available operations
|
|
84
|
-
| `describe <operation>` | Show tag schema
|
|
85
|
-
|
|
86
|
-
### Configuration
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
# Multiple clusters
|
|
90
|
-
cisco-axl config add lab --host 10.0.0.1 --username admin --password secret --cucm-version 14.0 --insecure
|
|
91
|
-
cisco-axl config add prod --host 10.0.0.2 --username axladmin --password secret --cucm-version 15.0 --insecure
|
|
92
|
-
cisco-axl config use prod
|
|
93
|
-
cisco-axl config list
|
|
90
|
+
| `operations` | List available operations |
|
|
91
|
+
| `describe <operation>` | Show tag schema for an operation |
|
|
92
|
+
| `doctor` | Check AXL connectivity and health |
|
|
94
93
|
|
|
95
|
-
|
|
96
|
-
cisco-axl list Phone --search "name=SEP%" --cluster lab
|
|
94
|
+
See [full CLI reference](docs/cli.md) for bulk CSV, command chaining, piping with `--stdin`, and operation discovery.
|
|
97
95
|
|
|
98
|
-
|
|
99
|
-
export CUCM_HOST=10.0.0.1 CUCM_USERNAME=admin CUCM_PASSWORD=secret CUCM_VERSION=14.0
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Config stored at `~/.cisco-axl/config.json`. Supports optional [Secret Server](https://github.com/sieteunoseis/ss-cli) integration via `<ss:ID:field>` placeholders.
|
|
103
|
-
|
|
104
|
-
### Output Formats
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
cisco-axl list Phone --search "name=SEP%" --format table # default, human-readable
|
|
108
|
-
cisco-axl list Phone --search "name=SEP%" --format json # structured JSON
|
|
109
|
-
cisco-axl list Phone --search "name=SEP%" --format toon # token-efficient for AI agents
|
|
110
|
-
cisco-axl list Phone --search "name=SEP%" --format csv # spreadsheet export
|
|
111
|
-
```
|
|
96
|
+
## Global Flags
|
|
112
97
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
# Single template with inline vars
|
|
123
|
-
cisco-axl add Phone --template phone-template.json --vars '{"mac":"001122334455","dp":"DP_HQ"}'
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
Template file (`phone-template.json`):
|
|
127
|
-
```json
|
|
128
|
-
{
|
|
129
|
-
"name": "SEP%%mac%%",
|
|
130
|
-
"devicePoolName": "%%devicePool%%",
|
|
131
|
-
"description": "%%description%%",
|
|
132
|
-
"protocol": "SIP"
|
|
133
|
-
}
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Global Flags
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
--format table|json|toon|csv Output format (default: table)
|
|
140
|
-
--insecure Skip TLS certificate verification
|
|
141
|
-
--clean Remove empty/null values from results
|
|
142
|
-
--no-attributes Remove XML attributes from results
|
|
143
|
-
--read-only Restrict to read-only operations
|
|
144
|
-
--no-audit Disable audit logging for this command
|
|
145
|
-
--debug Enable debug logging
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Audit Trail
|
|
149
|
-
|
|
150
|
-
All operations are logged to `~/.cisco-axl/audit.jsonl` (JSONL format). Credentials are never logged. Use `--no-audit` to skip.
|
|
98
|
+
| Flag | Description |
|
|
99
|
+
|------|-------------|
|
|
100
|
+
| `--format table\|json\|toon\|csv` | Output format (default: table) |
|
|
101
|
+
| `--insecure` | Skip TLS certificate verification |
|
|
102
|
+
| `--clean` | Remove empty/null values from results |
|
|
103
|
+
| `--no-attributes` | Remove XML attributes from results |
|
|
104
|
+
| `--read-only` | Restrict to read-only operations |
|
|
105
|
+
| `--no-audit` | Disable audit logging for this command |
|
|
106
|
+
| `--debug` | Enable debug logging |
|
|
151
107
|
|
|
152
108
|
## Library API
|
|
153
109
|
|
|
154
|
-
### Setup
|
|
155
|
-
|
|
156
110
|
```javascript
|
|
157
111
|
const axlService = require("cisco-axl");
|
|
112
|
+
const service = new axlService("10.10.20.1", "administrator", "ciscopsdt", "14.0");
|
|
158
113
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
// With options
|
|
162
|
-
let service = new axlService("10.10.20.1", "administrator", "ciscopsdt", "14.0", {
|
|
163
|
-
logging: { level: "info" },
|
|
164
|
-
retry: { retries: 3, retryDelay: 1000 }
|
|
165
|
-
});
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Logging
|
|
169
|
-
|
|
170
|
-
```javascript
|
|
171
|
-
// Via environment variable
|
|
172
|
-
// DEBUG=true
|
|
173
|
-
|
|
174
|
-
// Via constructor
|
|
175
|
-
let service = new axlService("10.10.20.1", "administrator", "ciscopsdt", "14.0", {
|
|
176
|
-
logging: {
|
|
177
|
-
level: "info", // "error" | "warn" | "info" | "debug"
|
|
178
|
-
handler: (level, message, data) => {
|
|
179
|
-
myLogger[level](message, data);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
// Change at runtime
|
|
185
|
-
service.setLogLevel("debug");
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### Convenience Methods
|
|
189
|
-
|
|
190
|
-
```javascript
|
|
191
|
-
// Get a single item by name or UUID
|
|
114
|
+
// Get, list, add, update, remove
|
|
192
115
|
await service.getItem("Phone", "SEP001122334455");
|
|
193
|
-
await service.
|
|
194
|
-
|
|
195
|
-
// List items with search criteria and returned tags
|
|
196
|
-
await service.listItems("RoutePartition"); // all partitions
|
|
197
|
-
await service.listItems("Phone", { name: "SEP%" }, { name: "", model: "" });
|
|
198
|
-
|
|
199
|
-
// Add, update, remove
|
|
116
|
+
await service.listItems("Phone", { name: "SEP%" });
|
|
200
117
|
await service.addItem("RoutePartition", { name: "NEW-PT", description: "New" });
|
|
201
|
-
await service.updateItem("Phone", "SEP001122334455", { description: "Updated" });
|
|
202
|
-
await service.removeItem("RoutePartition", "NEW-PT");
|
|
203
118
|
|
|
204
119
|
// SQL
|
|
205
|
-
|
|
206
|
-
await service.executeSqlUpdate("UPDATE routepartition SET description='test' WHERE name='NEW-PT'");
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### Operation Discovery
|
|
210
|
-
|
|
211
|
-
```javascript
|
|
212
|
-
// List all operations
|
|
213
|
-
const ops = await service.returnOperations();
|
|
214
|
-
const phoneOps = await service.returnOperations("phone");
|
|
215
|
-
|
|
216
|
-
// Get tag schema
|
|
217
|
-
const tags = await service.getOperationTags("addRoutePartition");
|
|
218
|
-
|
|
219
|
-
// Get detailed metadata (required, nillable, type)
|
|
220
|
-
const detailed = await service.getOperationTagsDetailed("addRoutePartition");
|
|
221
|
-
console.log(detailed.routePartition.required); // true
|
|
222
|
-
console.log(detailed.routePartition.children.name.type); // "string"
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### Execute Any Operation
|
|
120
|
+
await service.executeSqlQuery("SELECT name FROM routepartition");
|
|
226
121
|
|
|
227
|
-
|
|
122
|
+
// Any AXL operation
|
|
228
123
|
const tags = await service.getOperationTags("addRoutePartition");
|
|
229
124
|
tags.routePartition.name = "INTERNAL-PT";
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const result = await service.executeOperation("addRoutePartition", tags);
|
|
233
|
-
console.log("UUID:", result);
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
### Batch Operations
|
|
237
|
-
|
|
238
|
-
```javascript
|
|
239
|
-
const results = await service.executeBatch([
|
|
240
|
-
{ operation: "getPhone", tags: { name: "SEP001122334455" } },
|
|
241
|
-
{ operation: "getPhone", tags: { name: "SEP556677889900" } },
|
|
242
|
-
], 5); // concurrency limit
|
|
243
|
-
|
|
244
|
-
results.forEach((r) => {
|
|
245
|
-
console.log(r.success ? `${r.operation}: OK` : `${r.operation}: ${r.error.message}`);
|
|
246
|
-
});
|
|
125
|
+
await service.executeOperation("addRoutePartition", tags);
|
|
247
126
|
```
|
|
248
127
|
|
|
249
|
-
|
|
128
|
+
See [full API documentation](docs/api.md) for all methods, error handling, batch operations, TypeScript, retry config, and logging.
|
|
250
129
|
|
|
251
|
-
|
|
252
|
-
const { AXLAuthError, AXLNotFoundError, AXLOperationError, AXLValidationError } = require("cisco-axl");
|
|
253
|
-
|
|
254
|
-
try {
|
|
255
|
-
await service.executeOperation("getPhone", { name: "INVALID" });
|
|
256
|
-
} catch (error) {
|
|
257
|
-
if (error instanceof AXLAuthError) console.log("Bad credentials");
|
|
258
|
-
else if (error instanceof AXLNotFoundError) console.log("Operation not found:", error.operation);
|
|
259
|
-
else if (error instanceof AXLOperationError) console.log("SOAP fault:", error.message);
|
|
260
|
-
else if (error instanceof AXLValidationError) console.log("Invalid input:", error.message);
|
|
261
|
-
}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
### Retry Configuration
|
|
265
|
-
|
|
266
|
-
```javascript
|
|
267
|
-
let service = new axlService("10.10.20.1", "admin", "pass", "14.0", {
|
|
268
|
-
retry: {
|
|
269
|
-
retries: 3,
|
|
270
|
-
retryDelay: 1000,
|
|
271
|
-
retryOn: (error) => error.message.includes("ECONNRESET")
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
### ESM Support
|
|
277
|
-
|
|
278
|
-
```javascript
|
|
279
|
-
// CommonJS
|
|
280
|
-
const axlService = require("cisco-axl");
|
|
281
|
-
|
|
282
|
-
// ESM
|
|
283
|
-
import axlService from "cisco-axl";
|
|
284
|
-
import { AXLAuthError, AXLOperationError } from "cisco-axl";
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
### json-variables Support
|
|
288
|
-
|
|
289
|
-
```javascript
|
|
290
|
-
var lineTemplate = {
|
|
291
|
-
pattern: "%%_extension_%%",
|
|
292
|
-
alertingName: "%%_firstName_%% %%_lastName_%%",
|
|
293
|
-
description: "%%_firstName_%% %%_lastName_%%",
|
|
294
|
-
_data: {
|
|
295
|
-
extension: "1001",
|
|
296
|
-
firstName: "Tom",
|
|
297
|
-
lastName: "Smith",
|
|
298
|
-
},
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
const lineTags = jVar(lineTemplate);
|
|
302
|
-
await service.executeOperation("updateLine", lineTags);
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
## Methods Reference
|
|
306
|
-
|
|
307
|
-
### Core
|
|
308
|
-
|
|
309
|
-
| Method | Description |
|
|
310
|
-
|--------|-------------|
|
|
311
|
-
| `new axlService(host, user, pass, version, opts?)` | Constructor |
|
|
312
|
-
| `testAuthentication()` | Test credentials against AXL endpoint |
|
|
313
|
-
| `returnOperations(filter?)` | List available operations |
|
|
314
|
-
| `getOperationTags(operation)` | Get tag schema for an operation |
|
|
315
|
-
| `getOperationTagsDetailed(operation)` | Get detailed tag metadata (required/nillable/type) |
|
|
316
|
-
| `executeOperation(operation, tags, opts?)` | Execute any AXL operation |
|
|
317
|
-
| `executeBatch(operations[], concurrency?)` | Parallel batch execution |
|
|
318
|
-
| `setLogLevel(level)` | Change log level at runtime |
|
|
319
|
-
|
|
320
|
-
### Convenience
|
|
321
|
-
|
|
322
|
-
| Method | Description |
|
|
323
|
-
|--------|-------------|
|
|
324
|
-
| `getItem(type, identifier, opts?)` | Get single item by name or UUID |
|
|
325
|
-
| `listItems(type, search?, returnedTags?, opts?)` | List items with filtering |
|
|
326
|
-
| `addItem(type, data, opts?)` | Add a new item |
|
|
327
|
-
| `updateItem(type, identifier, updates, opts?)` | Update an existing item |
|
|
328
|
-
| `removeItem(type, identifier, opts?)` | Remove an item |
|
|
329
|
-
| `executeSqlQuery(sql)` | Run a SQL SELECT query |
|
|
330
|
-
| `executeSqlUpdate(sql)` | Run a SQL INSERT/UPDATE/DELETE |
|
|
331
|
-
|
|
332
|
-
## Examples
|
|
333
|
-
|
|
334
|
-
Check the **examples** folder for different ways to use this library.
|
|
335
|
-
|
|
336
|
-
Run the integration tests against a CUCM cluster:
|
|
337
|
-
|
|
338
|
-
```bash
|
|
339
|
-
npm run staging
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
## TypeScript Support
|
|
343
|
-
|
|
344
|
-
```typescript
|
|
345
|
-
import axlService from 'cisco-axl';
|
|
346
|
-
|
|
347
|
-
const service = new axlService("10.10.20.1", "administrator", "ciscopsdt", "14.0");
|
|
348
|
-
|
|
349
|
-
const tags = await service.getOperationTags("listRoutePartition");
|
|
350
|
-
tags.searchCriteria.name = "%%";
|
|
351
|
-
const result = await service.executeOperation("listRoutePartition", tags);
|
|
352
|
-
```
|
|
130
|
+
## Giving Back
|
|
353
131
|
|
|
354
|
-
|
|
132
|
+
If you found this helpful, consider:
|
|
355
133
|
|
|
356
|
-
|
|
134
|
+
[](https://buymeacoffee.com/automatebldrs)
|
|
357
135
|
|
|
358
|
-
|
|
136
|
+
## License
|
|
359
137
|
|
|
360
|
-
|
|
138
|
+
MIT
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cisco-axl",
|
|
3
|
+
"description": "Hooks for Cisco CUCM AXL CLI",
|
|
4
|
+
"author": "sieteunoseis",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"name": "Block write operations",
|
|
9
|
+
"description": "Blocks add, update, remove, execute, and sql update commands",
|
|
10
|
+
"default": true,
|
|
11
|
+
"event": "PreToolUse",
|
|
12
|
+
"matcher": "Bash",
|
|
13
|
+
"hook": {
|
|
14
|
+
"type": "command",
|
|
15
|
+
"command": "jq -r '.tool_input.command' | { read -r cmd; if echo \"$cmd\" | grep -qE '^(npx )?cisco-axl (add|update|remove|execute|sql update)'; then echo '{\"decision\":\"block\",\"reason\":\"BLOCKED: cisco-axl write operation. Use --read-only or get explicit user approval.\"}'; fi; }"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "Enforce --read-only flag",
|
|
20
|
+
"description": "Requires --read-only on every cisco-axl command",
|
|
21
|
+
"default": false,
|
|
22
|
+
"event": "PreToolUse",
|
|
23
|
+
"matcher": "Bash",
|
|
24
|
+
"hook": {
|
|
25
|
+
"type": "command",
|
|
26
|
+
"command": "jq -r '.tool_input.command' | { read -r cmd; if echo \"$cmd\" | grep -qE '^(npx )?cisco-axl ' && ! echo \"$cmd\" | grep -q '\\-\\-read-only'; then echo '{\"decision\":\"block\",\"reason\":\"BLOCKED: cisco-axl must be run with --read-only. Retry with the flag.\"}'; fi; }"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
package/cli/commands/add.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
const { createService } = require("../utils/connection.js");
|
|
11
11
|
const { printResult, printError } = require("../utils/output.js");
|
|
12
12
|
const { enforceReadOnly } = require("../utils/readonly.js");
|
|
13
|
+
const { readStdin } = require("../utils/stdin.js");
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Registers the add command on the given Commander program.
|
|
@@ -20,6 +21,7 @@ module.exports = function registerAddCommand(program) {
|
|
|
20
21
|
.command("add <type>")
|
|
21
22
|
.description("Add a new AXL item of the given type")
|
|
22
23
|
.option("--data <json>", "JSON definition of the item to add")
|
|
24
|
+
.option("--stdin", "read JSON data from stdin (for piping)")
|
|
23
25
|
.option("--template <file>", "JSON template file with %%var%% placeholders")
|
|
24
26
|
.option("--vars <json>", "variables to resolve in template (JSON)")
|
|
25
27
|
.option("--csv <file>", "CSV file for bulk operations (use with --template)")
|
|
@@ -32,14 +34,22 @@ module.exports = function registerAddCommand(program) {
|
|
|
32
34
|
let errorMsg;
|
|
33
35
|
|
|
34
36
|
try {
|
|
35
|
-
enforceReadOnly(globalOpts, "add");
|
|
37
|
+
await enforceReadOnly(globalOpts, "add");
|
|
38
|
+
|
|
39
|
+
// Read from stdin if --stdin flag is set
|
|
40
|
+
if (cmdOpts.stdin) {
|
|
41
|
+
const stdinData = await readStdin();
|
|
42
|
+
if (!stdinData) throw new Error("--stdin specified but no data piped. Pipe JSON via: echo '{...}' | cisco-axl add <type> --stdin");
|
|
43
|
+
cmdOpts.data = stdinData.trim();
|
|
44
|
+
}
|
|
36
45
|
|
|
37
46
|
// Validate mutual exclusivity
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
const inputCount = [cmdOpts.data, cmdOpts.template].filter(Boolean).length;
|
|
48
|
+
if (inputCount > 1) {
|
|
49
|
+
throw new Error("--data, --stdin, and --template are mutually exclusive");
|
|
40
50
|
}
|
|
41
|
-
if (
|
|
42
|
-
throw new Error("
|
|
51
|
+
if (inputCount === 0) {
|
|
52
|
+
throw new Error("Provide input via --data, --stdin, or --template");
|
|
43
53
|
}
|
|
44
54
|
|
|
45
55
|
const opts = {
|
package/cli/commands/config.js
CHANGED
|
@@ -14,38 +14,32 @@ const { createService } = require("../utils/connection.js");
|
|
|
14
14
|
* @param {import("commander").Command} program
|
|
15
15
|
*/
|
|
16
16
|
module.exports = function registerConfigCommand(program) {
|
|
17
|
-
const config = program
|
|
17
|
+
const config = program
|
|
18
|
+
.command("config")
|
|
19
|
+
.description("Manage CUCM cluster configuration");
|
|
18
20
|
|
|
19
21
|
// ── config add <name> ───────────────────────────────────────────────────────
|
|
20
22
|
|
|
21
23
|
config
|
|
22
24
|
.command("add <name>")
|
|
23
|
-
.description("Add a named CUCM cluster to config")
|
|
24
|
-
.
|
|
25
|
-
.
|
|
25
|
+
.description("Add a named CUCM cluster to config (use --host, --username, --password, --version-cucm)")
|
|
26
|
+
.option("--cucm-version <ver>", "CUCM version (e.g. 14.0)")
|
|
27
|
+
.option("--insecure", "skip TLS verification for this cluster")
|
|
28
|
+
.action((name, opts, cmd) => {
|
|
26
29
|
try {
|
|
27
|
-
|
|
28
|
-
const globalOpts = program.opts();
|
|
29
|
-
|
|
30
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
30
31
|
const host = globalOpts.host;
|
|
31
32
|
const username = globalOpts.username;
|
|
32
33
|
const password = globalOpts.password;
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
if (!
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
throw new Error("Missing required option: --password");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Commander camelCases --cucm-version to cucmVersion; addCluster expects version
|
|
46
|
-
const clusterOpts = { host, username, password, version: opts.cucmVersion };
|
|
47
|
-
if (insecure !== undefined) {
|
|
48
|
-
clusterOpts.insecure = insecure;
|
|
34
|
+
const version = opts.cucmVersion || globalOpts.versionCucm;
|
|
35
|
+
if (!host) throw new Error("Missing required option: --host");
|
|
36
|
+
if (!username) throw new Error("Missing required option: --username");
|
|
37
|
+
if (!password) throw new Error("Missing required option: --password");
|
|
38
|
+
if (!version) throw new Error("Missing required option: --cucm-version or --version-cucm");
|
|
39
|
+
|
|
40
|
+
const clusterOpts = { host, username, password, version };
|
|
41
|
+
if (opts.insecure || globalOpts.insecure) {
|
|
42
|
+
clusterOpts.insecure = true;
|
|
49
43
|
}
|
|
50
44
|
|
|
51
45
|
configUtil.addCluster(name, clusterOpts);
|