@kill-switch/cli 0.1.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,340 @@
1
+ /**
2
+ * Onboard Command
3
+ *
4
+ * One-command setup for connecting cloud providers, applying protection rules,
5
+ * and configuring alerts. Designed for both human use and AI agent automation.
6
+ *
7
+ * Human use (interactive prompts):
8
+ * kill-switch onboard
9
+ *
10
+ * AI agent use (non-interactive):
11
+ * kill-switch onboard \
12
+ * --provider cloudflare \
13
+ * --account-id YOUR_CF_ACCOUNT_ID \
14
+ * --token YOUR_CF_API_TOKEN \
15
+ * --name "Production" \
16
+ * --shields cost-runaway,ddos \
17
+ * --alert-email you@example.com
18
+ *
19
+ * Multi-provider (run multiple times or comma-separate):
20
+ * kill-switch onboard --provider cloudflare --token ... --account-id ...
21
+ * kill-switch onboard --provider aws --access-key ... --secret-key ... --region us-east-1
22
+ */
23
+ import { apiRequest } from "../api-client.js";
24
+ import { outputJson, outputError } from "../output.js";
25
+ import { createInterface } from "readline";
26
+ const PROVIDER_HELP = {
27
+ cloudflare: {
28
+ name: "Cloudflare",
29
+ fields: "--account-id and --token",
30
+ howToGet: `How to get these values:
31
+
32
+ Account ID:
33
+ Found in your browser URL bar on any Cloudflare dashboard page:
34
+ https://dash.cloudflare.com/<ACCOUNT_ID>/example.com
35
+ Or run: curl -s -H "Authorization: Bearer TOKEN" https://api.cloudflare.com/client/v4/accounts | jq '.result[].id'
36
+
37
+ API Token (NOT Global API Key):
38
+ 1. Go to https://dash.cloudflare.com/profile/api-tokens
39
+ 2. Click "Create Token"
40
+ 3. Use the "Edit Cloudflare Workers" template, or create custom with:
41
+ - Account > Account Analytics > Read
42
+ - Account > Workers Scripts > Edit
43
+ - Account > Workers R2 Storage > Read
44
+ - Account > D1 > Read
45
+ - Zone > Zone > Read
46
+ 4. Copy the token (starts with a long alphanumeric string)
47
+
48
+ NOTE: The Global API Key will NOT work. You must create an API Token.`,
49
+ },
50
+ gcp: {
51
+ name: "Google Cloud",
52
+ fields: "--project-id and --service-account",
53
+ howToGet: `How to get these values:
54
+
55
+ Project ID:
56
+ Run: gcloud config get-value project
57
+ Or find it at: https://console.cloud.google.com/home/dashboard (project selector)
58
+
59
+ Service Account Key (JSON):
60
+ 1. Go to https://console.cloud.google.com/iam-admin/serviceaccounts
61
+ 2. Create a service account with "Viewer" + "Cloud Run Admin" roles
62
+ 3. Create a JSON key: Actions > Manage Keys > Add Key > JSON
63
+ 4. Pass the file contents: --service-account "$(cat key.json)"`,
64
+ },
65
+ aws: {
66
+ name: "Amazon Web Services",
67
+ fields: "--access-key, --secret-key, and --region",
68
+ howToGet: `How to get these values:
69
+
70
+ Access Key ID & Secret Access Key:
71
+ 1. Go to https://console.aws.amazon.com/iam/home#/security_credentials
72
+ 2. Create an access key (or use an existing IAM user with read permissions)
73
+ 3. Copy both the Access Key ID and Secret Access Key
74
+ Run: aws configure get aws_access_key_id
75
+
76
+ Region:
77
+ Run: aws configure get region
78
+ Common values: us-east-1, us-west-2, eu-west-1`,
79
+ },
80
+ };
81
+ const AVAILABLE_SHIELDS = [
82
+ "cost-runaway", "ddos", "brute-force", "error-storm",
83
+ "exfiltration", "gpu-runaway", "lambda-loop", "aws-cost-runaway",
84
+ ];
85
+ function ask(question) {
86
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
87
+ return new Promise((resolve) => {
88
+ rl.question(question, (answer) => {
89
+ rl.close();
90
+ resolve(answer.trim());
91
+ });
92
+ });
93
+ }
94
+ export function registerOnboardCommands(program) {
95
+ program
96
+ .command("onboard")
97
+ .alias("setup")
98
+ .description("Quick setup: connect a cloud provider, apply protection, configure alerts")
99
+ .option("--provider <provider>", "Cloud provider: cloudflare, gcp, aws")
100
+ .option("--name <name>", "Account name (e.g., Production)")
101
+ .option("--token <token>", "API token (Cloudflare)")
102
+ .option("--account-id <id>", "Account ID (Cloudflare)")
103
+ .option("--project-id <id>", "Project ID (GCP)")
104
+ .option("--service-account <json>", "Service Account JSON (GCP)")
105
+ .option("--access-key <key>", "Access Key ID (AWS)")
106
+ .option("--secret-key <key>", "Secret Access Key (AWS)")
107
+ .option("--region <region>", "Region (AWS, default: us-east-1)")
108
+ .option("--shields <presets>", "Comma-separated shield presets to apply (default: cost-runaway)")
109
+ .option("--alert-email <email>", "Email address for alerts")
110
+ .option("--alert-discord <url>", "Discord webhook URL for alerts")
111
+ .option("--alert-slack <url>", "Slack webhook URL for alerts")
112
+ .option("--skip-shields", "Skip applying protection rules")
113
+ .option("--skip-alerts", "Skip setting up alerts")
114
+ .option("--help-provider <provider>", "Show how to get credentials for a provider")
115
+ .addHelpText("after", `
116
+ Examples:
117
+
118
+ # Interactive onboarding
119
+ kill-switch onboard
120
+
121
+ # AI agent / non-interactive: connect Cloudflare
122
+ kill-switch onboard \\
123
+ --provider cloudflare \\
124
+ --account-id 14a6fa23390363382f378b5bd4a0f849 \\
125
+ --token cf-api-token-here \\
126
+ --name "Production" \\
127
+ --shields cost-runaway,ddos \\
128
+ --alert-email you@example.com
129
+
130
+ # Show how to get Cloudflare credentials
131
+ kill-switch onboard --help-provider cloudflare
132
+
133
+ # Connect AWS with shields
134
+ kill-switch onboard \\
135
+ --provider aws \\
136
+ --access-key AKIA... \\
137
+ --secret-key wJalr... \\
138
+ --region us-east-1 \\
139
+ --shields aws-cost-runaway,gpu-runaway
140
+
141
+ Available shields: ${AVAILABLE_SHIELDS.join(", ")}
142
+ `)
143
+ .action(async (opts) => {
144
+ const json = program.opts().json;
145
+ // Help for a specific provider
146
+ if (opts.helpProvider) {
147
+ const help = PROVIDER_HELP[opts.helpProvider];
148
+ if (!help) {
149
+ outputError(`Unknown provider: ${opts.helpProvider}. Use: cloudflare, gcp, aws`, json);
150
+ process.exit(1);
151
+ }
152
+ if (json) {
153
+ outputJson({ provider: opts.helpProvider, ...help });
154
+ }
155
+ else {
156
+ console.log(`\n${help.name} — required flags: ${help.fields}\n`);
157
+ console.log(help.howToGet);
158
+ console.log();
159
+ }
160
+ return;
161
+ }
162
+ try {
163
+ let provider = opts.provider;
164
+ let name = opts.name;
165
+ // Interactive mode if no provider specified
166
+ if (!provider) {
167
+ if (json) {
168
+ outputError("--provider is required in JSON mode. Use: cloudflare, gcp, aws", json);
169
+ process.exit(1);
170
+ }
171
+ console.log("\n\u26a1 Kill Switch Onboarding\n");
172
+ console.log("Let's connect your cloud provider and set up cost protection.\n");
173
+ console.log("Available providers:");
174
+ console.log(" 1. cloudflare — Workers, R2, D1, Queues, Stream");
175
+ console.log(" 2. gcp — Cloud Run, Compute, GKE, BigQuery");
176
+ console.log(" 3. aws — EC2, Lambda, RDS, ECS, S3");
177
+ 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;
180
+ }
181
+ if (!PROVIDER_HELP[provider]) {
182
+ outputError(`Unknown provider: ${provider}. Use: cloudflare, gcp, aws`, json);
183
+ process.exit(1);
184
+ }
185
+ if (!name && !json) {
186
+ name = await ask("Account name (e.g., Production): ");
187
+ }
188
+ name = name || `${PROVIDER_HELP[provider].name} account`;
189
+ // Build credential
190
+ const credential = { provider };
191
+ if (provider === "cloudflare") {
192
+ let accountId = opts.accountId;
193
+ let token = opts.token;
194
+ if (!accountId && !json) {
195
+ console.log("\n Tip: Your Account ID is in the URL: dash.cloudflare.com/<ACCOUNT_ID>/...");
196
+ accountId = await ask(" Cloudflare Account ID: ");
197
+ }
198
+ if (!token && !json) {
199
+ console.log("\n Tip: Create an API Token (not Global Key) at:");
200
+ console.log(" https://dash.cloudflare.com/profile/api-tokens");
201
+ console.log(" Use the 'Edit Cloudflare Workers' template.\n");
202
+ token = await ask(" API Token: ");
203
+ }
204
+ if (!accountId || !token) {
205
+ outputError(`Cloudflare requires ${PROVIDER_HELP.cloudflare.fields}`, json);
206
+ process.exit(1);
207
+ }
208
+ credential.accountId = accountId;
209
+ credential.apiToken = token;
210
+ }
211
+ else if (provider === "gcp") {
212
+ let projectId = opts.projectId;
213
+ let serviceAccount = opts.serviceAccount;
214
+ if (!projectId && !json) {
215
+ console.log("\n Tip: Run `gcloud config get-value project` to find your project ID.");
216
+ projectId = await ask(" GCP Project ID: ");
217
+ }
218
+ if (!serviceAccount && !json) {
219
+ console.log("\n Tip: Create at IAM > Service Accounts > Manage Keys > Add Key > JSON");
220
+ serviceAccount = await ask(" Service Account Key JSON: ");
221
+ }
222
+ if (!projectId || !serviceAccount) {
223
+ outputError(`GCP requires ${PROVIDER_HELP.gcp.fields}`, json);
224
+ process.exit(1);
225
+ }
226
+ credential.projectId = projectId;
227
+ credential.serviceAccountJson = serviceAccount;
228
+ }
229
+ else if (provider === "aws") {
230
+ let accessKey = opts.accessKey;
231
+ let secretKey = opts.secretKey;
232
+ let region = opts.region;
233
+ if (!accessKey && !json) {
234
+ console.log("\n Tip: Find at IAM > Security Credentials, or `aws configure get aws_access_key_id`");
235
+ accessKey = await ask(" AWS Access Key ID: ");
236
+ }
237
+ if (!secretKey && !json) {
238
+ secretKey = await ask(" AWS Secret Access Key: ");
239
+ }
240
+ if (!region && !json) {
241
+ region = await ask(" AWS Region (default: us-east-1): ");
242
+ }
243
+ if (!accessKey || !secretKey) {
244
+ outputError(`AWS requires ${PROVIDER_HELP.aws.fields}`, json);
245
+ process.exit(1);
246
+ }
247
+ credential.awsAccessKeyId = accessKey;
248
+ credential.awsSecretAccessKey = secretKey;
249
+ credential.awsRegion = region || "us-east-1";
250
+ }
251
+ // 1. Connect cloud account
252
+ if (!json)
253
+ console.log(`\nConnecting ${PROVIDER_HELP[provider].name}...`);
254
+ const account = await apiRequest("/cloud-accounts", {
255
+ method: "POST",
256
+ body: { provider, name, credential },
257
+ });
258
+ if (!json)
259
+ console.log(`\u2713 Connected: ${account.name || account._id}`);
260
+ // 2. Apply shields
261
+ if (!opts.skipShields) {
262
+ const shieldList = opts.shields
263
+ ? opts.shields.split(",").map((s) => s.trim())
264
+ : ["cost-runaway"];
265
+ if (!json)
266
+ console.log(`\nApplying ${shieldList.length} shield(s)...`);
267
+ for (const shield of shieldList) {
268
+ try {
269
+ await apiRequest(`/rules/presets/${shield}`, { method: "POST", body: {} });
270
+ if (!json)
271
+ console.log(` \u2713 ${shield}`);
272
+ }
273
+ catch (err) {
274
+ if (!json)
275
+ console.log(` \u2717 ${shield}: ${err.message}`);
276
+ }
277
+ }
278
+ }
279
+ // 3. Set up alerts
280
+ if (!opts.skipAlerts) {
281
+ const channels = [];
282
+ if (opts.alertEmail) {
283
+ channels.push({ type: "email", name: "Email", config: { email: opts.alertEmail }, enabled: true });
284
+ }
285
+ if (opts.alertDiscord) {
286
+ channels.push({ type: "discord", name: "Discord", config: { webhookUrl: opts.alertDiscord }, enabled: true });
287
+ }
288
+ if (opts.alertSlack) {
289
+ channels.push({ type: "slack", name: "Slack", config: { webhookUrl: opts.alertSlack }, enabled: true });
290
+ }
291
+ if (channels.length === 0 && !json) {
292
+ const email = await ask("\nAlert email (or Enter to skip): ");
293
+ if (email) {
294
+ channels.push({ type: "email", name: "Email", config: { email }, enabled: true });
295
+ }
296
+ }
297
+ if (channels.length > 0) {
298
+ if (!json)
299
+ console.log("Setting up alerts...");
300
+ try {
301
+ await apiRequest("/alerts/channels", { method: "PUT", body: { channels } });
302
+ if (!json)
303
+ console.log(` \u2713 ${channels.length} alert channel(s) configured`);
304
+ }
305
+ catch (err) {
306
+ if (!json)
307
+ console.log(` \u2717 Alerts: ${err.message}`);
308
+ }
309
+ }
310
+ }
311
+ // 4. Complete onboarding
312
+ try {
313
+ await apiRequest("/accounts/me", { method: "PATCH", body: { onboardingCompleted: true } });
314
+ }
315
+ catch {
316
+ // Non-critical
317
+ }
318
+ if (json) {
319
+ outputJson({
320
+ success: true,
321
+ provider,
322
+ accountId: account._id || account.id,
323
+ accountName: account.name,
324
+ });
325
+ }
326
+ else {
327
+ console.log(`\n\u2705 Setup complete! Kill Switch is monitoring your ${PROVIDER_HELP[provider].name} account.`);
328
+ console.log("\nNext steps:");
329
+ console.log(" kill-switch accounts list — view connected accounts");
330
+ console.log(" kill-switch check — run a monitoring check");
331
+ console.log(" kill-switch shield --list — see all available shields");
332
+ console.log(" kill-switch onboard --provider — add another provider\n");
333
+ }
334
+ }
335
+ catch (err) {
336
+ outputError(err.message, json);
337
+ process.exit(1);
338
+ }
339
+ });
340
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerRuleCommands(program: Command): void;
@@ -0,0 +1,121 @@
1
+ import { apiRequest } from "../api-client.js";
2
+ import { outputJson, formatTable, outputError } from "../output.js";
3
+ export function registerRuleCommands(program) {
4
+ const rules = program.command("rules").description("Manage kill switch rules");
5
+ rules
6
+ .command("list")
7
+ .alias("ls")
8
+ .description("List active rules")
9
+ .action(async () => {
10
+ const json = program.opts().json;
11
+ try {
12
+ const data = await apiRequest("/rules");
13
+ const list = data.rules || data;
14
+ if (json) {
15
+ outputJson(list);
16
+ }
17
+ else {
18
+ formatTable(Array.isArray(list) ? list : [], [
19
+ { key: "id", header: "ID" },
20
+ { key: "name", header: "Name" },
21
+ { key: "trigger", header: "Trigger" },
22
+ { key: "enabled", header: "Enabled" },
23
+ ]);
24
+ }
25
+ }
26
+ catch (err) {
27
+ outputError(err.message, json);
28
+ process.exit(1);
29
+ }
30
+ });
31
+ rules
32
+ .command("presets")
33
+ .description("List available preset templates")
34
+ .action(async () => {
35
+ const json = program.opts().json;
36
+ try {
37
+ const data = await apiRequest("/rules/presets", { public: true });
38
+ const presets = data.presets || data;
39
+ if (json) {
40
+ outputJson(presets);
41
+ }
42
+ else {
43
+ formatTable(presets, [
44
+ { key: "id", header: "ID", width: 20 },
45
+ { key: "name", header: "Name", width: 30 },
46
+ { key: "description", header: "Description", width: 50 },
47
+ ]);
48
+ }
49
+ }
50
+ catch (err) {
51
+ outputError(err.message, json);
52
+ process.exit(1);
53
+ }
54
+ });
55
+ rules
56
+ .command("create <name>")
57
+ .description("Create a custom rule")
58
+ .requiredOption("--trigger <type>", "Trigger type (cost, security, api)")
59
+ .option("--condition <json>", "Condition JSON")
60
+ .option("--action <json>", "Action JSON")
61
+ .action(async (name, opts) => {
62
+ const json = program.opts().json;
63
+ try {
64
+ const body = { name, trigger: opts.trigger };
65
+ if (opts.condition)
66
+ body.conditions = JSON.parse(opts.condition);
67
+ if (opts.action)
68
+ body.actions = JSON.parse(opts.action);
69
+ const data = await apiRequest("/rules", { method: "POST", body });
70
+ if (json) {
71
+ outputJson(data);
72
+ }
73
+ else {
74
+ console.log(`Rule created: ${data.id || data._id}`);
75
+ }
76
+ }
77
+ catch (err) {
78
+ outputError(err.message, json);
79
+ process.exit(1);
80
+ }
81
+ });
82
+ rules
83
+ .command("delete <id>")
84
+ .alias("rm")
85
+ .description("Delete a rule")
86
+ .action(async (id) => {
87
+ const json = program.opts().json;
88
+ try {
89
+ await apiRequest(`/rules/${id}`, { method: "DELETE" });
90
+ if (json) {
91
+ outputJson({ deleted: true, id });
92
+ }
93
+ else {
94
+ console.log(`Rule ${id} deleted.`);
95
+ }
96
+ }
97
+ catch (err) {
98
+ outputError(err.message, json);
99
+ process.exit(1);
100
+ }
101
+ });
102
+ rules
103
+ .command("toggle <id>")
104
+ .description("Enable/disable a rule")
105
+ .action(async (id) => {
106
+ const json = program.opts().json;
107
+ try {
108
+ const data = await apiRequest(`/rules/${id}/toggle`, { method: "POST" });
109
+ if (json) {
110
+ outputJson(data);
111
+ }
112
+ else {
113
+ console.log(`Rule ${id} is now ${data.enabled ? "enabled" : "disabled"}.`);
114
+ }
115
+ }
116
+ catch (err) {
117
+ outputError(err.message, json);
118
+ process.exit(1);
119
+ }
120
+ });
121
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerShieldCommands(program: Command): void;
@@ -0,0 +1,57 @@
1
+ import { apiRequest } from "../api-client.js";
2
+ import { outputJson, formatTable, outputError } from "../output.js";
3
+ const PRESETS = [
4
+ "ddos", "brute-force", "cost-runaway", "error-storm",
5
+ "exfiltration", "gpu-runaway", "lambda-loop", "aws-cost-runaway",
6
+ ];
7
+ export function registerShieldCommands(program) {
8
+ const shield = program
9
+ .command("shield [preset]")
10
+ .description("Quick-apply a protection preset (e.g., kill-switch shield cost-runaway)")
11
+ .option("--list", "List available shields")
12
+ .action(async (preset, opts) => {
13
+ const json = program.opts().json;
14
+ if (opts.list || !preset) {
15
+ try {
16
+ const data = await apiRequest("/rules/presets", { public: true });
17
+ const presets = data.presets || data;
18
+ if (json) {
19
+ outputJson(presets);
20
+ }
21
+ else {
22
+ console.log("Available shields:\n");
23
+ formatTable(presets, [
24
+ { key: "id", header: "Shield", width: 20 },
25
+ { key: "name", header: "Name", width: 30 },
26
+ { key: "description", header: "Description", width: 50 },
27
+ ]);
28
+ console.log("\nUsage: kill-switch shield <preset-id>");
29
+ }
30
+ }
31
+ catch (err) {
32
+ outputError(err.message, json);
33
+ process.exit(1);
34
+ }
35
+ return;
36
+ }
37
+ if (!PRESETS.includes(preset)) {
38
+ outputError(`Unknown preset "${preset}". Run: kill-switch shield --list`, json);
39
+ process.exit(1);
40
+ }
41
+ try {
42
+ const data = await apiRequest(`/rules/presets/${preset}`, { method: "POST" });
43
+ if (json) {
44
+ outputJson(data);
45
+ }
46
+ else {
47
+ console.log(`Shield activated: ${data.name || preset}`);
48
+ if (data.id)
49
+ console.log(`Rule ID: ${data.id}`);
50
+ }
51
+ }
52
+ catch (err) {
53
+ outputError(err.message, json);
54
+ process.exit(1);
55
+ }
56
+ });
57
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Config file management — ~/.kill-switch/config.json
3
+ */
4
+ export interface KillSwitchConfig {
5
+ apiKey?: string;
6
+ apiUrl?: string;
7
+ }
8
+ declare const CONFIG_DIR: string;
9
+ declare const CONFIG_FILE: string;
10
+ declare const DEFAULT_API_URL = "https://api.kill-switch.net";
11
+ export declare function loadConfig(): KillSwitchConfig;
12
+ export declare function saveConfig(config: KillSwitchConfig): void;
13
+ export declare function deleteConfig(): void;
14
+ /**
15
+ * Resolve the API key from (in priority order):
16
+ * 1. KILL_SWITCH_API_KEY env var
17
+ * 2. --api-key flag (passed at call site)
18
+ * 3. Config file
19
+ */
20
+ export declare function resolveApiKey(flagKey?: string): string | undefined;
21
+ /**
22
+ * Resolve the API URL.
23
+ */
24
+ export declare function resolveApiUrl(flagUrl?: string): string;
25
+ export { CONFIG_DIR, CONFIG_FILE, DEFAULT_API_URL };
package/dist/config.js ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Config file management — ~/.kill-switch/config.json
3
+ */
4
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from "fs";
5
+ import { join } from "path";
6
+ import { homedir } from "os";
7
+ const CONFIG_DIR = join(homedir(), ".kill-switch");
8
+ const CONFIG_FILE = join(CONFIG_DIR, "config.json");
9
+ const DEFAULT_API_URL = "https://api.kill-switch.net";
10
+ export function loadConfig() {
11
+ try {
12
+ const raw = readFileSync(CONFIG_FILE, "utf-8");
13
+ return JSON.parse(raw);
14
+ }
15
+ catch {
16
+ return {};
17
+ }
18
+ }
19
+ export function saveConfig(config) {
20
+ if (!existsSync(CONFIG_DIR)) {
21
+ mkdirSync(CONFIG_DIR, { recursive: true });
22
+ }
23
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
24
+ chmodSync(CONFIG_FILE, 0o600); // owner read/write only
25
+ }
26
+ export function deleteConfig() {
27
+ try {
28
+ writeFileSync(CONFIG_FILE, "{}\n");
29
+ }
30
+ catch {
31
+ // ignore
32
+ }
33
+ }
34
+ /**
35
+ * Resolve the API key from (in priority order):
36
+ * 1. KILL_SWITCH_API_KEY env var
37
+ * 2. --api-key flag (passed at call site)
38
+ * 3. Config file
39
+ */
40
+ export function resolveApiKey(flagKey) {
41
+ return process.env.KILL_SWITCH_API_KEY || flagKey || loadConfig().apiKey;
42
+ }
43
+ /**
44
+ * Resolve the API URL.
45
+ */
46
+ export function resolveApiUrl(flagUrl) {
47
+ return process.env.KILL_SWITCH_API_URL || flagUrl || loadConfig().apiUrl || DEFAULT_API_URL;
48
+ }
49
+ export { CONFIG_DIR, CONFIG_FILE, DEFAULT_API_URL };
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Kill Switch CLI
4
+ *
5
+ * Monitor cloud spending, kill runaway services from the terminal.
6
+ *
7
+ * Usage:
8
+ * kill-switch auth login --api-key ks_live_abc123
9
+ * kill-switch shield cost-runaway
10
+ * kill-switch check
11
+ * kill-switch accounts list --json
12
+ */
13
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Kill Switch CLI
4
+ *
5
+ * Monitor cloud spending, kill runaway services from the terminal.
6
+ *
7
+ * Usage:
8
+ * kill-switch auth login --api-key ks_live_abc123
9
+ * kill-switch shield cost-runaway
10
+ * kill-switch check
11
+ * kill-switch accounts list --json
12
+ */
13
+ import { Command } from "commander";
14
+ import { registerAuthCommands } from "./commands/auth.js";
15
+ import { registerAccountCommands } from "./commands/accounts.js";
16
+ import { registerRuleCommands } from "./commands/rules.js";
17
+ import { registerShieldCommands } from "./commands/shield.js";
18
+ import { registerCheckCommands } from "./commands/check.js";
19
+ import { registerAlertCommands } from "./commands/alerts.js";
20
+ import { registerKillCommands } from "./commands/kill.js";
21
+ import { registerAnalyticsCommands } from "./commands/analytics.js";
22
+ import { registerConfigCommands } from "./commands/config-cmd.js";
23
+ import { registerOnboardCommands } from "./commands/onboard.js";
24
+ const program = new Command();
25
+ program
26
+ .name("kill-switch")
27
+ .description("Monitor cloud spending, kill runaway services, protect your infrastructure")
28
+ .version("0.1.0")
29
+ .option("--json", "Output as JSON (for automation/scripting)")
30
+ .option("--api-key <key>", "API key (overrides config and env)")
31
+ .option("--api-url <url>", "API URL (overrides config and env)");
32
+ registerAuthCommands(program);
33
+ registerAccountCommands(program);
34
+ registerRuleCommands(program);
35
+ registerShieldCommands(program);
36
+ registerCheckCommands(program);
37
+ registerAlertCommands(program);
38
+ registerKillCommands(program);
39
+ registerAnalyticsCommands(program);
40
+ registerConfigCommands(program);
41
+ registerOnboardCommands(program);
42
+ program.parse();
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Output formatting — tables for humans, JSON for machines
3
+ */
4
+ export interface Column {
5
+ key: string;
6
+ header: string;
7
+ width?: number;
8
+ }
9
+ export declare function formatTable(rows: any[], columns: Column[]): void;
10
+ export declare function formatObject(obj: any, fields?: string[]): void;
11
+ export declare function outputJson(data: any): void;
12
+ export declare function outputError(message: string, json: boolean): void;
13
+ /**
14
+ * Wrap a command handler with JSON/error handling.
15
+ */
16
+ export declare function withOutput(fn: (opts: any) => Promise<any>, opts: {
17
+ json: boolean;
18
+ }): Promise<any>;