cisco-perfmon 1.6.0 → 2.0.0

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.
@@ -0,0 +1,115 @@
1
+ const config = require("../utils/config.js");
2
+ const { resolveConfig } = require("../utils/connection.js");
3
+
4
+ module.exports = function (program) {
5
+ program.command("doctor")
6
+ .description("Check PerfMon connectivity and configuration health")
7
+ .action(async (opts, command) => {
8
+ const globalOpts = command.optsWithGlobals();
9
+ let passed = 0;
10
+ let warned = 0;
11
+ let failed = 0;
12
+
13
+ const ok = (msg) => { console.log(` \u2713 ${msg}`); passed++; };
14
+ const warn = (msg) => { console.log(` \u26A0 ${msg}`); warned++; };
15
+ const fail = (msg) => { console.log(` \u2717 ${msg}`); failed++; };
16
+
17
+ console.log("\n cisco-perfmon doctor");
18
+ console.log(" " + "\u2500".repeat(50));
19
+
20
+ // 1. Configuration
21
+ console.log("\n Configuration");
22
+ let conn;
23
+ try {
24
+ const data = config.loadConfig();
25
+ if (!data.activeCluster) {
26
+ fail("No active cluster configured");
27
+ console.log(" Run: cisco-perfmon config add <name> --host <host> --username <user> --password <pass>");
28
+ printSummary(passed, warned, failed);
29
+ return;
30
+ }
31
+ ok(`Active cluster: ${data.activeCluster}`);
32
+ const cluster = data.clusters[data.activeCluster];
33
+ ok(`Host: ${cluster.host}`);
34
+ ok(`Username: ${cluster.username}`);
35
+
36
+ if (cluster.insecure) warn("TLS verification: disabled (--insecure)");
37
+ else ok("TLS verification: enabled");
38
+
39
+ conn = resolveConfig(globalOpts);
40
+ } catch (err) {
41
+ fail(`Config error: ${err.message}`);
42
+ printSummary(passed, warned, failed);
43
+ return;
44
+ }
45
+
46
+ // 2. PerfMon API connectivity
47
+ console.log("\n PerfMon API");
48
+ try {
49
+ if (conn.insecure) { process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; }
50
+ const PerfMon = require("../../main.js");
51
+ const svc = new PerfMon(conn.host, conn.username, conn.password);
52
+
53
+ const result = await svc.listCounter(conn.host);
54
+ ok("PerfMon API: connected");
55
+
56
+ const count = Array.isArray(result.results) ? result.results.length : 0;
57
+ ok(`Counter objects available: ${count}`);
58
+ } catch (err) {
59
+ const msg = err.message || String(err);
60
+ if (msg.includes("401") || msg.includes("Authentication")) {
61
+ fail("PerfMon API: authentication failed \u2014 check username/password");
62
+ } else if (msg.includes("ECONNREFUSED")) {
63
+ fail("PerfMon API: connection refused \u2014 check host and port");
64
+ } else if (msg.includes("ENOTFOUND")) {
65
+ fail("PerfMon API: hostname not found \u2014 check host");
66
+ } else if (msg.includes("certificate")) {
67
+ fail("PerfMon API: TLS certificate error \u2014 try adding --insecure to the cluster config");
68
+ } else {
69
+ fail(`PerfMon API: ${msg}`);
70
+ }
71
+ }
72
+
73
+ // 3. Security
74
+ console.log("\n Security");
75
+ try {
76
+ const fs = require("node:fs");
77
+ const configPath = config.getConfigPath();
78
+ const stats = fs.statSync(configPath);
79
+ const mode = (stats.mode & 0o777).toString(8);
80
+ if (mode === "600") ok(`Config file permissions: ${mode} (secure)`);
81
+ else warn(`Config file permissions: ${mode} \u2014 should be 600. Run: chmod 600 ${configPath}`);
82
+ } catch { /* config file may not exist yet */ }
83
+
84
+ // 4. Audit trail
85
+ try {
86
+ const fs = require("node:fs");
87
+ const path = require("node:path");
88
+ const auditPath = path.join(config.getConfigDir(), "audit.jsonl");
89
+ if (fs.existsSync(auditPath)) {
90
+ const stats = fs.statSync(auditPath);
91
+ const sizeMB = (stats.size / 1024 / 1024).toFixed(1);
92
+ ok(`Audit trail: ${sizeMB}MB`);
93
+ if (stats.size > 8 * 1024 * 1024) warn("Audit trail approaching 10MB rotation limit");
94
+ } else {
95
+ ok("Audit trail: empty (no operations logged yet)");
96
+ }
97
+ } catch { /* ignore */ }
98
+
99
+ printSummary(passed, warned, failed);
100
+ });
101
+
102
+ function printSummary(passed, warned, failed) {
103
+ console.log("\n " + "\u2500".repeat(50));
104
+ console.log(` Results: ${passed} passed, ${warned} warning${warned !== 1 ? "s" : ""}, ${failed} failed`);
105
+ if (failed > 0) {
106
+ process.exitCode = 1;
107
+ console.log(" Status: issues found \u2014 review failures above");
108
+ } else if (warned > 0) {
109
+ console.log(" Status: healthy with warnings");
110
+ } else {
111
+ console.log(" Status: all systems healthy");
112
+ }
113
+ console.log("");
114
+ }
115
+ };
@@ -0,0 +1,39 @@
1
+ const { resolveConfig } = require("../utils/connection.js");
2
+ const { printResult, printError } = require("../utils/output.js");
3
+ const audit = require("../utils/audit.js");
4
+
5
+ module.exports = function (program) {
6
+ program
7
+ .command("list-instances <object>")
8
+ .description("List instances of a perfmon object")
9
+ .action(async (object, opts, cmd) => {
10
+ const globalOpts = cmd.optsWithGlobals();
11
+ const start = Date.now();
12
+ try {
13
+ const connConfig = resolveConfig(globalOpts);
14
+ if (connConfig.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
15
+ if (globalOpts.debug) process.env.DEBUG = "cisco-perfmon";
16
+
17
+ const PerfMon = require("../../main.js");
18
+ const svc = new PerfMon(connConfig.host, connConfig.username, connConfig.password);
19
+ const result = await svc.listInstance(connConfig.host, object);
20
+
21
+ let instances = Array.isArray(result.results) ? result.results : [];
22
+ const rows = instances.map((item) => {
23
+ if (typeof item === "string") return { instance: item };
24
+ return { instance: item.Name || item.name || JSON.stringify(item) };
25
+ });
26
+
27
+ if (globalOpts.audit !== false) {
28
+ audit.log({ cluster: connConfig.host, command: "list-instances", args: object, duration_ms: Date.now() - start, status: "success", rows: rows.length });
29
+ }
30
+
31
+ await printResult(rows, globalOpts.format);
32
+ } catch (err) {
33
+ if (globalOpts.audit !== false) {
34
+ try { audit.log({ cluster: "", command: "list-instances", args: object, duration_ms: Date.now() - start, status: "error", message: err.message }); } catch {}
35
+ }
36
+ printError(err);
37
+ }
38
+ });
39
+ };
@@ -0,0 +1,45 @@
1
+ const { resolveConfig } = require("../utils/connection.js");
2
+ const { printResult, printError } = require("../utils/output.js");
3
+ const audit = require("../utils/audit.js");
4
+
5
+ module.exports = function (program) {
6
+ program
7
+ .command("list-objects")
8
+ .description("List available perfmon objects")
9
+ .option("--search <keyword>", "filter objects by keyword (case-insensitive)")
10
+ .action(async (opts, cmd) => {
11
+ const globalOpts = cmd.optsWithGlobals();
12
+ const start = Date.now();
13
+ try {
14
+ const connConfig = resolveConfig(globalOpts);
15
+ if (connConfig.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
16
+ if (globalOpts.debug) process.env.DEBUG = "cisco-perfmon";
17
+
18
+ const PerfMon = require("../../main.js");
19
+ const svc = new PerfMon(connConfig.host, connConfig.username, connConfig.password);
20
+ const result = await svc.listCounter(connConfig.host);
21
+
22
+ let objects = Array.isArray(result.results) ? result.results : [];
23
+
24
+ // Extract unique object names from counter list
25
+ const objectNames = [...new Set(objects.map((item) => item.Name || item.name).filter(Boolean))];
26
+ let rows = objectNames.map((name) => ({ name }));
27
+
28
+ if (opts.search) {
29
+ const keyword = opts.search.toLowerCase();
30
+ rows = rows.filter((r) => r.name.toLowerCase().includes(keyword));
31
+ }
32
+
33
+ if (globalOpts.audit !== false) {
34
+ audit.log({ cluster: connConfig.host, command: "list-objects", args: opts.search || "", duration_ms: Date.now() - start, status: "success", rows: rows.length });
35
+ }
36
+
37
+ await printResult(rows, globalOpts.format);
38
+ } catch (err) {
39
+ if (globalOpts.audit !== false) {
40
+ try { audit.log({ cluster: "", command: "list-objects", duration_ms: Date.now() - start, status: "error", message: err.message }); } catch {}
41
+ }
42
+ printError(err);
43
+ }
44
+ });
45
+ };
@@ -0,0 +1,162 @@
1
+ const { resolveConfig } = require("../utils/connection.js");
2
+ const { printResult, printError } = require("../utils/output.js");
3
+ const audit = require("../utils/audit.js");
4
+
5
+ module.exports = function (program) {
6
+ const session = program.command("session").description("Manage perfmon sessions");
7
+
8
+ session
9
+ .command("open")
10
+ .description("Open a new perfmon session")
11
+ .action(async (opts, cmd) => {
12
+ const globalOpts = cmd.optsWithGlobals();
13
+ const start = Date.now();
14
+ try {
15
+ const connConfig = resolveConfig(globalOpts);
16
+ if (connConfig.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
17
+ if (globalOpts.debug) process.env.DEBUG = "cisco-perfmon";
18
+
19
+ const PerfMon = require("../../main.js");
20
+ const svc = new PerfMon(connConfig.host, connConfig.username, connConfig.password);
21
+ const result = await svc.openSession();
22
+
23
+ if (globalOpts.audit !== false) {
24
+ audit.log({ cluster: connConfig.host, command: "session open", duration_ms: Date.now() - start, status: "success" });
25
+ }
26
+
27
+ const handle = result.results;
28
+ if (globalOpts.format === "json") {
29
+ await printResult({ sessionHandle: handle }, globalOpts.format);
30
+ } else {
31
+ console.log(`Session handle: ${handle}`);
32
+ }
33
+ } catch (err) {
34
+ if (globalOpts.audit !== false) {
35
+ try { audit.log({ cluster: "", command: "session open", duration_ms: Date.now() - start, status: "error", message: err.message }); } catch {}
36
+ }
37
+ printError(err);
38
+ }
39
+ });
40
+
41
+ session
42
+ .command("add <handle>")
43
+ .description("Add counters to a session")
44
+ .option("--counters <json>", "JSON array of counter objects [{host,object,counter,instance?}]")
45
+ .action(async (handle, opts, cmd) => {
46
+ const globalOpts = cmd.optsWithGlobals();
47
+ const start = Date.now();
48
+ try {
49
+ const connConfig = resolveConfig(globalOpts);
50
+ if (connConfig.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
51
+ if (globalOpts.debug) process.env.DEBUG = "cisco-perfmon";
52
+
53
+ if (!opts.counters) throw new Error("Missing required option: --counters (JSON array of counter objects)");
54
+ const counters = JSON.parse(opts.counters);
55
+
56
+ const PerfMon = require("../../main.js");
57
+ const svc = new PerfMon(connConfig.host, connConfig.username, connConfig.password);
58
+ const result = await svc.addCounter(handle, counters);
59
+
60
+ if (globalOpts.audit !== false) {
61
+ audit.log({ cluster: connConfig.host, command: "session add", args: handle, duration_ms: Date.now() - start, status: "success" });
62
+ }
63
+
64
+ console.log(`Counters added to session ${handle}: ${result.results}`);
65
+ } catch (err) {
66
+ if (globalOpts.audit !== false) {
67
+ try { audit.log({ cluster: "", command: "session add", args: handle, duration_ms: Date.now() - start, status: "error", message: err.message }); } catch {}
68
+ }
69
+ printError(err);
70
+ }
71
+ });
72
+
73
+ session
74
+ .command("collect <handle>")
75
+ .description("Collect data from a session")
76
+ .action(async (handle, opts, cmd) => {
77
+ const globalOpts = cmd.optsWithGlobals();
78
+ const start = Date.now();
79
+ try {
80
+ const connConfig = resolveConfig(globalOpts);
81
+ if (connConfig.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
82
+ if (globalOpts.debug) process.env.DEBUG = "cisco-perfmon";
83
+
84
+ const PerfMon = require("../../main.js");
85
+ const svc = new PerfMon(connConfig.host, connConfig.username, connConfig.password);
86
+ const result = await svc.collectSessionData(handle);
87
+
88
+ let rows = Array.isArray(result.results) ? result.results : result.results ? [result.results] : [];
89
+
90
+ if (globalOpts.audit !== false) {
91
+ audit.log({ cluster: connConfig.host, command: "session collect", args: handle, duration_ms: Date.now() - start, status: "success", rows: rows.length });
92
+ }
93
+
94
+ await printResult(rows, globalOpts.format);
95
+ } catch (err) {
96
+ if (globalOpts.audit !== false) {
97
+ try { audit.log({ cluster: "", command: "session collect", args: handle, duration_ms: Date.now() - start, status: "error", message: err.message }); } catch {}
98
+ }
99
+ printError(err);
100
+ }
101
+ });
102
+
103
+ session
104
+ .command("remove <handle>")
105
+ .description("Remove counters from a session")
106
+ .option("--counters <json>", "JSON array of counter objects [{host,object,counter,instance?}]")
107
+ .action(async (handle, opts, cmd) => {
108
+ const globalOpts = cmd.optsWithGlobals();
109
+ const start = Date.now();
110
+ try {
111
+ const connConfig = resolveConfig(globalOpts);
112
+ if (connConfig.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
113
+ if (globalOpts.debug) process.env.DEBUG = "cisco-perfmon";
114
+
115
+ if (!opts.counters) throw new Error("Missing required option: --counters (JSON array of counter objects)");
116
+ const counters = JSON.parse(opts.counters);
117
+
118
+ const PerfMon = require("../../main.js");
119
+ const svc = new PerfMon(connConfig.host, connConfig.username, connConfig.password);
120
+ const result = await svc.removeCounter(handle, counters);
121
+
122
+ if (globalOpts.audit !== false) {
123
+ audit.log({ cluster: connConfig.host, command: "session remove", args: handle, duration_ms: Date.now() - start, status: "success" });
124
+ }
125
+
126
+ console.log(`Counters removed from session ${handle}: ${result.results}`);
127
+ } catch (err) {
128
+ if (globalOpts.audit !== false) {
129
+ try { audit.log({ cluster: "", command: "session remove", args: handle, duration_ms: Date.now() - start, status: "error", message: err.message }); } catch {}
130
+ }
131
+ printError(err);
132
+ }
133
+ });
134
+
135
+ session
136
+ .command("close <handle>")
137
+ .description("Close a perfmon session")
138
+ .action(async (handle, opts, cmd) => {
139
+ const globalOpts = cmd.optsWithGlobals();
140
+ const start = Date.now();
141
+ try {
142
+ const connConfig = resolveConfig(globalOpts);
143
+ if (connConfig.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
144
+ if (globalOpts.debug) process.env.DEBUG = "cisco-perfmon";
145
+
146
+ const PerfMon = require("../../main.js");
147
+ const svc = new PerfMon(connConfig.host, connConfig.username, connConfig.password);
148
+ const result = await svc.closeSession(handle);
149
+
150
+ if (globalOpts.audit !== false) {
151
+ audit.log({ cluster: connConfig.host, command: "session close", args: handle, duration_ms: Date.now() - start, status: "success" });
152
+ }
153
+
154
+ console.log(`Session ${handle} closed: ${result.results}`);
155
+ } catch (err) {
156
+ if (globalOpts.audit !== false) {
157
+ try { audit.log({ cluster: "", command: "session close", args: handle, duration_ms: Date.now() - start, status: "error", message: err.message }); } catch {}
158
+ }
159
+ printError(err);
160
+ }
161
+ });
162
+ };
@@ -0,0 +1,163 @@
1
+ const { resolveConfig } = require("../utils/connection.js");
2
+ const { printResult, printError } = require("../utils/output.js");
3
+ const audit = require("../utils/audit.js");
4
+
5
+ const SPARK_CHARS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
6
+
7
+ function sparkline(values) {
8
+ if (values.length === 0) return "";
9
+ const min = Math.min(...values);
10
+ const max = Math.max(...values);
11
+ const range = max - min || 1;
12
+ return values.map((v) => SPARK_CHARS[Math.min(Math.floor(((v - min) / range) * 7), 7)]).join("");
13
+ }
14
+
15
+ function avg(values) {
16
+ if (values.length === 0) return 0;
17
+ return values.reduce((a, b) => a + b, 0) / values.length;
18
+ }
19
+
20
+ function watchCommand(program) {
21
+ program
22
+ .command("watch <object>")
23
+ .description("Continuously poll perfmon counters with live sparklines")
24
+ .option("--counter <names>", "comma-separated counter names to watch")
25
+ .option("--instance <name>", "filter by instance name")
26
+ .option("--interval <seconds>", "polling interval in seconds", "10")
27
+ .option("--duration <seconds>", "stop after N seconds (default: indefinite)")
28
+ .action(async (object, opts, cmd) => {
29
+ const globalOpts = cmd.optsWithGlobals();
30
+ const interval = parseInt(opts.interval, 10) * 1000;
31
+ const duration = opts.duration ? parseInt(opts.duration, 10) * 1000 : 0;
32
+ const counterFilter = opts.counter ? opts.counter.split(",").map((c) => c.trim()) : null;
33
+ const start = Date.now();
34
+ let iterations = 0;
35
+
36
+ try {
37
+ const connConfig = resolveConfig(globalOpts);
38
+ if (connConfig.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
39
+ if (globalOpts.debug) process.env.DEBUG = "cisco-perfmon";
40
+
41
+ const PerfMon = require("../../main.js");
42
+ const svc = new PerfMon(connConfig.host, connConfig.username, connConfig.password);
43
+
44
+ // Rolling history: key = "object|instance|counter", value = number[]
45
+ const history = {};
46
+ const MAX_SAMPLES = 12;
47
+ let running = true;
48
+
49
+ const cleanup = () => {
50
+ running = false;
51
+ // Print final summary
52
+ console.log("\n");
53
+ console.log("Watch stopped. Final summary:");
54
+ console.log(` Iterations: ${iterations}`);
55
+ console.log(` Duration: ${((Date.now() - start) / 1000).toFixed(1)}s`);
56
+
57
+ if (globalOpts.audit !== false) {
58
+ try { audit.log({ cluster: connConfig.host, command: "watch", args: object, duration_ms: Date.now() - start, status: "success", iterations }); } catch {}
59
+ }
60
+
61
+ process.exit(0);
62
+ };
63
+
64
+ process.on("SIGINT", cleanup);
65
+ process.on("SIGTERM", cleanup);
66
+
67
+ const poll = async () => {
68
+ try {
69
+ const result = await svc.collectCounterData(connConfig.host, object);
70
+ let rows = Array.isArray(result.results) ? result.results : result.results ? [result.results] : [];
71
+
72
+ if (counterFilter) {
73
+ rows = rows.filter((r) => counterFilter.includes(r.counter));
74
+ }
75
+ if (opts.instance) {
76
+ rows = rows.filter((r) => r.instance === opts.instance);
77
+ }
78
+
79
+ // Update history
80
+ for (const row of rows) {
81
+ const key = `${row.object}|${row.instance || ""}|${row.counter}`;
82
+ if (!history[key]) history[key] = [];
83
+ const numVal = parseFloat(row.value);
84
+ if (!isNaN(numVal)) {
85
+ history[key].push(numVal);
86
+ if (history[key].length > MAX_SAMPLES) history[key].shift();
87
+ }
88
+ }
89
+
90
+ iterations++;
91
+
92
+ if (globalOpts.format !== "table") {
93
+ // Non-table formats: print timestamped blocks
94
+ console.log(`\n--- ${new Date().toISOString()} (sample ${iterations}) ---`);
95
+ await printResult(rows, globalOpts.format);
96
+ } else {
97
+ // Table format: clear and redraw with sparklines
98
+ process.stdout.write("\x1b[2J\x1b[0f");
99
+ console.log(`cisco-perfmon watch: ${object} | interval: ${opts.interval}s | sample: ${iterations} | ${new Date().toISOString()}\n`);
100
+
101
+ const Table = require("cli-table3");
102
+ const table = new Table({ head: ["counter", "instance", "value", "sparkline", "min", "max", "avg"] });
103
+
104
+ for (const row of rows) {
105
+ const key = `${row.object}|${row.instance || ""}|${row.counter}`;
106
+ const values = history[key] || [];
107
+ const numVal = parseFloat(row.value);
108
+ const numValues = values.filter((v) => !isNaN(v));
109
+
110
+ table.push([
111
+ row.counter,
112
+ row.instance || "",
113
+ row.value,
114
+ sparkline(numValues),
115
+ numValues.length > 0 ? Math.min(...numValues).toFixed(1) : "-",
116
+ numValues.length > 0 ? Math.max(...numValues).toFixed(1) : "-",
117
+ numValues.length > 0 ? avg(numValues).toFixed(1) : "-",
118
+ ]);
119
+ }
120
+
121
+ console.log(table.toString());
122
+ console.log(`\n${rows.length} counter${rows.length !== 1 ? "s" : ""} | Press Ctrl+C to stop`);
123
+ }
124
+ } catch (err) {
125
+ process.stderr.write(`\nPoll error: ${err.message || err}\n`);
126
+ }
127
+ };
128
+
129
+ // Initial poll
130
+ await poll();
131
+
132
+ // Set up interval
133
+ const pollTimer = setInterval(async () => {
134
+ if (!running) return;
135
+ if (duration > 0 && Date.now() - start >= duration) {
136
+ clearInterval(pollTimer);
137
+ cleanup();
138
+ return;
139
+ }
140
+ await poll();
141
+ }, interval);
142
+
143
+ // If duration is set, schedule exit
144
+ if (duration > 0) {
145
+ setTimeout(() => {
146
+ clearInterval(pollTimer);
147
+ cleanup();
148
+ }, duration);
149
+ }
150
+
151
+ // Keep process alive
152
+ await new Promise(() => {});
153
+ } catch (err) {
154
+ if (globalOpts.audit !== false) {
155
+ try { audit.log({ cluster: "", command: "watch", args: object, duration_ms: Date.now() - start, status: "error", message: err.message }); } catch {}
156
+ }
157
+ printError(err);
158
+ }
159
+ });
160
+ }
161
+
162
+ module.exports = watchCommand;
163
+ module.exports.sparkline = sparkline;
@@ -0,0 +1,10 @@
1
+ const { stringify } = require("csv-stringify/sync");
2
+
3
+ function formatCsv(data) {
4
+ const rows = Array.isArray(data) ? data : [data];
5
+ if (rows.length === 0) return "";
6
+ const columns = Object.keys(rows[0]);
7
+ return stringify(rows, { header: true, columns });
8
+ }
9
+
10
+ module.exports = formatCsv;
@@ -0,0 +1,5 @@
1
+ function formatJson(data) {
2
+ return JSON.stringify(data, null, 2);
3
+ }
4
+
5
+ module.exports = formatJson;
@@ -0,0 +1,25 @@
1
+ const Table = require("cli-table3");
2
+
3
+ function formatTable(data) {
4
+ if (Array.isArray(data)) return formatListTable(data);
5
+ return formatItemTable(data);
6
+ }
7
+
8
+ function formatListTable(rows) {
9
+ if (rows.length === 0) return "No results found";
10
+ const columns = Object.keys(rows[0]);
11
+ const table = new Table({ head: columns });
12
+ for (const row of rows) table.push(columns.map((col) => String(row[col] ?? "")));
13
+ return `${table.toString()}\n${rows.length} result${rows.length !== 1 ? "s" : ""} found`;
14
+ }
15
+
16
+ function formatItemTable(item) {
17
+ const table = new Table();
18
+ for (const [key, value] of Object.entries(item)) {
19
+ const displayValue = typeof value === "object" && value !== null ? JSON.stringify(value, null, 2) : String(value ?? "");
20
+ table.push({ [key]: displayValue });
21
+ }
22
+ return table.toString();
23
+ }
24
+
25
+ module.exports = formatTable;
@@ -0,0 +1,6 @@
1
+ async function formatToon(data) {
2
+ const { encode } = await import("@toon-format/toon");
3
+ return encode(data);
4
+ }
5
+
6
+ module.exports = formatToon;
package/cli/index.js ADDED
@@ -0,0 +1,40 @@
1
+ const { Command } = require("commander");
2
+ const pkg = require("../package.json");
3
+
4
+ // Suppress Node.js TLS warning when --insecure is used
5
+ const originalEmitWarning = process.emitWarning;
6
+ process.emitWarning = (warning, ...args) => {
7
+ if (typeof warning === "string" && warning.includes("NODE_TLS_REJECT_UNAUTHORIZED")) return;
8
+ originalEmitWarning.call(process, warning, ...args);
9
+ };
10
+
11
+ try {
12
+ const updateNotifier = require("update-notifier").default || require("update-notifier");
13
+ updateNotifier({ pkg }).notify();
14
+ } catch {}
15
+
16
+ const program = new Command();
17
+
18
+ program
19
+ .name("cisco-perfmon")
20
+ .description("CLI for Cisco CUCM Performance Monitoring via PerfMon API")
21
+ .version(pkg.version)
22
+ .option("--format <type>", "output format: table, json, toon, csv", "table")
23
+ .option("--host <host>", "CUCM hostname (overrides config/env)")
24
+ .option("--username <user>", "CUCM username (overrides config/env)")
25
+ .option("--password <pass>", "CUCM password (overrides config/env)")
26
+ .option("--cluster <name>", "use a specific named cluster")
27
+ .option("--insecure", "skip TLS certificate verification")
28
+ .option("--no-audit", "disable audit logging for this command")
29
+ .option("--debug", "enable debug logging");
30
+
31
+ require("./commands/config.js")(program);
32
+ require("./commands/list-objects.js")(program);
33
+ require("./commands/list-instances.js")(program);
34
+ require("./commands/describe.js")(program);
35
+ require("./commands/collect.js")(program);
36
+ require("./commands/session.js")(program);
37
+ require("./commands/watch.js")(program);
38
+ require("./commands/doctor.js")(program);
39
+
40
+ program.parse();
@@ -0,0 +1,30 @@
1
+ const fs = require("node:fs");
2
+ const path = require("node:path");
3
+ const { getConfigDir } = require("./config.js");
4
+
5
+ const MAX_FILE_SIZE = 10 * 1024 * 1024;
6
+
7
+ function getAuditPath() {
8
+ return path.join(getConfigDir(), "audit.jsonl");
9
+ }
10
+
11
+ function rotateIfNeeded(auditPath) {
12
+ try {
13
+ const stats = fs.statSync(auditPath);
14
+ if (stats.size >= MAX_FILE_SIZE) {
15
+ const rotated = auditPath + ".1";
16
+ if (fs.existsSync(rotated)) fs.unlinkSync(rotated);
17
+ fs.renameSync(auditPath, rotated);
18
+ }
19
+ } catch { /* file doesn't exist yet */ }
20
+ }
21
+
22
+ function log(entry) {
23
+ const dir = getConfigDir();
24
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
25
+ const auditPath = getAuditPath();
26
+ rotateIfNeeded(auditPath);
27
+ fs.appendFileSync(auditPath, JSON.stringify({ timestamp: new Date().toISOString(), ...entry }) + "\n");
28
+ }
29
+
30
+ module.exports = { log, getAuditPath };