@kill-switch/cli 0.2.0 → 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.
Files changed (43) hide show
  1. package/README.md +49 -4
  2. package/dist/commands/accounts.d.ts +2 -1
  3. package/dist/commands/accounts.js +44 -27
  4. package/dist/commands/activity.d.ts +3 -0
  5. package/dist/commands/activity.js +80 -0
  6. package/dist/commands/agent-guard.d.ts +10 -0
  7. package/dist/commands/agent-guard.js +175 -0
  8. package/dist/commands/alerts.d.ts +2 -1
  9. package/dist/commands/alerts.js +112 -12
  10. package/dist/commands/analytics.d.ts +2 -1
  11. package/dist/commands/analytics.js +36 -14
  12. package/dist/commands/auth.d.ts +2 -1
  13. package/dist/commands/auth.js +86 -46
  14. package/dist/commands/check.d.ts +2 -1
  15. package/dist/commands/check.js +28 -15
  16. package/dist/commands/kill.d.ts +2 -1
  17. package/dist/commands/kill.js +52 -37
  18. package/dist/commands/onboard.d.ts +2 -17
  19. package/dist/commands/onboard.js +244 -61
  20. package/dist/commands/orgs.d.ts +3 -0
  21. package/dist/commands/orgs.js +192 -0
  22. package/dist/commands/providers.d.ts +3 -0
  23. package/dist/commands/providers.js +82 -0
  24. package/dist/commands/rules.d.ts +2 -1
  25. package/dist/commands/rules.js +51 -28
  26. package/dist/commands/shield.d.ts +2 -1
  27. package/dist/commands/shield.js +36 -17
  28. package/dist/commands/status.d.ts +3 -0
  29. package/dist/commands/status.js +100 -0
  30. package/dist/commands/watch.d.ts +3 -0
  31. package/dist/commands/watch.js +68 -0
  32. package/dist/device-flow.d.ts +33 -0
  33. package/dist/device-flow.js +91 -0
  34. package/dist/index.js +38 -11
  35. package/dist/output.d.ts +26 -4
  36. package/dist/output.js +101 -12
  37. package/dist/prompts.d.ts +13 -0
  38. package/dist/prompts.js +24 -0
  39. package/dist/types.d.ts +2 -0
  40. package/dist/types.js +1 -0
  41. package/dist/version.d.ts +1 -0
  42. package/dist/version.js +4 -0
  43. package/package.json +29 -7
package/README.md CHANGED
@@ -40,7 +40,7 @@ ks onboard --provider cloudflare \
40
40
  --token YOUR_CF_API_TOKEN \
41
41
  --name "Production" \
42
42
  --shields cost-runaway,ddos \
43
- --alert-email you@example.com
43
+ --alert-pagerduty YOUR_ROUTING_KEY
44
44
 
45
45
  # AWS
46
46
  ks onboard --provider aws \
@@ -85,6 +85,40 @@ ks shield error-storm # Scale down on sustained high error rate
85
85
  ks shield --list
86
86
  ```
87
87
 
88
+ ## Alert Channels
89
+
90
+ ```sh
91
+ # List configured channels
92
+ ks alerts list
93
+
94
+ # Add PagerDuty (recommended — get routing key from PagerDuty > Service > Integrations)
95
+ ks alerts add --type pagerduty --routing-key YOUR_ROUTING_KEY
96
+
97
+ # Add Slack
98
+ ks alerts add --type slack --webhook-url https://hooks.slack.com/...
99
+
100
+ # Add Discord
101
+ ks alerts add --type discord --webhook-url https://discord.com/api/webhooks/...
102
+
103
+ # Add GitHub AI remediation (triggers Claude Code to open a fix PR on extreme violations)
104
+ ks alerts add --type github \
105
+ --token ghp_YOUR_PAT \
106
+ --repo-owner YOUR_ORG \
107
+ --repo-name YOUR_REPO \
108
+ --workflow kill-switch-remediate.yml \
109
+ --branch main
110
+
111
+ # Add email or generic webhook
112
+ ks alerts add --type email --email you@example.com
113
+ ks alerts add --type webhook --webhook-url https://your-service.example.com/webhook
114
+
115
+ # Remove a channel by name
116
+ ks alerts remove "PagerDuty"
117
+
118
+ # Send a test alert to all channels
119
+ ks alerts test
120
+ ```
121
+
88
122
  ## Commands
89
123
 
90
124
  | Command | Description |
@@ -92,14 +126,18 @@ ks shield --list
92
126
  | `ks onboard` | One-command setup: connect + shields + alerts |
93
127
  | `ks auth login` | Authenticate with API key |
94
128
  | `ks auth status` | Show auth status |
129
+ | `ks status` | Dashboard: accounts, alerts, 30-day spend summary |
95
130
  | `ks accounts list` | List connected cloud accounts |
96
131
  | `ks accounts add` | Connect a cloud provider |
97
132
  | `ks accounts check <id>` | Run manual check on an account |
98
- | `ks check` | Check all accounts |
133
+ | `ks check` | Check all accounts (shows violations with multiplier column e.g. 60x) |
99
134
  | `ks shield <preset>` | Apply a protection preset |
100
135
  | `ks rules list` | List active rules |
101
136
  | `ks alerts list` | List alert channels |
102
- | `ks analytics` | Cost analytics overview |
137
+ | `ks alerts add` | Add an alert channel |
138
+ | `ks alerts remove <name>` | Remove an alert channel by name |
139
+ | `ks alerts test` | Send a test alert to all channels |
140
+ | `ks analytics` | Cost analytics: spend summary, 7-day table, per-account breakdown |
103
141
  | `ks config list` | Show configuration |
104
142
 
105
143
  ## AI Agent Usage
@@ -117,8 +155,15 @@ ks onboard \
117
155
  --token CF_API_TOKEN \
118
156
  --name "Production" \
119
157
  --shields cost-runaway,ddos \
158
+ --alert-pagerduty KEY \
120
159
  --json
121
160
 
161
+ # Add PagerDuty alerts
162
+ ks alerts add --type pagerduty --routing-key KEY --json
163
+
164
+ # Full status dashboard
165
+ ks status --json
166
+
122
167
  # All commands support --json for machine-readable output
123
168
  ks accounts list --json
124
169
  ks check --json
@@ -171,7 +216,7 @@ The CLI uses personal API keys (prefixed with `ks_live_`). Create one from [app.
171
216
  - [Dashboard](https://app.kill-switch.net)
172
217
  - [API Docs](https://kill-switch.net/docs)
173
218
  - [CLI Docs](https://kill-switch.net/docs/cli.html)
174
- - [GitHub](https://github.com/AiExpanse/kill-switch)
219
+ - [GitHub](https://github.com/divinci-ai/kill-switch)
175
220
 
176
221
  ## License
177
222
 
@@ -1,2 +1,3 @@
1
1
  import { Command } from "commander";
2
- export declare function registerAccountCommands(program: Command): void;
2
+ import type { ClientFactory } from "../types.js";
3
+ export declare function registerAccountCommands(program: Command, createClient: ClientFactory): void;
@@ -1,22 +1,28 @@
1
- import { apiRequest } from "../api-client.js";
2
- import { outputJson, formatTable, formatObject, outputError } from "../output.js";
3
- export function registerAccountCommands(program) {
1
+ import { outputJson, formatTable, formatObject, handleError, spinner, success } from "../output.js";
2
+ import { confirm } from "../prompts.js";
3
+ export function registerAccountCommands(program, createClient) {
4
4
  const accounts = program.command("accounts").description("Manage cloud accounts");
5
5
  accounts
6
6
  .command("list")
7
7
  .alias("ls")
8
8
  .description("List connected cloud accounts")
9
- .action(async () => {
9
+ .option("--provider <provider>", "Filter by provider (cloudflare, gcp, aws, runpod)")
10
+ .option("--status <status>", "Filter by status (active, paused, disconnected)")
11
+ .action(async (opts) => {
10
12
  const json = program.opts().json;
11
13
  try {
12
- const data = await apiRequest("/cloud-accounts");
13
- const list = data.accounts || data;
14
+ const client = createClient();
15
+ let list = await client.accounts.list();
16
+ if (opts.provider)
17
+ list = list.filter((a) => a.provider === opts.provider);
18
+ if (opts.status)
19
+ list = list.filter((a) => a.status === opts.status);
14
20
  if (json) {
15
21
  outputJson(list);
16
22
  }
17
23
  else {
18
- formatTable(Array.isArray(list) ? list : [], [
19
- { key: "_id", header: "ID" },
24
+ formatTable(list, [
25
+ { key: "id", header: "ID" },
20
26
  { key: "provider", header: "Provider" },
21
27
  { key: "name", header: "Name" },
22
28
  { key: "status", header: "Status" },
@@ -24,8 +30,7 @@ export function registerAccountCommands(program) {
24
30
  }
25
31
  }
26
32
  catch (err) {
27
- outputError(err.message, json);
28
- process.exit(1);
33
+ handleError(err, json);
29
34
  }
30
35
  });
31
36
  accounts
@@ -34,7 +39,8 @@ export function registerAccountCommands(program) {
34
39
  .action(async (id) => {
35
40
  const json = program.opts().json;
36
41
  try {
37
- const data = await apiRequest(`/cloud-accounts/${id}`);
42
+ const client = createClient();
43
+ const data = await client.accounts.get(id);
38
44
  if (json) {
39
45
  outputJson(data);
40
46
  }
@@ -43,8 +49,7 @@ export function registerAccountCommands(program) {
43
49
  }
44
50
  }
45
51
  catch (err) {
46
- outputError(err.message, json);
47
- process.exit(1);
52
+ handleError(err, json);
48
53
  }
49
54
  });
50
55
  accounts
@@ -66,21 +71,25 @@ export function registerAccountCommands(program) {
66
71
  credential.projectId = opts.projectId;
67
72
  if (opts.serviceAccount)
68
73
  credential.serviceAccountJson = opts.serviceAccount;
74
+ const s = json ? null : spinner(`Connecting ${provider}...`).start();
69
75
  try {
70
- const data = await apiRequest("/cloud-accounts", {
71
- method: "POST",
72
- body: { provider, name: opts.name, credential },
76
+ const client = createClient();
77
+ const data = await client.accounts.create({
78
+ provider: provider,
79
+ name: opts.name,
80
+ credential: credential,
73
81
  });
82
+ s?.stop();
74
83
  if (json) {
75
84
  outputJson(data);
76
85
  }
77
86
  else {
78
- console.log(`Connected ${provider} account: ${data.name || data._id}`);
87
+ success(`Connected ${provider} account: ${data.name || data.id}`);
79
88
  }
80
89
  }
81
90
  catch (err) {
82
- outputError(err.message, json);
83
- process.exit(1);
91
+ s?.stop();
92
+ handleError(err, json);
84
93
  }
85
94
  });
86
95
  accounts
@@ -88,19 +97,24 @@ export function registerAccountCommands(program) {
88
97
  .alias("rm")
89
98
  .description("Disconnect and delete a cloud account")
90
99
  .action(async (id) => {
91
- const json = program.opts().json;
100
+ const { json, yes } = program.opts();
92
101
  try {
93
- await apiRequest(`/cloud-accounts/${id}`, { method: "DELETE" });
102
+ const ok = await confirm(`Are you sure you want to disconnect account ${id}?`, { yes, json });
103
+ if (!ok) {
104
+ console.log("Aborted.");
105
+ return;
106
+ }
107
+ const client = createClient();
108
+ await client.accounts.delete(id);
94
109
  if (json) {
95
110
  outputJson({ deleted: true, id });
96
111
  }
97
112
  else {
98
- console.log(`Account ${id} disconnected.`);
113
+ success(`Account ${id} disconnected.`);
99
114
  }
100
115
  }
101
116
  catch (err) {
102
- outputError(err.message, json);
103
- process.exit(1);
117
+ handleError(err, json);
104
118
  }
105
119
  });
106
120
  accounts
@@ -108,8 +122,11 @@ export function registerAccountCommands(program) {
108
122
  .description("Run manual monitoring check on an account")
109
123
  .action(async (id) => {
110
124
  const json = program.opts().json;
125
+ const s = json ? null : spinner("Running check...").start();
111
126
  try {
112
- const data = await apiRequest(`/cloud-accounts/${id}/check`, { method: "POST" });
127
+ const client = createClient();
128
+ const data = await client.accounts.check(id);
129
+ s?.stop();
113
130
  if (json) {
114
131
  outputJson(data);
115
132
  }
@@ -126,8 +143,8 @@ export function registerAccountCommands(program) {
126
143
  }
127
144
  }
128
145
  catch (err) {
129
- outputError(err.message, json);
130
- process.exit(1);
146
+ s?.stop();
147
+ handleError(err, json);
131
148
  }
132
149
  });
133
150
  }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ import type { ClientFactory } from "../types.js";
3
+ export declare function registerActivityCommands(program: Command, createClient: ClientFactory): void;
@@ -0,0 +1,80 @@
1
+ import { outputJson, formatTable, handleError } from "../output.js";
2
+ export function registerActivityCommands(program, createClient) {
3
+ const activity = program.command("activity").description("View audit trail and activity logs");
4
+ activity
5
+ .command("list")
6
+ .alias("ls")
7
+ .description("Query activity log (owner/admin only)")
8
+ .option("--page <n>", "Page number", "1")
9
+ .option("--limit <n>", "Results per page (max 100)", "25")
10
+ .option("--action <prefix>", "Filter by action prefix (e.g., cloud_account, rule, team, kill_switch)")
11
+ .option("--resource-type <type>", "Filter by resource type")
12
+ .option("--actor <userId>", "Filter by actor user ID")
13
+ .option("--from <date>", "Start date (ISO format)")
14
+ .option("--to <date>", "End date (ISO format)")
15
+ .action(async (opts) => {
16
+ const json = program.opts().json;
17
+ try {
18
+ const client = createClient();
19
+ const data = await client.activity.list({
20
+ page: parseInt(opts.page) || undefined,
21
+ limit: parseInt(opts.limit) || undefined,
22
+ action: opts.action,
23
+ resourceType: opts.resourceType,
24
+ actorUserId: opts.actor,
25
+ from: opts.from,
26
+ to: opts.to,
27
+ });
28
+ if (json) {
29
+ outputJson(data);
30
+ }
31
+ else {
32
+ console.log(`Activity Log — Page ${data.page} (${data.total} total entries)\n`);
33
+ formatTable((data.entries || []).map((e) => ({
34
+ time: new Date(e.created_at).toLocaleString(),
35
+ actor: e.actor_email || e.actor_user_id?.substring(0, 12) || "—",
36
+ action: e.action,
37
+ resource: `${e.resource_type}${e.resource_id ? `:${e.resource_id.substring(0, 8)}` : ""}`,
38
+ })), [
39
+ { key: "time", header: "Time" },
40
+ { key: "actor", header: "Actor" },
41
+ { key: "action", header: "Action" },
42
+ { key: "resource", header: "Resource" },
43
+ ]);
44
+ const totalPages = Math.ceil(data.total / data.limit);
45
+ if (totalPages > 1) {
46
+ console.log(`\nPage ${data.page} of ${totalPages}. Use --page ${data.page + 1} for next.`);
47
+ }
48
+ }
49
+ }
50
+ catch (err) {
51
+ handleError(err, json);
52
+ }
53
+ });
54
+ // Shorthand: `ks activity` without subcommand shows recent
55
+ activity
56
+ .action(async () => {
57
+ const json = program.opts().json;
58
+ try {
59
+ const client = createClient();
60
+ const data = await client.activity.list({ limit: 10 });
61
+ if (json) {
62
+ outputJson(data);
63
+ }
64
+ else {
65
+ console.log("Recent Activity (last 10):\n");
66
+ for (const e of data.entries || []) {
67
+ const time = new Date(e.created_at).toLocaleTimeString();
68
+ const actor = e.actor_email || e.actor_user_id?.substring(0, 12) || "?";
69
+ console.log(` ${time} ${actor} ${e.action} ${e.resourceType || ""}`);
70
+ }
71
+ if (data.total > 10) {
72
+ console.log(`\n ... ${data.total - 10} more. Use 'ks activity list' for full log.`);
73
+ }
74
+ }
75
+ }
76
+ catch (err) {
77
+ handleError(err, json);
78
+ }
79
+ });
80
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `ks guard` — the agent-guard surface inside the main Kill Switch CLI.
3
+ *
4
+ * Thin wrapper over @kill-switch/agent-guard's exported ops so users get one
5
+ * tool name. Local-only: these read/write the on-disk ledger + config and
6
+ * Claude Code settings; they do NOT call the Guardian API (the hook/proxy report
7
+ * breaches to the API on their own).
8
+ */
9
+ import { Command } from "commander";
10
+ export declare function registerAgentGuardCommands(program: Command): void;
@@ -0,0 +1,175 @@
1
+ /**
2
+ * `ks guard` — the agent-guard surface inside the main Kill Switch CLI.
3
+ *
4
+ * Thin wrapper over @kill-switch/agent-guard's exported ops so users get one
5
+ * tool name. Local-only: these read/write the on-disk ledger + config and
6
+ * Claude Code settings; they do NOT call the Guardian API (the hook/proxy report
7
+ * breaches to the API on their own).
8
+ */
9
+ import { createRequire } from "node:module";
10
+ import { buildStatusReport, installHook, setBudget, resetLedger, writePause, clearPause, configPath, pausePath, startProxy, resolveUpstream, fmtUSD, } from "@kill-switch/agent-guard";
11
+ import { outputJson, colors as c } from "../output.js";
12
+ /** Resolve the absolute path to the agent-guard hook entry (its dist/cli.js). */
13
+ function agentGuardCliPath() {
14
+ const require = createRequire(import.meta.url);
15
+ // Resolve the package entry, then point at the sibling cli.js the hook uses.
16
+ const pkgMain = require.resolve("@kill-switch/agent-guard");
17
+ return pkgMain.replace(/index\.js$/, "cli.js");
18
+ }
19
+ function bar(spent, hard) {
20
+ const pct = hard > 0 ? Math.min(100, Math.round((spent / hard) * 100)) : 0;
21
+ const filled = Math.round(pct / 5);
22
+ return `[${"█".repeat(filled)}${"░".repeat(20 - filled)}] ${pct}%`;
23
+ }
24
+ export function registerAgentGuardCommands(program) {
25
+ const guard = program
26
+ .command("guard")
27
+ .description("Kill Switch for coding agents — cap Claude Code / Cursor / Aider spend");
28
+ // ks guard status
29
+ guard
30
+ .command("status")
31
+ .description("Show current session + daily agent spend against the budget")
32
+ .action(() => {
33
+ const json = program.opts().json;
34
+ const report = buildStatusReport();
35
+ if (json) {
36
+ outputJson(report);
37
+ return;
38
+ }
39
+ const icon = report.paused ? "⏸ " : report.verdict === "block" ? "🛑" : report.verdict === "warn" ? "⚠️ " : "✅";
40
+ console.log(`\n${icon} ${c.bold("Agent Guard")} — ${report.paused ? "PAUSED (enforcement off)" : report.verdict.toUpperCase()}`);
41
+ if (report.paused) {
42
+ console.log(report.pauseUntil
43
+ ? c.dim(` resumes ${new Date(report.pauseUntil).toLocaleString()}`)
44
+ : c.dim(" paused indefinitely — `ks guard resume` to re-arm"));
45
+ }
46
+ console.log("");
47
+ console.log(` ${c.bold("Daily (24h):")} ${fmtUSD(report.dailyUSD)} / ${fmtUSD(report.budget.dailyHardUSD)} ${bar(report.dailyUSD, report.budget.dailyHardUSD)}`);
48
+ console.log("");
49
+ if (report.sessions.length === 0) {
50
+ console.log(c.dim(" No active sessions in the last 24h."));
51
+ }
52
+ else {
53
+ console.log(c.bold(" Active sessions (24h):"));
54
+ for (const s of report.sessions.slice(0, 8)) {
55
+ console.log(` ${fmtUSD(s.costUSD).padStart(9)} / ${fmtUSD(report.budget.sessionHardUSD)} ${bar(s.costUSD, report.budget.sessionHardUSD)} ${c.dim(s.id)}`);
56
+ }
57
+ }
58
+ for (const r of report.reasons)
59
+ console.log(` ${c.yellow("•")} ${r}`);
60
+ console.log("");
61
+ });
62
+ // ks guard install
63
+ guard
64
+ .command("install")
65
+ .description("Wire the agent-guard hook into Claude Code settings")
66
+ .option("--global", "Install into ~/.claude/settings.json (default: ./.claude/settings.json)")
67
+ .action((opts) => {
68
+ const json = program.opts().json;
69
+ const { settingsPath, command, added } = installHook(agentGuardCliPath(), process.execPath, { global: opts.global });
70
+ if (json) {
71
+ outputJson({ settingsPath, command, added });
72
+ return;
73
+ }
74
+ console.log(`✅ Hook installed → ${settingsPath}`);
75
+ console.log(` Events: ${added.length ? added.join(", ") : "(already present — no change)"}`);
76
+ console.log(c.dim(` Command: ${command}`));
77
+ console.log(c.dim(` Set caps with: ks guard config --session-hard 30 --daily-hard 150`));
78
+ });
79
+ // ks guard config
80
+ guard
81
+ .command("config")
82
+ .description("View or set agent-guard budget caps")
83
+ .option("--session-soft <usd>", "Per-session soft cap (warn)")
84
+ .option("--session-hard <usd>", "Per-session hard cap (block)")
85
+ .option("--daily-soft <usd>", "Daily rolling soft cap (warn)")
86
+ .option("--daily-hard <usd>", "Daily rolling hard cap (block)")
87
+ .option("--slack-webhook <url>", "Slack incoming-webhook for breach alerts")
88
+ .action((opts) => {
89
+ const json = program.opts().json;
90
+ const anySet = ["sessionSoft", "sessionHard", "dailySoft", "dailyHard", "slackWebhook"].some((k) => opts[k] !== undefined);
91
+ if (!anySet) {
92
+ const report = buildStatusReport();
93
+ if (json)
94
+ return outputJson({ budget: report.budget, configPath: configPath() });
95
+ console.log(JSON.stringify(report.budget, null, 2));
96
+ console.log(c.dim(`\nConfig file: ${configPath()}`));
97
+ return;
98
+ }
99
+ const num = (v) => (v !== undefined ? Number(v) : undefined);
100
+ const budget = setBudget({
101
+ sessionSoftUSD: num(opts.sessionSoft),
102
+ sessionHardUSD: num(opts.sessionHard),
103
+ dailySoftUSD: num(opts.dailySoft),
104
+ dailyHardUSD: num(opts.dailyHard),
105
+ slackWebhook: opts.slackWebhook,
106
+ });
107
+ if (json)
108
+ return outputJson({ budget, saved: true });
109
+ console.log(`✅ Saved → ${configPath()}`);
110
+ console.log(JSON.stringify(budget, null, 2));
111
+ });
112
+ // ks guard pause
113
+ guard
114
+ .command("pause")
115
+ .description("Temporarily disable enforcement (escape hatch)")
116
+ .option("--minutes <n>", "Auto-resume after N minutes (default: indefinite)")
117
+ .action((opts) => {
118
+ const json = program.opts().json;
119
+ const mins = opts.minutes !== undefined ? Number(opts.minutes) : NaN;
120
+ if (opts.minutes !== undefined && Number.isFinite(mins)) {
121
+ const until = Date.now() + mins * 60_000;
122
+ writePause(until);
123
+ if (json)
124
+ return outputJson({ paused: true, until });
125
+ console.log(`⏸ Enforcement paused until ${new Date(until).toLocaleString()} (${mins} min).`);
126
+ }
127
+ else {
128
+ writePause();
129
+ if (json)
130
+ return outputJson({ paused: true, until: null });
131
+ console.log("⏸ Enforcement paused indefinitely. Re-arm with `ks guard resume`.");
132
+ }
133
+ console.log(c.dim(` Sentinel: ${pausePath()}`));
134
+ });
135
+ // ks guard resume
136
+ guard
137
+ .command("resume")
138
+ .description("Re-arm enforcement after a pause")
139
+ .action(() => {
140
+ const json = program.opts().json;
141
+ clearPause();
142
+ if (json)
143
+ return outputJson({ paused: false });
144
+ console.log("✅ Enforcement re-armed.");
145
+ });
146
+ // ks guard reset
147
+ guard
148
+ .command("reset")
149
+ .description("Clear the agent spend ledger")
150
+ .option("--all", "Wipe all sessions")
151
+ .option("--session <id>", "Clear a single session")
152
+ .option("--today", "Clear sessions active today")
153
+ .action((opts) => {
154
+ const json = program.opts().json;
155
+ const msg = resetLedger({ all: opts.all, session: opts.session, today: opts.today });
156
+ if (json)
157
+ return outputJson({ message: msg });
158
+ console.log(`✅ ${msg}`);
159
+ });
160
+ // ks guard proxy
161
+ guard
162
+ .command("proxy")
163
+ .description("Start the token-metering proxy (HTTP 402 at the hard cap) for non-Claude-Code agents")
164
+ .option("--port <n>", "Port to listen on", "8787")
165
+ .option("--flavor <name>", "API flavor: anthropic | openai", "anthropic")
166
+ .option("--upstream <url>", "Upstream origin (default: api.anthropic.com / api.openai.com)")
167
+ .action((opts) => {
168
+ const flavor = opts.flavor === "openai" ? "openai" : "anthropic";
169
+ startProxy({
170
+ port: parseInt(opts.port, 10) || 8787,
171
+ flavor,
172
+ upstream: resolveUpstream(flavor, opts.upstream),
173
+ });
174
+ });
175
+ }
@@ -1,2 +1,3 @@
1
1
  import { Command } from "commander";
2
- export declare function registerAlertCommands(program: Command): void;
2
+ import type { ClientFactory } from "../types.js";
3
+ export declare function registerAlertCommands(program: Command, createClient: ClientFactory): void;