@simonfestl/husky-cli 1.15.1 → 1.17.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/dist/commands/auth.js +25 -2
- package/dist/commands/config.d.ts +8 -3
- package/dist/commands/config.js +91 -10
- package/dist/commands/interactive/auth.d.ts +6 -0
- package/dist/commands/interactive/auth.js +227 -0
- package/dist/commands/interactive/brain.d.ts +6 -0
- package/dist/commands/interactive/brain.js +356 -0
- package/dist/commands/interactive/chat.d.ts +6 -0
- package/dist/commands/interactive/chat.js +367 -0
- package/dist/commands/interactive/infra.d.ts +6 -0
- package/dist/commands/interactive/infra.js +243 -0
- package/dist/commands/interactive/pr.d.ts +6 -0
- package/dist/commands/interactive/pr.js +323 -0
- package/dist/commands/interactive/preview.d.ts +6 -0
- package/dist/commands/interactive/preview.js +238 -0
- package/dist/commands/interactive/tools.d.ts +6 -0
- package/dist/commands/interactive/tools.js +375 -0
- package/dist/commands/interactive/worker.d.ts +6 -0
- package/dist/commands/interactive/worker.js +196 -0
- package/dist/commands/interactive.js +59 -15
- package/dist/lib/permissions.d.ts +1 -1
- package/dist/lib/permissions.js +10 -11
- package/package.json +1 -1
package/dist/commands/auth.js
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { getConfig, setSessionConfig, clearSessionConfig, getSessionConfig } from "./config.js";
|
|
2
|
+
import { getConfig, setSessionConfig, clearSessionConfig, getSessionConfig, fetchAndCacheRole } from "./config.js";
|
|
3
3
|
import { getPermissions, clearPermissionsCache, getCacheStatus, hasPermission, canAccessKnowledgeBase } from "../lib/permissions-cache.js";
|
|
4
4
|
const API_KEY_ROLES = [
|
|
5
5
|
"admin", "supervisor", "worker", "reviewer", "support",
|
|
6
6
|
"purchasing", "ops", "e2e_agent", "pr_agent"
|
|
7
7
|
];
|
|
8
|
+
/**
|
|
9
|
+
* Sanitize error messages to prevent sensitive data leakage.
|
|
10
|
+
* Truncates long messages and removes potential secrets.
|
|
11
|
+
*/
|
|
12
|
+
function sanitizeErrorMessage(message, maxLength = 200) {
|
|
13
|
+
if (!message)
|
|
14
|
+
return "Unknown error";
|
|
15
|
+
// Remove potential secrets (patterns like API keys, tokens, etc.)
|
|
16
|
+
let sanitized = message
|
|
17
|
+
.replace(/[a-zA-Z0-9_-]{32,}/g, "[REDACTED]") // Long alphanumeric strings
|
|
18
|
+
.replace(/Bearer\s+[^\s]+/gi, "Bearer [REDACTED]") // Bearer tokens
|
|
19
|
+
.replace(/key[=:]\s*[^\s,}]+/gi, "key=[REDACTED]"); // key=value patterns
|
|
20
|
+
// Truncate if too long
|
|
21
|
+
if (sanitized.length > maxLength) {
|
|
22
|
+
sanitized = sanitized.substring(0, maxLength) + "...";
|
|
23
|
+
}
|
|
24
|
+
return sanitized;
|
|
25
|
+
}
|
|
8
26
|
async function apiRequest(path, options = {}) {
|
|
9
27
|
const config = getConfig();
|
|
10
28
|
if (!config.apiUrl || !config.apiKey) {
|
|
@@ -21,7 +39,8 @@ async function apiRequest(path, options = {}) {
|
|
|
21
39
|
});
|
|
22
40
|
if (!res.ok) {
|
|
23
41
|
const error = await res.json().catch(() => ({ error: res.statusText }));
|
|
24
|
-
|
|
42
|
+
const rawMessage = error.message || error.error || `HTTP ${res.status}`;
|
|
43
|
+
throw new Error(sanitizeErrorMessage(rawMessage));
|
|
25
44
|
}
|
|
26
45
|
return res.json();
|
|
27
46
|
}
|
|
@@ -293,6 +312,8 @@ authCommand
|
|
|
293
312
|
}
|
|
294
313
|
const session = await res.json();
|
|
295
314
|
setSessionConfig(session);
|
|
315
|
+
// Fetch and cache permissions for the new session role
|
|
316
|
+
await fetchAndCacheRole();
|
|
296
317
|
if (options.json) {
|
|
297
318
|
console.log(JSON.stringify({
|
|
298
319
|
success: true,
|
|
@@ -418,6 +439,8 @@ authCommand
|
|
|
418
439
|
}
|
|
419
440
|
const session = await res.json();
|
|
420
441
|
setSessionConfig(session);
|
|
442
|
+
// Refresh permissions for the session role
|
|
443
|
+
await fetchAndCacheRole();
|
|
421
444
|
if (options.json) {
|
|
422
445
|
console.log(JSON.stringify({
|
|
423
446
|
success: true,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
|
|
2
|
+
declare const VALID_ROLES: readonly ["admin", "supervisor", "worker", "reviewer", "e2e_agent", "pr_agent", "support", "devops", "purchasing", "ops"];
|
|
3
|
+
type AgentRole = typeof VALID_ROLES[number];
|
|
3
4
|
interface Config {
|
|
4
5
|
apiUrl?: string;
|
|
5
6
|
apiKey?: string;
|
|
@@ -47,7 +48,8 @@ export declare function getConfig(): Config;
|
|
|
47
48
|
export declare function saveConfig(config: Config): void;
|
|
48
49
|
/**
|
|
49
50
|
* Fetch role and permissions from /api/auth/whoami
|
|
50
|
-
*
|
|
51
|
+
* Uses session token (Bearer) if available, otherwise falls back to API key.
|
|
52
|
+
* Caches the result in config for 1 hour.
|
|
51
53
|
*/
|
|
52
54
|
export declare function fetchAndCacheRole(): Promise<{
|
|
53
55
|
role?: AgentRole;
|
|
@@ -58,7 +60,9 @@ export declare function fetchAndCacheRole(): Promise<{
|
|
|
58
60
|
*/
|
|
59
61
|
export declare function hasPermission(permission: string): boolean;
|
|
60
62
|
/**
|
|
61
|
-
* Get current role from config
|
|
63
|
+
* Get current role from config.
|
|
64
|
+
* Prefers sessionRole (from auth login) over role (from API key) when session is active.
|
|
65
|
+
* Validates that the role is a known valid role before returning.
|
|
62
66
|
*/
|
|
63
67
|
export declare function getRole(): AgentRole | undefined;
|
|
64
68
|
/**
|
|
@@ -72,6 +76,7 @@ export declare function setSessionConfig(session: {
|
|
|
72
76
|
expiresAt: string;
|
|
73
77
|
agent: string;
|
|
74
78
|
role: string;
|
|
79
|
+
permissions?: string[];
|
|
75
80
|
}): void;
|
|
76
81
|
export declare function clearSessionConfig(): void;
|
|
77
82
|
export declare function getSessionConfig(): {
|
package/dist/commands/config.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import { ErrorHelpers, errorWithHint, ExplainTopic } from "../lib/error-hints.js";
|
|
6
|
+
// Valid agent roles - used for runtime validation
|
|
7
|
+
const VALID_ROLES = ["admin", "supervisor", "worker", "reviewer", "e2e_agent", "pr_agent", "support", "devops", "purchasing", "ops"];
|
|
6
8
|
const CONFIG_DIR = join(homedir(), ".husky");
|
|
7
9
|
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
10
|
+
/**
|
|
11
|
+
* Validate if a string is a valid AgentRole
|
|
12
|
+
*/
|
|
13
|
+
function isValidRole(role) {
|
|
14
|
+
return VALID_ROLES.includes(role);
|
|
15
|
+
}
|
|
8
16
|
// API Key validation - must be at least 16 characters, alphanumeric + common key chars (base64, JWT, etc.)
|
|
9
17
|
function validateApiKey(key) {
|
|
10
18
|
if (key.length < 16) {
|
|
@@ -43,25 +51,67 @@ export function getConfig() {
|
|
|
43
51
|
}
|
|
44
52
|
export function saveConfig(config) {
|
|
45
53
|
if (!existsSync(CONFIG_DIR)) {
|
|
46
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
54
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 }); // rwx------ for directory
|
|
55
|
+
}
|
|
56
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 }); // rw------- for file
|
|
57
|
+
// Ensure permissions are set even if file existed
|
|
58
|
+
try {
|
|
59
|
+
chmodSync(CONFIG_FILE, 0o600);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Ignore chmod errors on Windows
|
|
47
63
|
}
|
|
48
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
49
64
|
}
|
|
50
65
|
/**
|
|
51
66
|
* Fetch role and permissions from /api/auth/whoami
|
|
52
|
-
*
|
|
67
|
+
* Uses session token (Bearer) if available, otherwise falls back to API key.
|
|
68
|
+
* Caches the result in config for 1 hour.
|
|
53
69
|
*/
|
|
54
70
|
export async function fetchAndCacheRole() {
|
|
55
71
|
const config = getConfig();
|
|
56
|
-
// Check if
|
|
72
|
+
// Check if there's an active session - if so, use session role directly
|
|
73
|
+
// Session roles are already validated by the server, no need to re-fetch
|
|
74
|
+
if (config.sessionToken && config.sessionRole && config.sessionExpiresAt) {
|
|
75
|
+
const expiresAt = new Date(config.sessionExpiresAt);
|
|
76
|
+
if (expiresAt > new Date()) {
|
|
77
|
+
// Session is active - fetch permissions for this session role if needed
|
|
78
|
+
// Check if we have fresh permissions for this session
|
|
79
|
+
const needsPermissionsFetch = !config.roleLastChecked || !config.permissions;
|
|
80
|
+
if (needsPermissionsFetch && config.apiUrl) {
|
|
81
|
+
try {
|
|
82
|
+
const url = new URL("/api/auth/whoami", config.apiUrl);
|
|
83
|
+
const res = await fetch(url.toString(), {
|
|
84
|
+
headers: { Authorization: `Bearer ${config.sessionToken}` },
|
|
85
|
+
});
|
|
86
|
+
if (res.ok) {
|
|
87
|
+
const data = await res.json();
|
|
88
|
+
// Update permissions cache (keep sessionRole as source of truth for role)
|
|
89
|
+
config.permissions = data.permissions;
|
|
90
|
+
config.roleLastChecked = new Date().toISOString();
|
|
91
|
+
saveConfig(config);
|
|
92
|
+
return { role: config.sessionRole, permissions: data.permissions };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Fall through to use cached permissions
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Return session role with cached permissions
|
|
100
|
+
return { role: config.sessionRole, permissions: config.permissions };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// No active session - use API key auth
|
|
104
|
+
// Check if we have cached role that's less than 5 minutes old
|
|
105
|
+
// Short TTL ensures revoked permissions are detected quickly
|
|
106
|
+
const PERMISSION_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
57
107
|
if (config.role && config.roleLastChecked) {
|
|
58
108
|
const lastChecked = new Date(config.roleLastChecked);
|
|
59
|
-
const
|
|
60
|
-
if (lastChecked >
|
|
109
|
+
const cacheExpiry = new Date(Date.now() - PERMISSION_CACHE_TTL_MS);
|
|
110
|
+
if (lastChecked > cacheExpiry) {
|
|
61
111
|
return { role: config.role, permissions: config.permissions };
|
|
62
112
|
}
|
|
63
113
|
}
|
|
64
|
-
// Fetch fresh role/permissions
|
|
114
|
+
// Fetch fresh role/permissions using API key
|
|
65
115
|
if (!config.apiUrl || !config.apiKey) {
|
|
66
116
|
return {};
|
|
67
117
|
}
|
|
@@ -102,10 +152,30 @@ export function hasPermission(permission) {
|
|
|
102
152
|
return false;
|
|
103
153
|
}
|
|
104
154
|
/**
|
|
105
|
-
* Get current role from config
|
|
155
|
+
* Get current role from config.
|
|
156
|
+
* Prefers sessionRole (from auth login) over role (from API key) when session is active.
|
|
157
|
+
* Validates that the role is a known valid role before returning.
|
|
106
158
|
*/
|
|
107
159
|
export function getRole() {
|
|
108
|
-
|
|
160
|
+
const config = getConfig();
|
|
161
|
+
// Check if there's an active (non-expired) session
|
|
162
|
+
if (config.sessionToken && config.sessionRole && config.sessionExpiresAt) {
|
|
163
|
+
const expiresAt = new Date(config.sessionExpiresAt);
|
|
164
|
+
if (expiresAt > new Date()) {
|
|
165
|
+
// Session is active, validate and use session role
|
|
166
|
+
if (isValidRole(config.sessionRole)) {
|
|
167
|
+
return config.sessionRole;
|
|
168
|
+
}
|
|
169
|
+
// Invalid role in session - treat as no session
|
|
170
|
+
console.error(`Warning: Invalid session role '${config.sessionRole}' in config`);
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Fall back to API key role (also validate)
|
|
175
|
+
if (config.role && isValidRole(config.role)) {
|
|
176
|
+
return config.role;
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
109
179
|
}
|
|
110
180
|
/**
|
|
111
181
|
* Clear the role cache to force a refresh on next fetchAndCacheRole call
|
|
@@ -133,6 +203,17 @@ export function setSessionConfig(session) {
|
|
|
133
203
|
config.sessionExpiresAt = session.expiresAt;
|
|
134
204
|
config.sessionAgent = session.agent;
|
|
135
205
|
config.sessionRole = session.role;
|
|
206
|
+
// Clear old permissions and role cache to force re-fetch for new session role
|
|
207
|
+
delete config.roleLastChecked;
|
|
208
|
+
if (session.permissions) {
|
|
209
|
+
// If permissions provided, use them directly
|
|
210
|
+
config.permissions = session.permissions;
|
|
211
|
+
config.roleLastChecked = new Date().toISOString();
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
// Clear permissions to force re-fetch
|
|
215
|
+
delete config.permissions;
|
|
216
|
+
}
|
|
136
217
|
saveConfig(config);
|
|
137
218
|
}
|
|
138
219
|
export function clearSessionConfig() {
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Mode: Auth Module
|
|
3
|
+
*
|
|
4
|
+
* Provides menu-based authentication and API key management.
|
|
5
|
+
*/
|
|
6
|
+
import { select, input, confirm } from "@inquirer/prompts";
|
|
7
|
+
import { getConfig, setConfig, setSessionConfig, clearSessionConfig } from "../config.js";
|
|
8
|
+
import { pressEnterToContinue } from "./utils.js";
|
|
9
|
+
export async function authMenu() {
|
|
10
|
+
console.log("\n AUTH");
|
|
11
|
+
console.log(" " + "-".repeat(50));
|
|
12
|
+
console.log(" API keys and session management");
|
|
13
|
+
console.log("");
|
|
14
|
+
const menuItems = [
|
|
15
|
+
{ name: "🔑 Login (Create Session)", value: "login" },
|
|
16
|
+
{ name: "📊 Session Status", value: "session" },
|
|
17
|
+
{ name: "🔄 Refresh Token", value: "refresh" },
|
|
18
|
+
{ name: "🚪 Logout", value: "logout" },
|
|
19
|
+
{ name: "🔐 Show API Key", value: "show-key" },
|
|
20
|
+
{ name: "✏️ Set API Key", value: "set-key" },
|
|
21
|
+
{ name: "← Back", value: "back" },
|
|
22
|
+
];
|
|
23
|
+
const choice = await select({
|
|
24
|
+
message: "Auth Action:",
|
|
25
|
+
choices: menuItems,
|
|
26
|
+
});
|
|
27
|
+
switch (choice) {
|
|
28
|
+
case "login":
|
|
29
|
+
await login();
|
|
30
|
+
break;
|
|
31
|
+
case "session":
|
|
32
|
+
await sessionStatus();
|
|
33
|
+
break;
|
|
34
|
+
case "refresh":
|
|
35
|
+
await refreshToken();
|
|
36
|
+
break;
|
|
37
|
+
case "logout":
|
|
38
|
+
await logout();
|
|
39
|
+
break;
|
|
40
|
+
case "show-key":
|
|
41
|
+
await showApiKey();
|
|
42
|
+
break;
|
|
43
|
+
case "set-key":
|
|
44
|
+
await setApiKey();
|
|
45
|
+
break;
|
|
46
|
+
case "back":
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function login() {
|
|
51
|
+
try {
|
|
52
|
+
const config = getConfig();
|
|
53
|
+
if (!config.apiUrl) {
|
|
54
|
+
console.error("\n Error: API URL not configured. Run 'husky config set api-url <url>' first.\n");
|
|
55
|
+
await pressEnterToContinue();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const agentName = await input({
|
|
59
|
+
message: "Agent name:",
|
|
60
|
+
default: "interactive-cli",
|
|
61
|
+
validate: (v) => (v.length > 0 ? true : "Agent name is required"),
|
|
62
|
+
});
|
|
63
|
+
console.log("\n Creating session...");
|
|
64
|
+
const res = await fetch(`${config.apiUrl}/api/auth/session`, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: {
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify({ agent: agentName }),
|
|
71
|
+
});
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
const error = await res.text();
|
|
74
|
+
console.error(`\n Error: ${error}\n`);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const data = await res.json();
|
|
78
|
+
// Save session token
|
|
79
|
+
setSessionConfig({
|
|
80
|
+
token: data.token,
|
|
81
|
+
expiresAt: data.expiresAt,
|
|
82
|
+
agent: agentName,
|
|
83
|
+
role: data.role || "",
|
|
84
|
+
});
|
|
85
|
+
console.log("\n Session created!");
|
|
86
|
+
console.log(` Agent: ${agentName}`);
|
|
87
|
+
console.log(` Expires: ${new Date(data.expiresAt).toLocaleString()}`);
|
|
88
|
+
console.log("");
|
|
89
|
+
}
|
|
90
|
+
await pressEnterToContinue();
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error("\n Error creating session:", error);
|
|
94
|
+
await pressEnterToContinue();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function sessionStatus() {
|
|
98
|
+
try {
|
|
99
|
+
const config = getConfig();
|
|
100
|
+
console.log("\n SESSION STATUS");
|
|
101
|
+
console.log(" " + "-".repeat(50));
|
|
102
|
+
if (!config.sessionToken) {
|
|
103
|
+
console.log(" No active session.");
|
|
104
|
+
console.log(" Use 'Login' to create a new session.");
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const expiresAt = config.sessionExpiresAt ? new Date(config.sessionExpiresAt) : null;
|
|
108
|
+
const isExpired = expiresAt ? expiresAt < new Date() : true;
|
|
109
|
+
console.log(` Agent: ${config.sessionAgent || "unknown"}`);
|
|
110
|
+
console.log(` Token: ${config.sessionToken.substring(0, 20)}...`);
|
|
111
|
+
console.log(` Expires: ${expiresAt ? expiresAt.toLocaleString() : "unknown"}`);
|
|
112
|
+
console.log(` Status: ${isExpired ? "❌ EXPIRED" : "✅ VALID"}`);
|
|
113
|
+
}
|
|
114
|
+
console.log("");
|
|
115
|
+
await pressEnterToContinue();
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
console.error("\n Error checking session:", error);
|
|
119
|
+
await pressEnterToContinue();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function refreshToken() {
|
|
123
|
+
try {
|
|
124
|
+
const config = getConfig();
|
|
125
|
+
if (!config.apiUrl) {
|
|
126
|
+
console.error("\n Error: API URL not configured.\n");
|
|
127
|
+
await pressEnterToContinue();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (!config.sessionToken) {
|
|
131
|
+
console.error("\n Error: No active session to refresh.\n");
|
|
132
|
+
await pressEnterToContinue();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
console.log("\n Refreshing token...");
|
|
136
|
+
const res = await fetch(`${config.apiUrl}/api/auth/refresh`, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: {
|
|
139
|
+
"Content-Type": "application/json",
|
|
140
|
+
Authorization: `Bearer ${config.sessionToken}`,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
if (!res.ok) {
|
|
144
|
+
const error = await res.text();
|
|
145
|
+
console.error(`\n Error: ${error}\n`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
const data = await res.json();
|
|
149
|
+
const currentConfig = getConfig();
|
|
150
|
+
setSessionConfig({
|
|
151
|
+
token: data.token,
|
|
152
|
+
expiresAt: data.expiresAt,
|
|
153
|
+
agent: currentConfig.sessionAgent || "",
|
|
154
|
+
role: data.role || currentConfig.sessionRole || "",
|
|
155
|
+
});
|
|
156
|
+
console.log("\n Token refreshed!");
|
|
157
|
+
console.log(` New expiry: ${new Date(data.expiresAt).toLocaleString()}`);
|
|
158
|
+
console.log("");
|
|
159
|
+
}
|
|
160
|
+
await pressEnterToContinue();
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
console.error("\n Error refreshing token:", error);
|
|
164
|
+
await pressEnterToContinue();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function logout() {
|
|
168
|
+
try {
|
|
169
|
+
const config = getConfig();
|
|
170
|
+
if (!config.sessionToken) {
|
|
171
|
+
console.log("\n No active session.\n");
|
|
172
|
+
await pressEnterToContinue();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const confirmed = await confirm({
|
|
176
|
+
message: "Are you sure you want to logout?",
|
|
177
|
+
default: true,
|
|
178
|
+
});
|
|
179
|
+
if (!confirmed) {
|
|
180
|
+
console.log("\n Cancelled.\n");
|
|
181
|
+
await pressEnterToContinue();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// Clear session from config
|
|
185
|
+
clearSessionConfig();
|
|
186
|
+
console.log("\n Logged out successfully!\n");
|
|
187
|
+
await pressEnterToContinue();
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
console.error("\n Error logging out:", error);
|
|
191
|
+
await pressEnterToContinue();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function showApiKey() {
|
|
195
|
+
const config = getConfig();
|
|
196
|
+
console.log("\n API KEY");
|
|
197
|
+
console.log(" " + "-".repeat(50));
|
|
198
|
+
if (!config.apiKey) {
|
|
199
|
+
console.log(" No API key configured.");
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
const masked = config.apiKey.substring(0, 8) + "..." + config.apiKey.substring(config.apiKey.length - 4);
|
|
203
|
+
console.log(` API Key: ${masked}`);
|
|
204
|
+
console.log(` Length: ${config.apiKey.length} characters`);
|
|
205
|
+
}
|
|
206
|
+
console.log("");
|
|
207
|
+
await pressEnterToContinue();
|
|
208
|
+
}
|
|
209
|
+
async function setApiKey() {
|
|
210
|
+
try {
|
|
211
|
+
const key = await input({
|
|
212
|
+
message: "New API Key:",
|
|
213
|
+
validate: (v) => {
|
|
214
|
+
if (v.length < 16)
|
|
215
|
+
return "API key must be at least 16 characters";
|
|
216
|
+
return true;
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
setConfig("apiKey", key);
|
|
220
|
+
console.log("\n API Key saved!\n");
|
|
221
|
+
await pressEnterToContinue();
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
console.error("\n Error setting API key:", error);
|
|
225
|
+
await pressEnterToContinue();
|
|
226
|
+
}
|
|
227
|
+
}
|