@kill-switch/cli 0.1.0 → 0.2.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 +178 -0
- package/dist/commands/auth.js +77 -4
- package/dist/commands/onboard.js +34 -5
- package/package.json +9 -2
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# @kill-switch/cli
|
|
2
|
+
|
|
3
|
+
Stop runaway cloud bills from the terminal. Monitor Cloudflare, GCP, and AWS spending with automatic kill switches that shut down services before they drain your account.
|
|
4
|
+
|
|
5
|
+
Born from a [$91K Cloudflare bill](https://kill-switch.net).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install -g @kill-switch/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This gives you two commands: `kill-switch` and `ks` (short alias).
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
# 1. Get an API key from https://app.kill-switch.net (Settings > API Keys)
|
|
19
|
+
ks auth login --api-key ks_live_your_key_here
|
|
20
|
+
|
|
21
|
+
# 2. Connect your cloud provider and apply protection
|
|
22
|
+
ks onboard --provider cloudflare \
|
|
23
|
+
--account-id YOUR_ACCOUNT_ID \
|
|
24
|
+
--token YOUR_API_TOKEN \
|
|
25
|
+
--name "Production" \
|
|
26
|
+
--shields cost-runaway,ddos
|
|
27
|
+
|
|
28
|
+
# 3. Check your accounts
|
|
29
|
+
ks check
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## One-Command Setup
|
|
33
|
+
|
|
34
|
+
The `onboard` command connects a provider, applies shield presets, and configures alerts in one step:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
# Cloudflare
|
|
38
|
+
ks onboard --provider cloudflare \
|
|
39
|
+
--account-id YOUR_CF_ACCOUNT_ID \
|
|
40
|
+
--token YOUR_CF_API_TOKEN \
|
|
41
|
+
--name "Production" \
|
|
42
|
+
--shields cost-runaway,ddos \
|
|
43
|
+
--alert-email you@example.com
|
|
44
|
+
|
|
45
|
+
# AWS
|
|
46
|
+
ks onboard --provider aws \
|
|
47
|
+
--access-key AKIA... \
|
|
48
|
+
--secret-key wJalr... \
|
|
49
|
+
--region us-east-1 \
|
|
50
|
+
--shields aws-cost-runaway,gpu-runaway
|
|
51
|
+
|
|
52
|
+
# GCP
|
|
53
|
+
ks onboard --provider gcp \
|
|
54
|
+
--project-id my-project-123 \
|
|
55
|
+
--service-account "$(cat key.json)" \
|
|
56
|
+
--shields cost-runaway
|
|
57
|
+
|
|
58
|
+
# Interactive mode (prompts for everything)
|
|
59
|
+
ks onboard
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Don't know where to find your credentials? Run:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
ks onboard --help-provider cloudflare
|
|
66
|
+
ks onboard --help-provider aws
|
|
67
|
+
ks onboard --help-provider gcp
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Shields (Quick Protect)
|
|
71
|
+
|
|
72
|
+
Apply preset protection rules with one command:
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
ks shield cost-runaway # Kill services exceeding daily cost limit
|
|
76
|
+
ks shield ddos # Kill services getting excessive requests
|
|
77
|
+
ks shield gpu-runaway # Stop unexpected GPU instances
|
|
78
|
+
ks shield lambda-loop # Throttle recursive Lambda invocations
|
|
79
|
+
ks shield aws-cost-runaway # Emergency stop on AWS daily spend spike
|
|
80
|
+
ks shield brute-force # Rotate creds on mass auth failures
|
|
81
|
+
ks shield exfiltration # Isolate on unusual egress
|
|
82
|
+
ks shield error-storm # Scale down on sustained high error rate
|
|
83
|
+
|
|
84
|
+
# List all shields
|
|
85
|
+
ks shield --list
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Commands
|
|
89
|
+
|
|
90
|
+
| Command | Description |
|
|
91
|
+
|---------|-------------|
|
|
92
|
+
| `ks onboard` | One-command setup: connect + shields + alerts |
|
|
93
|
+
| `ks auth login` | Authenticate with API key |
|
|
94
|
+
| `ks auth status` | Show auth status |
|
|
95
|
+
| `ks accounts list` | List connected cloud accounts |
|
|
96
|
+
| `ks accounts add` | Connect a cloud provider |
|
|
97
|
+
| `ks accounts check <id>` | Run manual check on an account |
|
|
98
|
+
| `ks check` | Check all accounts |
|
|
99
|
+
| `ks shield <preset>` | Apply a protection preset |
|
|
100
|
+
| `ks rules list` | List active rules |
|
|
101
|
+
| `ks alerts list` | List alert channels |
|
|
102
|
+
| `ks analytics` | Cost analytics overview |
|
|
103
|
+
| `ks config list` | Show configuration |
|
|
104
|
+
|
|
105
|
+
## AI Agent Usage
|
|
106
|
+
|
|
107
|
+
The CLI is designed for AI coding agents (Claude Code, Cursor, Windsurf) to set up cloud monitoring on behalf of users without interactive prompts.
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
# Set API key via env var
|
|
111
|
+
export KILL_SWITCH_API_KEY=ks_live_your_key
|
|
112
|
+
|
|
113
|
+
# Non-interactive setup with JSON output
|
|
114
|
+
ks onboard \
|
|
115
|
+
--provider cloudflare \
|
|
116
|
+
--account-id CF_ACCOUNT_ID \
|
|
117
|
+
--token CF_API_TOKEN \
|
|
118
|
+
--name "Production" \
|
|
119
|
+
--shields cost-runaway,ddos \
|
|
120
|
+
--json
|
|
121
|
+
|
|
122
|
+
# All commands support --json for machine-readable output
|
|
123
|
+
ks accounts list --json
|
|
124
|
+
ks check --json
|
|
125
|
+
ks analytics --json
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### CLAUDE.md Integration
|
|
129
|
+
|
|
130
|
+
Add this to your project's `CLAUDE.md` so your AI agent knows how to manage Kill Switch:
|
|
131
|
+
|
|
132
|
+
```markdown
|
|
133
|
+
## Kill Switch (Cloud Cost Protection)
|
|
134
|
+
- CLI: `ks` (alias for `kill-switch`)
|
|
135
|
+
- Auth: KILL_SWITCH_API_KEY env var or `ks auth login --api-key KEY`
|
|
136
|
+
- Setup: `ks onboard --provider cloudflare --account-id ID --token TOKEN`
|
|
137
|
+
- Check: `ks check --json`
|
|
138
|
+
- Docs: `ks onboard --help-provider cloudflare`
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Authentication
|
|
142
|
+
|
|
143
|
+
The CLI uses personal API keys (prefixed with `ks_live_`). Create one from [app.kill-switch.net](https://app.kill-switch.net) under Settings > API Keys.
|
|
144
|
+
|
|
145
|
+
**Auth resolution order:**
|
|
146
|
+
1. `KILL_SWITCH_API_KEY` environment variable (best for CI/CD and AI agents)
|
|
147
|
+
2. `--api-key` flag on any command
|
|
148
|
+
3. `~/.kill-switch/config.json` (set by `ks auth login`)
|
|
149
|
+
|
|
150
|
+
## Global Options
|
|
151
|
+
|
|
152
|
+
| Flag | Description |
|
|
153
|
+
|------|-------------|
|
|
154
|
+
| `--json` | Output raw JSON (for automation/scripting/AI agents) |
|
|
155
|
+
| `--api-key <key>` | Override API key for this command |
|
|
156
|
+
| `--api-url <url>` | Override API URL |
|
|
157
|
+
| `-V, --version` | Show version |
|
|
158
|
+
| `-h, --help` | Show help |
|
|
159
|
+
|
|
160
|
+
## Exit Codes
|
|
161
|
+
|
|
162
|
+
| Code | Meaning |
|
|
163
|
+
|------|---------|
|
|
164
|
+
| `0` | Success |
|
|
165
|
+
| `1` | Client error (bad arguments, API error) |
|
|
166
|
+
| `2` | Authentication error (invalid/missing API key) |
|
|
167
|
+
|
|
168
|
+
## Links
|
|
169
|
+
|
|
170
|
+
- [Website](https://kill-switch.net)
|
|
171
|
+
- [Dashboard](https://app.kill-switch.net)
|
|
172
|
+
- [API Docs](https://kill-switch.net/docs)
|
|
173
|
+
- [CLI Docs](https://kill-switch.net/docs/cli.html)
|
|
174
|
+
- [GitHub](https://github.com/AiExpanse/kill-switch)
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,17 +1,90 @@
|
|
|
1
1
|
import { saveConfig, deleteConfig, resolveApiKey, resolveApiUrl } from "../config.js";
|
|
2
2
|
import { apiRequest } from "../api-client.js";
|
|
3
3
|
import { outputJson, formatObject, outputError } from "../output.js";
|
|
4
|
+
import { createInterface } from "readline";
|
|
5
|
+
import { execFile } from "child_process";
|
|
6
|
+
function ask(question) {
|
|
7
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
rl.question(question, (answer) => {
|
|
10
|
+
rl.close();
|
|
11
|
+
resolve(answer.trim());
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function openBrowser(url) {
|
|
16
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
17
|
+
execFile(cmd, [url], () => { });
|
|
18
|
+
}
|
|
4
19
|
export function registerAuthCommands(program) {
|
|
5
20
|
const auth = program.command("auth").description("Manage authentication");
|
|
21
|
+
auth
|
|
22
|
+
.command("setup")
|
|
23
|
+
.description("Create an API key (opens browser to sign in, then paste the key)")
|
|
24
|
+
.action(async () => {
|
|
25
|
+
const json = program.opts().json;
|
|
26
|
+
const existing = resolveApiKey();
|
|
27
|
+
if (existing) {
|
|
28
|
+
try {
|
|
29
|
+
const result = await apiRequest("/accounts/me");
|
|
30
|
+
if (!json) {
|
|
31
|
+
console.log(`Already authenticated as ${result.name || result._id}.`);
|
|
32
|
+
const proceed = await ask("Create a new API key anyway? (y/N): ");
|
|
33
|
+
if (proceed.toLowerCase() !== "y")
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Key invalid, proceed
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (!json) {
|
|
42
|
+
console.log("\n\u26a1 Kill Switch CLI Setup\n");
|
|
43
|
+
console.log("Opening app.kill-switch.net in your browser...");
|
|
44
|
+
console.log("1. Sign in (or create an account)");
|
|
45
|
+
console.log("2. Go to Settings > API Keys");
|
|
46
|
+
console.log("3. Click 'Create API Key'");
|
|
47
|
+
console.log("4. Copy the key and paste it below\n");
|
|
48
|
+
}
|
|
49
|
+
openBrowser("https://app.kill-switch.net/settings");
|
|
50
|
+
const key = await ask("Paste your API key (ks_live_...): ");
|
|
51
|
+
if (!key.startsWith("ks_")) {
|
|
52
|
+
outputError("API key must start with 'ks_'. Try again.", json);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const result = await apiRequest("/accounts/me", { apiKey: key });
|
|
57
|
+
saveConfig({ apiKey: key, apiUrl: resolveApiUrl() });
|
|
58
|
+
if (json) {
|
|
59
|
+
outputJson({ authenticated: true, account: result.name || result._id });
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
console.log(`\n\u2713 Authenticated as ${result.name || result._id}`);
|
|
63
|
+
console.log("API key saved to ~/.kill-switch/config.json\n");
|
|
64
|
+
console.log("Next: ks onboard --provider cloudflare --help-provider cloudflare");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
outputError(`Authentication failed: ${err.message}`, json);
|
|
69
|
+
process.exit(2);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
6
72
|
auth
|
|
7
73
|
.command("login")
|
|
8
|
-
.description("Authenticate with an API key")
|
|
9
|
-
.
|
|
74
|
+
.description("Authenticate with an existing API key")
|
|
75
|
+
.option("--api-key <key>", "Personal API key (starts with ks_)")
|
|
10
76
|
.action(async (opts) => {
|
|
11
77
|
const json = program.opts().json;
|
|
12
|
-
|
|
78
|
+
let key = opts.apiKey;
|
|
79
|
+
if (!key) {
|
|
80
|
+
if (json) {
|
|
81
|
+
outputError("--api-key is required in JSON mode", json);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
key = await ask("API key (ks_live_...): ");
|
|
85
|
+
}
|
|
13
86
|
if (!key.startsWith("ks_")) {
|
|
14
|
-
outputError("API key must start with 'ks_'. Create one at app.kill-switch.net
|
|
87
|
+
outputError("API key must start with 'ks_'. Create one at app.kill-switch.net or run: ks auth setup", json);
|
|
15
88
|
process.exit(1);
|
|
16
89
|
}
|
|
17
90
|
// Validate the key by calling the API
|
package/dist/commands/onboard.js
CHANGED
|
@@ -77,6 +77,21 @@ const PROVIDER_HELP = {
|
|
|
77
77
|
Run: aws configure get region
|
|
78
78
|
Common values: us-east-1, us-west-2, eu-west-1`,
|
|
79
79
|
},
|
|
80
|
+
runpod: {
|
|
81
|
+
name: "RunPod",
|
|
82
|
+
fields: "--runpod-api-key",
|
|
83
|
+
howToGet: `How to get this value:
|
|
84
|
+
|
|
85
|
+
API Key:
|
|
86
|
+
1. Go to https://www.runpod.io/console/user/settings
|
|
87
|
+
2. Scroll to "API Keys" section
|
|
88
|
+
3. Click "Create API Key" (or copy an existing one)
|
|
89
|
+
4. The key starts with a long alphanumeric string
|
|
90
|
+
|
|
91
|
+
Required permissions:
|
|
92
|
+
- Read access to pods, serverless endpoints, and network volumes
|
|
93
|
+
- Write access if you want auto-kill actions (stop/terminate pods, scale endpoints)`,
|
|
94
|
+
},
|
|
80
95
|
};
|
|
81
96
|
const AVAILABLE_SHIELDS = [
|
|
82
97
|
"cost-runaway", "ddos", "brute-force", "error-storm",
|
|
@@ -105,6 +120,7 @@ export function registerOnboardCommands(program) {
|
|
|
105
120
|
.option("--access-key <key>", "Access Key ID (AWS)")
|
|
106
121
|
.option("--secret-key <key>", "Secret Access Key (AWS)")
|
|
107
122
|
.option("--region <region>", "Region (AWS, default: us-east-1)")
|
|
123
|
+
.option("--runpod-api-key <key>", "API Key (RunPod)")
|
|
108
124
|
.option("--shields <presets>", "Comma-separated shield presets to apply (default: cost-runaway)")
|
|
109
125
|
.option("--alert-email <email>", "Email address for alerts")
|
|
110
126
|
.option("--alert-discord <url>", "Discord webhook URL for alerts")
|
|
@@ -146,7 +162,7 @@ Available shields: ${AVAILABLE_SHIELDS.join(", ")}
|
|
|
146
162
|
if (opts.helpProvider) {
|
|
147
163
|
const help = PROVIDER_HELP[opts.helpProvider];
|
|
148
164
|
if (!help) {
|
|
149
|
-
outputError(`Unknown provider: ${opts.helpProvider}. Use: cloudflare, gcp, aws`, json);
|
|
165
|
+
outputError(`Unknown provider: ${opts.helpProvider}. Use: cloudflare, gcp, aws, runpod`, json);
|
|
150
166
|
process.exit(1);
|
|
151
167
|
}
|
|
152
168
|
if (json) {
|
|
@@ -165,7 +181,7 @@ Available shields: ${AVAILABLE_SHIELDS.join(", ")}
|
|
|
165
181
|
// Interactive mode if no provider specified
|
|
166
182
|
if (!provider) {
|
|
167
183
|
if (json) {
|
|
168
|
-
outputError("--provider is required in JSON mode. Use: cloudflare, gcp, aws", json);
|
|
184
|
+
outputError("--provider is required in JSON mode. Use: cloudflare, gcp, aws, runpod", json);
|
|
169
185
|
process.exit(1);
|
|
170
186
|
}
|
|
171
187
|
console.log("\n\u26a1 Kill Switch Onboarding\n");
|
|
@@ -174,12 +190,13 @@ Available shields: ${AVAILABLE_SHIELDS.join(", ")}
|
|
|
174
190
|
console.log(" 1. cloudflare — Workers, R2, D1, Queues, Stream");
|
|
175
191
|
console.log(" 2. gcp — Cloud Run, Compute, GKE, BigQuery");
|
|
176
192
|
console.log(" 3. aws — EC2, Lambda, RDS, ECS, S3");
|
|
193
|
+
console.log(" 4. runpod — GPU Pods, Serverless Endpoints, Network Volumes");
|
|
177
194
|
console.log();
|
|
178
|
-
const choice = await ask("Choose a provider (1/2/3 or name): ");
|
|
179
|
-
provider = { "1": "cloudflare", "2": "gcp", "3": "aws" }[choice] || choice;
|
|
195
|
+
const choice = await ask("Choose a provider (1/2/3/4 or name): ");
|
|
196
|
+
provider = { "1": "cloudflare", "2": "gcp", "3": "aws", "4": "runpod" }[choice] || choice;
|
|
180
197
|
}
|
|
181
198
|
if (!PROVIDER_HELP[provider]) {
|
|
182
|
-
outputError(`Unknown provider: ${provider}. Use: cloudflare, gcp, aws`, json);
|
|
199
|
+
outputError(`Unknown provider: ${provider}. Use: cloudflare, gcp, aws, runpod`, json);
|
|
183
200
|
process.exit(1);
|
|
184
201
|
}
|
|
185
202
|
if (!name && !json) {
|
|
@@ -248,6 +265,18 @@ Available shields: ${AVAILABLE_SHIELDS.join(", ")}
|
|
|
248
265
|
credential.awsSecretAccessKey = secretKey;
|
|
249
266
|
credential.awsRegion = region || "us-east-1";
|
|
250
267
|
}
|
|
268
|
+
else if (provider === "runpod") {
|
|
269
|
+
let apiKey = opts.runpodApiKey;
|
|
270
|
+
if (!apiKey && !json) {
|
|
271
|
+
console.log("\n Tip: Create an API Key at https://www.runpod.io/console/user/settings");
|
|
272
|
+
apiKey = await ask(" RunPod API Key: ");
|
|
273
|
+
}
|
|
274
|
+
if (!apiKey) {
|
|
275
|
+
outputError(`RunPod requires ${PROVIDER_HELP.runpod.fields}`, json);
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
credential.runpodApiKey = apiKey;
|
|
279
|
+
}
|
|
251
280
|
// 1. Connect cloud account
|
|
252
281
|
if (!json)
|
|
253
282
|
console.log(`\nConnecting ${PROVIDER_HELP[provider].name}...`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kill-switch/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Kill Switch CLI — monitor cloud spending, kill runaway services from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,13 @@
|
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": ">=18"
|
|
25
25
|
},
|
|
26
|
-
"files": ["dist"],
|
|
26
|
+
"files": ["dist", "README.md"],
|
|
27
|
+
"keywords": ["cloud", "kill-switch", "cost-monitoring", "cloudflare", "aws", "gcp", "runpod", "gpu", "finops", "billing", "cli", "devops"],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/AiExpanse/kill-switch",
|
|
31
|
+
"directory": "packages/cli"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://kill-switch.net",
|
|
27
34
|
"license": "MIT"
|
|
28
35
|
}
|