@kill-switch/cli 0.1.1 → 0.3.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.
- package/README.md +49 -4
- package/dist/commands/accounts.d.ts +2 -1
- package/dist/commands/accounts.js +44 -27
- package/dist/commands/activity.d.ts +3 -0
- package/dist/commands/activity.js +80 -0
- package/dist/commands/agent-guard.d.ts +10 -0
- package/dist/commands/agent-guard.js +175 -0
- package/dist/commands/alerts.d.ts +2 -1
- package/dist/commands/alerts.js +112 -12
- package/dist/commands/analytics.d.ts +2 -1
- package/dist/commands/analytics.js +36 -14
- package/dist/commands/auth.d.ts +2 -1
- package/dist/commands/auth.js +127 -14
- package/dist/commands/check.d.ts +2 -1
- package/dist/commands/check.js +28 -15
- package/dist/commands/kill.d.ts +2 -1
- package/dist/commands/kill.js +52 -37
- package/dist/commands/onboard.d.ts +2 -17
- package/dist/commands/onboard.js +273 -61
- package/dist/commands/orgs.d.ts +3 -0
- package/dist/commands/orgs.js +192 -0
- package/dist/commands/providers.d.ts +3 -0
- package/dist/commands/providers.js +82 -0
- package/dist/commands/rules.d.ts +2 -1
- package/dist/commands/rules.js +51 -28
- package/dist/commands/shield.d.ts +2 -1
- package/dist/commands/shield.js +36 -17
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.js +100 -0
- package/dist/commands/watch.d.ts +3 -0
- package/dist/commands/watch.js +68 -0
- package/dist/device-flow.d.ts +33 -0
- package/dist/device-flow.js +91 -0
- package/dist/index.js +38 -11
- package/dist/output.d.ts +26 -4
- package/dist/output.js +101 -12
- package/dist/prompts.d.ts +13 -0
- package/dist/prompts.js +24 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +4 -0
- package/package.json +29 -7
package/dist/commands/alerts.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export function registerAlertCommands(program) {
|
|
1
|
+
import { outputJson, formatTable, handleError, spinner, success } from "../output.js";
|
|
2
|
+
export function registerAlertCommands(program, createClient) {
|
|
4
3
|
const alerts = program.command("alerts").description("Manage alert channels");
|
|
5
4
|
alerts
|
|
6
5
|
.command("list")
|
|
@@ -9,22 +8,120 @@ export function registerAlertCommands(program) {
|
|
|
9
8
|
.action(async () => {
|
|
10
9
|
const json = program.opts().json;
|
|
11
10
|
try {
|
|
12
|
-
const
|
|
13
|
-
const channels =
|
|
11
|
+
const client = createClient();
|
|
12
|
+
const channels = await client.alerts.channels();
|
|
14
13
|
if (json) {
|
|
15
14
|
outputJson(channels);
|
|
16
15
|
}
|
|
17
16
|
else {
|
|
18
|
-
formatTable(
|
|
17
|
+
formatTable(channels, [
|
|
19
18
|
{ key: "type", header: "Type" },
|
|
20
19
|
{ key: "name", header: "Name" },
|
|
21
20
|
{ key: "enabled", header: "Enabled" },
|
|
21
|
+
{ key: "configPreview", header: "Config" },
|
|
22
22
|
]);
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
catch (err) {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
handleError(err, json);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
alerts
|
|
30
|
+
.command("add")
|
|
31
|
+
.description("Add an alert channel")
|
|
32
|
+
.requiredOption("--type <type>", "Channel type: pagerduty | slack | discord | webhook | email | github")
|
|
33
|
+
.option("--name <name>", "Display name for this channel")
|
|
34
|
+
.option("--routing-key <key>", "PagerDuty Events API v2 routing key")
|
|
35
|
+
.option("--webhook-url <url>", "Webhook URL (Slack, Discord, or custom webhook)")
|
|
36
|
+
.option("--email <address>", "Email address")
|
|
37
|
+
// GitHub options
|
|
38
|
+
.option("--token <token>", "GitHub Personal Access Token (repo + workflow scopes)")
|
|
39
|
+
.option("--repo-owner <owner>", "GitHub repository owner / org")
|
|
40
|
+
.option("--repo-name <name>", "GitHub repository name")
|
|
41
|
+
.option("--workflow <file>", "Workflow filename (default: kill-switch-remediate.yml)")
|
|
42
|
+
.option("--branch <ref>", "Branch to dispatch on (default: main)")
|
|
43
|
+
.action(async (opts) => {
|
|
44
|
+
const json = program.opts().json;
|
|
45
|
+
const s = json ? null : spinner("Adding alert channel...").start();
|
|
46
|
+
try {
|
|
47
|
+
const type = opts.type;
|
|
48
|
+
const validTypes = ["pagerduty", "slack", "discord", "webhook", "email", "github"];
|
|
49
|
+
if (!validTypes.includes(type)) {
|
|
50
|
+
throw new Error(`Unknown type "${type}". Valid types: ${validTypes.join(", ")}`);
|
|
51
|
+
}
|
|
52
|
+
// Build config and validate required fields per type
|
|
53
|
+
const config = {};
|
|
54
|
+
if (type === "pagerduty") {
|
|
55
|
+
if (!opts.routingKey)
|
|
56
|
+
throw new Error("--routing-key is required for pagerduty");
|
|
57
|
+
config.routingKey = opts.routingKey;
|
|
58
|
+
}
|
|
59
|
+
else if (type === "slack" || type === "discord" || type === "webhook") {
|
|
60
|
+
if (!opts.webhookUrl)
|
|
61
|
+
throw new Error(`--webhook-url is required for ${type}`);
|
|
62
|
+
config.webhookUrl = opts.webhookUrl;
|
|
63
|
+
}
|
|
64
|
+
else if (type === "email") {
|
|
65
|
+
if (!opts.email)
|
|
66
|
+
throw new Error("--email is required for email");
|
|
67
|
+
config.email = opts.email;
|
|
68
|
+
}
|
|
69
|
+
else if (type === "github") {
|
|
70
|
+
if (!opts.token)
|
|
71
|
+
throw new Error("--token is required for github");
|
|
72
|
+
if (!opts.repoOwner)
|
|
73
|
+
throw new Error("--repo-owner is required for github");
|
|
74
|
+
if (!opts.repoName)
|
|
75
|
+
throw new Error("--repo-name is required for github");
|
|
76
|
+
config.githubToken = opts.token;
|
|
77
|
+
config.repoOwner = opts.repoOwner;
|
|
78
|
+
config.repoName = opts.repoName;
|
|
79
|
+
config.workflowFile = opts.workflow || "kill-switch-remediate.yml";
|
|
80
|
+
config.branchRef = opts.branch || "main";
|
|
81
|
+
}
|
|
82
|
+
const channel = {
|
|
83
|
+
type,
|
|
84
|
+
name: opts.name || type.charAt(0).toUpperCase() + type.slice(1),
|
|
85
|
+
enabled: true,
|
|
86
|
+
config,
|
|
87
|
+
};
|
|
88
|
+
const client = createClient();
|
|
89
|
+
const result = await client.alerts.addChannel(channel);
|
|
90
|
+
s?.stop();
|
|
91
|
+
if (json) {
|
|
92
|
+
outputJson(result);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
success(`Channel added. You now have ${result.channelCount} alert channel(s).`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
s?.stop();
|
|
100
|
+
handleError(err, json);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
alerts
|
|
104
|
+
.command("remove")
|
|
105
|
+
.alias("rm")
|
|
106
|
+
.description("Remove an alert channel by name")
|
|
107
|
+
.argument("<name>", "Channel name to remove")
|
|
108
|
+
.action(async (name) => {
|
|
109
|
+
const json = program.opts().json;
|
|
110
|
+
const s = json ? null : spinner(`Removing channel "${name}"...`).start();
|
|
111
|
+
try {
|
|
112
|
+
const client = createClient();
|
|
113
|
+
const result = await client.alerts.removeChannel(name);
|
|
114
|
+
s?.stop();
|
|
115
|
+
if (json) {
|
|
116
|
+
outputJson(result);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
success(`Channel removed. ${result.channelCount} channel(s) remaining.`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
s?.stop();
|
|
124
|
+
handleError(err, json);
|
|
28
125
|
}
|
|
29
126
|
});
|
|
30
127
|
alerts
|
|
@@ -32,18 +129,21 @@ export function registerAlertCommands(program) {
|
|
|
32
129
|
.description("Send a test alert to all channels")
|
|
33
130
|
.action(async () => {
|
|
34
131
|
const json = program.opts().json;
|
|
132
|
+
const s = json ? null : spinner("Sending test alert...").start();
|
|
35
133
|
try {
|
|
36
|
-
const
|
|
134
|
+
const client = createClient();
|
|
135
|
+
const data = await client.alerts.test();
|
|
136
|
+
s?.stop();
|
|
37
137
|
if (json) {
|
|
38
138
|
outputJson(data);
|
|
39
139
|
}
|
|
40
140
|
else {
|
|
41
|
-
|
|
141
|
+
success(`Test alert sent to ${data.channelsSent} channel(s).`);
|
|
42
142
|
}
|
|
43
143
|
}
|
|
44
144
|
catch (err) {
|
|
45
|
-
|
|
46
|
-
|
|
145
|
+
s?.stop();
|
|
146
|
+
handleError(err, json);
|
|
47
147
|
}
|
|
48
148
|
});
|
|
49
149
|
}
|
|
@@ -1,35 +1,57 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export function registerAnalyticsCommands(program) {
|
|
1
|
+
import { outputJson, formatTable, handleError, spinner, colors as c } from "../output.js";
|
|
2
|
+
export function registerAnalyticsCommands(program, createClient) {
|
|
4
3
|
program
|
|
5
4
|
.command("analytics")
|
|
6
5
|
.description("FinOps analytics overview")
|
|
7
6
|
.option("--days <n>", "Days to analyze", "30")
|
|
8
7
|
.action(async (opts) => {
|
|
9
8
|
const json = program.opts().json;
|
|
9
|
+
const s = json ? null : spinner("Loading analytics...").start();
|
|
10
10
|
try {
|
|
11
|
-
const
|
|
11
|
+
const client = createClient();
|
|
12
|
+
const data = await client.analytics.overview();
|
|
13
|
+
s?.stop();
|
|
12
14
|
if (json) {
|
|
13
15
|
outputJson(data);
|
|
14
16
|
}
|
|
15
17
|
else {
|
|
16
|
-
console.log(
|
|
17
|
-
|
|
18
|
+
console.log(c.bold(`\nAnalytics Overview\n`));
|
|
19
|
+
// Summary stats
|
|
20
|
+
const totalSpend = data.totalSpendPeriod ?? 0;
|
|
21
|
+
const avgDaily = data.avgDailyCost ?? 0;
|
|
22
|
+
const projected = data.projectedMonthlyCost ?? 0;
|
|
23
|
+
const savings = data.savingsEstimate ?? 0;
|
|
24
|
+
const actions = data.killSwitchActions ?? 0;
|
|
25
|
+
console.log(` ${c.bold("Total spend:")} $${totalSpend.toFixed(2)}`);
|
|
26
|
+
console.log(` ${c.bold("Avg daily cost:")} $${avgDaily.toFixed(2)}`);
|
|
27
|
+
console.log(` ${c.bold("Projected monthly:")} $${projected.toFixed(2)}`);
|
|
28
|
+
console.log(` ${c.bold("Savings estimate:")} ${c.green("$" + savings.toFixed(2))}`);
|
|
29
|
+
console.log(` ${c.bold("Kill switch actions:")} ${actions > 0 ? c.yellow(String(actions)) : c.dim("0")}`);
|
|
30
|
+
// Last 7 days cost table
|
|
31
|
+
if (data.dailyCosts?.length) {
|
|
32
|
+
console.log(c.bold("\nLast 7 Days:\n"));
|
|
18
33
|
formatTable(data.dailyCosts.slice(-7), [
|
|
19
|
-
{ key: "date", header: "Date" },
|
|
20
|
-
{ key: "
|
|
21
|
-
{ key: "
|
|
22
|
-
{ key: "
|
|
34
|
+
{ key: "date", header: "Date", width: 12 },
|
|
35
|
+
{ key: "cost", header: "Cost (USD)", width: 12 },
|
|
36
|
+
{ key: "services", header: "Services", width: 10 },
|
|
37
|
+
{ key: "violations", header: "Violations", width: 12 },
|
|
23
38
|
]);
|
|
24
39
|
}
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
// Per-account breakdown
|
|
41
|
+
if (data.accountBreakdown?.length) {
|
|
42
|
+
console.log(c.bold("\nAccount Breakdown:\n"));
|
|
43
|
+
formatTable(data.accountBreakdown, [
|
|
44
|
+
{ key: "provider", header: "Provider", width: 14 },
|
|
45
|
+
{ key: "totalCost", header: "Total Cost", width: 12 },
|
|
46
|
+
{ key: "avgDailyCost", header: "Avg Daily", width: 12 },
|
|
47
|
+
]);
|
|
27
48
|
}
|
|
49
|
+
console.log();
|
|
28
50
|
}
|
|
29
51
|
}
|
|
30
52
|
catch (err) {
|
|
31
|
-
|
|
32
|
-
|
|
53
|
+
s?.stop();
|
|
54
|
+
handleError(err, json);
|
|
33
55
|
}
|
|
34
56
|
});
|
|
35
57
|
}
|
package/dist/commands/auth.d.ts
CHANGED
package/dist/commands/auth.js
CHANGED
|
@@ -1,34 +1,146 @@
|
|
|
1
|
+
import { KillSwitchClient } from "@kill-switch/sdk";
|
|
1
2
|
import { saveConfig, deleteConfig, resolveApiKey, resolveApiUrl } from "../config.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { outputJson, formatObject, outputError, handleError, spinner, success } from "../output.js";
|
|
4
|
+
import { ask } from "../prompts.js";
|
|
5
|
+
import { execFile } from "child_process";
|
|
6
|
+
import { CLI_VERSION } from "../version.js";
|
|
7
|
+
import { runDeviceFlow, defaultDeviceFlowDeps } from "../device-flow.js";
|
|
8
|
+
function openBrowser(url) {
|
|
9
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
10
|
+
execFile(cmd, [url], () => { });
|
|
11
|
+
}
|
|
12
|
+
// Wires the device-flow helper to this command's spinner + console output.
|
|
13
|
+
// Kept here (not in device-flow.ts) so the helper stays free of CLI-specific
|
|
14
|
+
// presentation concerns and remains pure for unit testing.
|
|
15
|
+
function deviceFlowWithSpinner() {
|
|
16
|
+
let sp = null;
|
|
17
|
+
return defaultDeviceFlowDeps({
|
|
18
|
+
log: (line) => console.log(line),
|
|
19
|
+
spinnerStart: (label) => { sp = spinner(label).start(); },
|
|
20
|
+
spinnerStop: () => { sp?.stop(); sp = null; },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export function registerAuthCommands(program, createClient) {
|
|
5
24
|
const auth = program.command("auth").description("Manage authentication");
|
|
25
|
+
auth
|
|
26
|
+
.command("setup")
|
|
27
|
+
.description("Authenticate via your browser (opens a one-time approval page)")
|
|
28
|
+
.option("--manual", "Manual flow: open Settings page, paste the key yourself")
|
|
29
|
+
.action(async (opts) => {
|
|
30
|
+
const json = program.opts().json;
|
|
31
|
+
const existing = resolveApiKey();
|
|
32
|
+
if (existing) {
|
|
33
|
+
try {
|
|
34
|
+
const client = createClient();
|
|
35
|
+
const result = await client.account.me();
|
|
36
|
+
if (!json) {
|
|
37
|
+
console.log(`Already authenticated as ${result.name || result._id}.`);
|
|
38
|
+
const proceed = await ask("Re-authenticate anyway? (y/N): ");
|
|
39
|
+
if (proceed.toLowerCase() !== "y")
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Key invalid, proceed
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const apiUrl = resolveApiUrl();
|
|
48
|
+
// Manual flow \u2014 original copy/paste path for offline / scripted setups
|
|
49
|
+
if (opts.manual) {
|
|
50
|
+
if (!json) {
|
|
51
|
+
console.log("\n\u26a1 Kill Switch CLI Setup (manual)\n");
|
|
52
|
+
console.log("1. Sign in at app.kill-switch.net");
|
|
53
|
+
console.log("2. Go to Settings > API Keys");
|
|
54
|
+
console.log("3. Click 'Create API Key'");
|
|
55
|
+
console.log("4. Copy the key and paste it below\n");
|
|
56
|
+
}
|
|
57
|
+
openBrowser("https://app.kill-switch.net/settings");
|
|
58
|
+
const key = await ask("Paste your API key (ks_live_...): ");
|
|
59
|
+
if (!key.startsWith("ks_")) {
|
|
60
|
+
outputError("API key must start with 'ks_'.", json);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const client = new KillSwitchClient({ apiKey: key, baseUrl: apiUrl });
|
|
65
|
+
const result = await client.account.me();
|
|
66
|
+
saveConfig({ apiKey: key, apiUrl });
|
|
67
|
+
if (json)
|
|
68
|
+
outputJson({ authenticated: true, account: result.name || result._id });
|
|
69
|
+
else {
|
|
70
|
+
success(`Authenticated as ${result.name || result._id}`);
|
|
71
|
+
console.log("API key saved to ~/.kill-switch/config.json");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
handleError(err, json);
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Device flow \u2014 default path
|
|
80
|
+
try {
|
|
81
|
+
const apiKey = await runDeviceFlow(apiUrl, CLI_VERSION, json, deviceFlowWithSpinner());
|
|
82
|
+
const client = new KillSwitchClient({ apiKey, baseUrl: apiUrl });
|
|
83
|
+
const result = await client.account.me();
|
|
84
|
+
saveConfig({ apiKey, apiUrl });
|
|
85
|
+
if (json) {
|
|
86
|
+
outputJson({ authenticated: true, account: result.name || result._id });
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
success(`Authenticated as ${result.name || result._id}`);
|
|
90
|
+
console.log("API key saved to ~/.kill-switch/config.json\n");
|
|
91
|
+
console.log("Next: ks onboard --help-provider mongodb");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
handleError(err, json);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
6
98
|
auth
|
|
7
99
|
.command("login")
|
|
8
|
-
.description("Authenticate
|
|
9
|
-
.
|
|
100
|
+
.description("Authenticate (browser device flow by default, or pass --api-key for direct)")
|
|
101
|
+
.option("--api-key <key>", "Personal API key (starts with ks_) — skips browser flow")
|
|
10
102
|
.action(async (opts) => {
|
|
11
103
|
const json = program.opts().json;
|
|
12
|
-
const
|
|
104
|
+
const apiUrl = resolveApiUrl();
|
|
105
|
+
let key = opts.apiKey;
|
|
106
|
+
// Device flow when no --api-key. JSON mode requires --api-key (no browser).
|
|
107
|
+
if (!key) {
|
|
108
|
+
if (json) {
|
|
109
|
+
outputError("--api-key is required in JSON mode", json);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
key = await runDeviceFlow(apiUrl, CLI_VERSION, json, deviceFlowWithSpinner());
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
handleError(err, json);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
13
120
|
if (!key.startsWith("ks_")) {
|
|
14
|
-
outputError("API key must start with 'ks_'. Create one at app.kill-switch.net
|
|
121
|
+
outputError("API key must start with 'ks_'. Create one at app.kill-switch.net or run: ks auth setup", json);
|
|
15
122
|
process.exit(1);
|
|
16
123
|
}
|
|
17
|
-
|
|
124
|
+
const s = json ? null : spinner("Validating API key...").start();
|
|
18
125
|
try {
|
|
19
|
-
const
|
|
20
|
-
|
|
126
|
+
const client = new KillSwitchClient({
|
|
127
|
+
apiKey: key,
|
|
128
|
+
baseUrl: apiUrl,
|
|
129
|
+
});
|
|
130
|
+
const result = await client.account.me();
|
|
131
|
+
s?.stop();
|
|
132
|
+
saveConfig({ apiKey: key, apiUrl });
|
|
21
133
|
if (json) {
|
|
22
134
|
outputJson({ authenticated: true, account: result.name || result._id });
|
|
23
135
|
}
|
|
24
136
|
else {
|
|
25
|
-
|
|
137
|
+
success(`Authenticated as ${result.name || result._id}`);
|
|
26
138
|
console.log("API key saved to ~/.kill-switch/config.json");
|
|
27
139
|
}
|
|
28
140
|
}
|
|
29
141
|
catch (err) {
|
|
30
|
-
|
|
31
|
-
|
|
142
|
+
s?.stop();
|
|
143
|
+
handleError(err, json);
|
|
32
144
|
}
|
|
33
145
|
});
|
|
34
146
|
auth
|
|
@@ -60,7 +172,8 @@ export function registerAuthCommands(program) {
|
|
|
60
172
|
return;
|
|
61
173
|
}
|
|
62
174
|
try {
|
|
63
|
-
const
|
|
175
|
+
const client = createClient();
|
|
176
|
+
const result = await client.account.me();
|
|
64
177
|
if (json) {
|
|
65
178
|
outputJson({ authenticated: true, ...result });
|
|
66
179
|
}
|
package/dist/commands/check.d.ts
CHANGED
package/dist/commands/check.js
CHANGED
|
@@ -1,37 +1,50 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export function registerCheckCommands(program) {
|
|
1
|
+
import { outputJson, formatTable, handleError, spinner, colors as c } from "../output.js";
|
|
2
|
+
export function registerCheckCommands(program, createClient) {
|
|
4
3
|
program
|
|
5
4
|
.command("check")
|
|
6
5
|
.description("Run monitoring check on all connected accounts")
|
|
7
6
|
.action(async () => {
|
|
8
7
|
const json = program.opts().json;
|
|
8
|
+
const s = json ? null : spinner("Running monitoring check...").start();
|
|
9
9
|
try {
|
|
10
|
-
const
|
|
10
|
+
const client = createClient();
|
|
11
|
+
const data = await client.monitoring.checkAll();
|
|
12
|
+
s?.stop();
|
|
11
13
|
if (json) {
|
|
12
14
|
outputJson(data);
|
|
13
15
|
}
|
|
14
16
|
else {
|
|
15
17
|
const results = data.results || [];
|
|
16
|
-
|
|
18
|
+
const totalViolations = results.reduce((n, r) => n + (r.violations?.length || 0), 0);
|
|
19
|
+
console.log(`Checked ${c.bold(String(results.length))} account(s) — ${totalViolations === 0 ? c.green("all clear") : c.red(totalViolations + " violation(s)")}\n`);
|
|
17
20
|
for (const r of results) {
|
|
18
|
-
|
|
21
|
+
const statusMark = r.status === "violation" ? c.red("✗") : r.status === "error" ? c.yellow("⚠") : c.green("✓");
|
|
22
|
+
console.log(`${statusMark} ${c.bold((r.provider || "unknown").padEnd(12))} ${r.name || r.cloudAccountId}`);
|
|
19
23
|
if (r.violations?.length) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
{
|
|
24
|
+
// Compute multiplier for each violation inline
|
|
25
|
+
const rows = r.violations.map((v) => ({
|
|
26
|
+
...v,
|
|
27
|
+
multiplier: v.threshold > 0 ? `${Math.round(v.currentValue / v.threshold)}x` : "—",
|
|
28
|
+
}));
|
|
29
|
+
formatTable(rows, [
|
|
30
|
+
{ key: "serviceName", header: "Service", width: 28 },
|
|
31
|
+
{ key: "metricName", header: "Metric", width: 22 },
|
|
32
|
+
{ key: "currentValue", header: "Current", width: 14 },
|
|
33
|
+
{ key: "threshold", header: "Threshold", width: 12 },
|
|
34
|
+
{ key: "multiplier", header: "Over", width: 8 },
|
|
35
|
+
{ key: "severity", header: "Severity", width: 10 },
|
|
24
36
|
]);
|
|
37
|
+
if (r.actionsTaken?.length) {
|
|
38
|
+
console.log(c.dim(` Actions: ${r.actionsTaken.join(", ")}`));
|
|
39
|
+
}
|
|
25
40
|
}
|
|
26
|
-
|
|
27
|
-
console.log(" All clear\n");
|
|
28
|
-
}
|
|
41
|
+
console.log();
|
|
29
42
|
}
|
|
30
43
|
}
|
|
31
44
|
}
|
|
32
45
|
catch (err) {
|
|
33
|
-
|
|
34
|
-
|
|
46
|
+
s?.stop();
|
|
47
|
+
handleError(err, json);
|
|
35
48
|
}
|
|
36
49
|
});
|
|
37
50
|
}
|
package/dist/commands/kill.d.ts
CHANGED