@kill-switch/cli 0.1.1 → 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.
@@ -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
- .requiredOption("--api-key <key>", "Personal API key (starts with ks_)")
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
- const key = opts.apiKey;
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.", json);
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
@@ -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.1.1",
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": {
@@ -24,7 +24,7 @@
24
24
  "node": ">=18"
25
25
  },
26
26
  "files": ["dist", "README.md"],
27
- "keywords": ["cloud", "kill-switch", "cost-monitoring", "cloudflare", "aws", "gcp", "finops", "billing", "cli", "devops"],
27
+ "keywords": ["cloud", "kill-switch", "cost-monitoring", "cloudflare", "aws", "gcp", "runpod", "gpu", "finops", "billing", "cli", "devops"],
28
28
  "repository": {
29
29
  "type": "git",
30
30
  "url": "https://github.com/AiExpanse/kill-switch",