@kill-switch/cli 0.3.0 → 0.3.2
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/dist/commands/auth.js +6 -2
- package/dist/commands/config-cmd.js +20 -5
- package/dist/config.d.ts +4 -1
- package/dist/config.js +19 -2
- package/package.json +2 -2
package/dist/commands/auth.js
CHANGED
|
@@ -101,8 +101,12 @@ export function registerAuthCommands(program, createClient) {
|
|
|
101
101
|
.option("--api-key <key>", "Personal API key (starts with ks_) — skips browser flow")
|
|
102
102
|
.action(async (opts) => {
|
|
103
103
|
const json = program.opts().json;
|
|
104
|
-
const apiUrl = resolveApiUrl();
|
|
105
|
-
|
|
104
|
+
const apiUrl = resolveApiUrl(program.opts().apiUrl);
|
|
105
|
+
// The program also declares a global --api-key/--api-url; by commander's
|
|
106
|
+
// parent/child precedence the value lands on the PARENT even when passed
|
|
107
|
+
// after `login`, leaving opts.apiKey undefined. Read from either place so
|
|
108
|
+
// `ks auth login --api-key KEY` actually works.
|
|
109
|
+
let key = opts.apiKey ?? program.opts().apiKey;
|
|
106
110
|
// Device flow when no --api-key. JSON mode requires --api-key (no browser).
|
|
107
111
|
if (!key) {
|
|
108
112
|
if (json) {
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { loadConfig, saveConfig, CONFIG_FILE, DEFAULT_API_URL } from "../config.js";
|
|
2
2
|
import { outputJson } from "../output.js";
|
|
3
|
+
/** Mask a secret so it never lands in logs / CI output in full. */
|
|
4
|
+
function maskKey(v) {
|
|
5
|
+
const s = String(v ?? "");
|
|
6
|
+
return s.length > 12 ? `${s.slice(0, 12)}…${s.slice(-2)}` : "****";
|
|
7
|
+
}
|
|
8
|
+
/** Return a copy of the config with secret values masked unless `reveal`. */
|
|
9
|
+
function redactConfig(cfg, reveal) {
|
|
10
|
+
if (reveal || !cfg.apiKey)
|
|
11
|
+
return cfg;
|
|
12
|
+
return { ...cfg, apiKey: maskKey(cfg.apiKey) };
|
|
13
|
+
}
|
|
3
14
|
export function registerConfigCommands(program) {
|
|
4
15
|
const config = program.command("config").description("Manage CLI configuration");
|
|
5
16
|
config
|
|
@@ -19,10 +30,13 @@ export function registerConfigCommands(program) {
|
|
|
19
30
|
config
|
|
20
31
|
.command("get <key>")
|
|
21
32
|
.description("Get a config value")
|
|
22
|
-
.
|
|
33
|
+
.option("--reveal", "Show secret values (e.g. apiKey) in full")
|
|
34
|
+
.action((key, opts) => {
|
|
23
35
|
const json = program.opts().json;
|
|
24
36
|
const cfg = loadConfig();
|
|
25
|
-
|
|
37
|
+
let value = cfg[key];
|
|
38
|
+
if (key === "apiKey" && value && !opts.reveal)
|
|
39
|
+
value = maskKey(value);
|
|
26
40
|
if (json) {
|
|
27
41
|
outputJson({ [key]: value ?? null });
|
|
28
42
|
}
|
|
@@ -49,11 +63,12 @@ export function registerConfigCommands(program) {
|
|
|
49
63
|
.command("list")
|
|
50
64
|
.alias("ls")
|
|
51
65
|
.description("Show all config values")
|
|
52
|
-
.
|
|
66
|
+
.option("--reveal", "Show secret values (e.g. apiKey) in full")
|
|
67
|
+
.action((opts) => {
|
|
53
68
|
const json = program.opts().json;
|
|
54
69
|
const cfg = loadConfig();
|
|
55
70
|
if (json) {
|
|
56
|
-
outputJson(cfg);
|
|
71
|
+
outputJson(redactConfig(cfg, !!opts?.reveal));
|
|
57
72
|
}
|
|
58
73
|
else {
|
|
59
74
|
const entries = Object.entries(cfg);
|
|
@@ -62,7 +77,7 @@ export function registerConfigCommands(program) {
|
|
|
62
77
|
}
|
|
63
78
|
else {
|
|
64
79
|
for (const [k, v] of entries) {
|
|
65
|
-
const display = k === "apiKey" ?
|
|
80
|
+
const display = k === "apiKey" && !opts?.reveal ? maskKey(v) : String(v);
|
|
66
81
|
console.log(`${k.padEnd(12)} ${display}`);
|
|
67
82
|
}
|
|
68
83
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -19,7 +19,10 @@ export declare function deleteConfig(): void;
|
|
|
19
19
|
*/
|
|
20
20
|
export declare function resolveApiKey(flagKey?: string): string | undefined;
|
|
21
21
|
/**
|
|
22
|
-
* Resolve the API URL.
|
|
22
|
+
* Resolve the API URL. The resolved endpoint receives the user's API key, so we
|
|
23
|
+
* refuse anything that isn't https:// (http:// allowed only for localhost) —
|
|
24
|
+
* this prevents a poisoned env var / config from redirecting the key over an
|
|
25
|
+
* insecure or unexpected channel (security: M-1).
|
|
23
26
|
*/
|
|
24
27
|
export declare function resolveApiUrl(flagUrl?: string): string;
|
|
25
28
|
export { CONFIG_DIR, CONFIG_FILE, DEFAULT_API_URL };
|
package/dist/config.js
CHANGED
|
@@ -40,10 +40,27 @@ export function deleteConfig() {
|
|
|
40
40
|
export function resolveApiKey(flagKey) {
|
|
41
41
|
return process.env.KILL_SWITCH_API_KEY || flagKey || loadConfig().apiKey;
|
|
42
42
|
}
|
|
43
|
+
const LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
43
44
|
/**
|
|
44
|
-
* Resolve the API URL.
|
|
45
|
+
* Resolve the API URL. The resolved endpoint receives the user's API key, so we
|
|
46
|
+
* refuse anything that isn't https:// (http:// allowed only for localhost) —
|
|
47
|
+
* this prevents a poisoned env var / config from redirecting the key over an
|
|
48
|
+
* insecure or unexpected channel (security: M-1).
|
|
45
49
|
*/
|
|
46
50
|
export function resolveApiUrl(flagUrl) {
|
|
47
|
-
|
|
51
|
+
const url = process.env.KILL_SWITCH_API_URL || flagUrl || loadConfig().apiUrl || DEFAULT_API_URL;
|
|
52
|
+
let parsed;
|
|
53
|
+
try {
|
|
54
|
+
parsed = new URL(url);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
throw new Error(`Invalid API URL: "${url}". Set a valid https:// URL.`);
|
|
58
|
+
}
|
|
59
|
+
const localhost = LOCAL_HOSTS.has(parsed.hostname);
|
|
60
|
+
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && localhost)) {
|
|
61
|
+
throw new Error(`Refusing to use API URL "${url}": it must be https:// (http:// allowed only for localhost). ` +
|
|
62
|
+
`This protects your API key from being sent over an insecure channel.`);
|
|
63
|
+
}
|
|
64
|
+
return url;
|
|
48
65
|
}
|
|
49
66
|
export { CONFIG_DIR, CONFIG_FILE, DEFAULT_API_URL };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kill-switch/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Kill Switch CLI — monitor cloud spending, kill runaway services from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"test": "vitest run"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@kill-switch/agent-guard": "^0.1.
|
|
16
|
+
"@kill-switch/agent-guard": "^0.1.1",
|
|
17
17
|
"@kill-switch/sdk": "^0.1.0",
|
|
18
18
|
"chalk": "^5.6.2",
|
|
19
19
|
"commander": "^12.1.0",
|