@simonfestl/husky-cli 1.16.0 → 1.18.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 +29 -3
- package/dist/commands/config.js +176 -10
- package/dist/commands/interactive/auth.js +2 -2
- package/dist/commands/interactive/brain.js +10 -9
- package/dist/commands/interactive/changelog.js +7 -6
- package/dist/commands/interactive/chat.js +16 -9
- package/dist/commands/interactive/departments.js +6 -5
- package/dist/commands/interactive/ideas.js +7 -6
- package/dist/commands/interactive/infra.js +7 -6
- package/dist/commands/interactive/preview.js +6 -5
- package/dist/commands/interactive/processes.js +6 -5
- package/dist/commands/interactive/projects.js +6 -5
- package/dist/commands/interactive/roadmaps.js +12 -11
- package/dist/commands/interactive/strategy.js +13 -12
- package/dist/commands/interactive/tasks.js +9 -8
- package/dist/commands/interactive/tools.js +4 -3
- package/dist/commands/interactive/vm-sessions.js +9 -8
- package/dist/commands/interactive/worker.js +5 -5
- package/dist/commands/interactive/workflows.js +11 -10
- 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,8 +76,30 @@ 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;
|
|
82
|
+
/**
|
|
83
|
+
* Check if there's an active (non-expired) session
|
|
84
|
+
*/
|
|
85
|
+
export declare function isSessionActive(): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Check if the API is properly configured for making requests.
|
|
88
|
+
* Returns true if we have either an active session or an API key.
|
|
89
|
+
*/
|
|
90
|
+
export declare function isApiConfigured(): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Get authentication headers for API requests.
|
|
93
|
+
* Returns Bearer token if session is active, otherwise x-api-key.
|
|
94
|
+
* Use this for all API calls to ensure consistent auth.
|
|
95
|
+
*/
|
|
96
|
+
export declare function getAuthHeaders(): Record<string, string>;
|
|
97
|
+
/**
|
|
98
|
+
* Ensure the session is valid, refreshing if needed.
|
|
99
|
+
* Call this before long-running operations (like watch modes).
|
|
100
|
+
* Returns true if session is valid (or was refreshed), false if no session/refresh failed.
|
|
101
|
+
*/
|
|
102
|
+
export declare function ensureValidSession(): Promise<boolean>;
|
|
77
103
|
export declare function getSessionConfig(): {
|
|
78
104
|
token?: string;
|
|
79
105
|
expiresAt?: string;
|
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() {
|
|
@@ -143,6 +224,91 @@ export function clearSessionConfig() {
|
|
|
143
224
|
delete config.sessionRole;
|
|
144
225
|
saveConfig(config);
|
|
145
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Check if there's an active (non-expired) session
|
|
229
|
+
*/
|
|
230
|
+
export function isSessionActive() {
|
|
231
|
+
const config = getConfig();
|
|
232
|
+
if (!config.sessionToken || !config.sessionExpiresAt) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
const expiresAt = new Date(config.sessionExpiresAt);
|
|
236
|
+
return expiresAt > new Date();
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Check if the API is properly configured for making requests.
|
|
240
|
+
* Returns true if we have either an active session or an API key.
|
|
241
|
+
*/
|
|
242
|
+
export function isApiConfigured() {
|
|
243
|
+
const config = getConfig();
|
|
244
|
+
return Boolean(config.apiUrl && (config.apiKey || isSessionActive()));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get authentication headers for API requests.
|
|
248
|
+
* Returns Bearer token if session is active, otherwise x-api-key.
|
|
249
|
+
* Use this for all API calls to ensure consistent auth.
|
|
250
|
+
*/
|
|
251
|
+
export function getAuthHeaders() {
|
|
252
|
+
const config = getConfig();
|
|
253
|
+
// Check if there's an active (non-expired) session
|
|
254
|
+
if (config.sessionToken && config.sessionExpiresAt) {
|
|
255
|
+
const expiresAt = new Date(config.sessionExpiresAt);
|
|
256
|
+
if (expiresAt > new Date()) {
|
|
257
|
+
// Session is active - use Bearer token
|
|
258
|
+
return { Authorization: `Bearer ${config.sessionToken}` };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// Fall back to API key
|
|
262
|
+
if (config.apiKey) {
|
|
263
|
+
return { "x-api-key": config.apiKey };
|
|
264
|
+
}
|
|
265
|
+
return {};
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Ensure the session is valid, refreshing if needed.
|
|
269
|
+
* Call this before long-running operations (like watch modes).
|
|
270
|
+
* Returns true if session is valid (or was refreshed), false if no session/refresh failed.
|
|
271
|
+
*/
|
|
272
|
+
export async function ensureValidSession() {
|
|
273
|
+
const config = getConfig();
|
|
274
|
+
if (!config.sessionToken || !config.sessionExpiresAt) {
|
|
275
|
+
return false; // No session, will use API key
|
|
276
|
+
}
|
|
277
|
+
const expiresAt = new Date(config.sessionExpiresAt);
|
|
278
|
+
const now = new Date();
|
|
279
|
+
const fiveMinutes = 5 * 60 * 1000;
|
|
280
|
+
// Refresh if expiring within 5 minutes
|
|
281
|
+
if (expiresAt.getTime() - now.getTime() < fiveMinutes) {
|
|
282
|
+
if (!config.apiUrl || !config.apiKey || !config.sessionAgent) {
|
|
283
|
+
return false; // Can't refresh without these
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const url = new URL("/api/auth/session", config.apiUrl);
|
|
287
|
+
const res = await fetch(url.toString(), {
|
|
288
|
+
method: "POST",
|
|
289
|
+
headers: {
|
|
290
|
+
"x-api-key": config.apiKey,
|
|
291
|
+
"Content-Type": "application/json",
|
|
292
|
+
},
|
|
293
|
+
body: JSON.stringify({ agent: config.sessionAgent }),
|
|
294
|
+
});
|
|
295
|
+
if (res.ok) {
|
|
296
|
+
const data = await res.json();
|
|
297
|
+
setSessionConfig({
|
|
298
|
+
token: data.token,
|
|
299
|
+
expiresAt: data.expiresAt,
|
|
300
|
+
agent: data.agent,
|
|
301
|
+
role: data.role,
|
|
302
|
+
});
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
// Refresh failed, continue with current token if not expired
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return expiresAt > now;
|
|
311
|
+
}
|
|
146
312
|
export function getSessionConfig() {
|
|
147
313
|
const config = getConfig();
|
|
148
314
|
if (!config.sessionToken)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Provides menu-based authentication and API key management.
|
|
5
5
|
*/
|
|
6
6
|
import { select, input, confirm } from "@inquirer/prompts";
|
|
7
|
-
import { getConfig, setConfig, setSessionConfig, clearSessionConfig } from "../config.js";
|
|
7
|
+
import { getConfig, setConfig, setSessionConfig, clearSessionConfig, getAuthHeaders } from "../config.js";
|
|
8
8
|
import { pressEnterToContinue } from "./utils.js";
|
|
9
9
|
export async function authMenu() {
|
|
10
10
|
console.log("\n AUTH");
|
|
@@ -65,7 +65,7 @@ async function login() {
|
|
|
65
65
|
method: "POST",
|
|
66
66
|
headers: {
|
|
67
67
|
"Content-Type": "application/json",
|
|
68
|
-
...(
|
|
68
|
+
...getAuthHeaders(),
|
|
69
69
|
},
|
|
70
70
|
body: JSON.stringify({ agent: agentName }),
|
|
71
71
|
});
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Provides menu-based agent memory and learning management.
|
|
5
5
|
*/
|
|
6
6
|
import { select, input, confirm } from "@inquirer/prompts";
|
|
7
|
+
import { getAuthHeaders } from "../config.js";
|
|
7
8
|
import { ensureConfig, pressEnterToContinue, truncate } from "./utils.js";
|
|
8
9
|
export async function brainMenu() {
|
|
9
10
|
const config = ensureConfig();
|
|
@@ -66,7 +67,7 @@ async function listMemories(config) {
|
|
|
66
67
|
default: "20",
|
|
67
68
|
});
|
|
68
69
|
const res = await fetch(`${config.apiUrl}/api/brain/memories?limit=${limit}`, {
|
|
69
|
-
headers:
|
|
70
|
+
headers: getAuthHeaders(),
|
|
70
71
|
});
|
|
71
72
|
if (!res.ok) {
|
|
72
73
|
console.error(`\n Error: API returned ${res.status}\n`);
|
|
@@ -103,7 +104,7 @@ async function recallMemory(config) {
|
|
|
103
104
|
validate: (v) => (v.length > 0 ? true : "Query is required"),
|
|
104
105
|
});
|
|
105
106
|
const res = await fetch(`${config.apiUrl}/api/brain/recall?query=${encodeURIComponent(query)}`, {
|
|
106
|
-
headers:
|
|
107
|
+
headers: getAuthHeaders(),
|
|
107
108
|
});
|
|
108
109
|
if (!res.ok) {
|
|
109
110
|
console.error(`\n Error: API returned ${res.status}\n`);
|
|
@@ -147,7 +148,7 @@ async function rememberContent(config) {
|
|
|
147
148
|
method: "POST",
|
|
148
149
|
headers: {
|
|
149
150
|
"Content-Type": "application/json",
|
|
150
|
-
...(
|
|
151
|
+
...(getAuthHeaders()),
|
|
151
152
|
},
|
|
152
153
|
body: JSON.stringify({ content, tags }),
|
|
153
154
|
});
|
|
@@ -183,7 +184,7 @@ async function publishMemory(config) {
|
|
|
183
184
|
method: "POST",
|
|
184
185
|
headers: {
|
|
185
186
|
"Content-Type": "application/json",
|
|
186
|
-
...(
|
|
187
|
+
...(getAuthHeaders()),
|
|
187
188
|
},
|
|
188
189
|
body: JSON.stringify({ visibility }),
|
|
189
190
|
});
|
|
@@ -204,7 +205,7 @@ async function publishMemory(config) {
|
|
|
204
205
|
async function sharedMemories(config) {
|
|
205
206
|
try {
|
|
206
207
|
const res = await fetch(`${config.apiUrl}/api/brain/shared`, {
|
|
207
|
-
headers:
|
|
208
|
+
headers: getAuthHeaders(),
|
|
208
209
|
});
|
|
209
210
|
if (!res.ok) {
|
|
210
211
|
console.error(`\n Error: API returned ${res.status}\n`);
|
|
@@ -242,7 +243,7 @@ async function boostMemory(config) {
|
|
|
242
243
|
});
|
|
243
244
|
const res = await fetch(`${config.apiUrl}/api/brain/boost/${id}`, {
|
|
244
245
|
method: "POST",
|
|
245
|
-
headers:
|
|
246
|
+
headers: getAuthHeaders(),
|
|
246
247
|
});
|
|
247
248
|
if (!res.ok) {
|
|
248
249
|
const error = await res.text();
|
|
@@ -275,7 +276,7 @@ async function archiveMemory(config) {
|
|
|
275
276
|
}
|
|
276
277
|
const res = await fetch(`${config.apiUrl}/api/brain/archive/${id}`, {
|
|
277
278
|
method: "POST",
|
|
278
|
-
headers:
|
|
279
|
+
headers: getAuthHeaders(),
|
|
279
280
|
});
|
|
280
281
|
if (!res.ok) {
|
|
281
282
|
const error = await res.text();
|
|
@@ -308,7 +309,7 @@ async function deleteMemory(config) {
|
|
|
308
309
|
}
|
|
309
310
|
const res = await fetch(`${config.apiUrl}/api/brain/memories/${id}`, {
|
|
310
311
|
method: "DELETE",
|
|
311
|
-
headers:
|
|
312
|
+
headers: getAuthHeaders(),
|
|
312
313
|
});
|
|
313
314
|
if (!res.ok) {
|
|
314
315
|
const error = await res.text();
|
|
@@ -337,7 +338,7 @@ async function cleanupMemories(config) {
|
|
|
337
338
|
}
|
|
338
339
|
const res = await fetch(`${config.apiUrl}/api/brain/cleanup`, {
|
|
339
340
|
method: "POST",
|
|
340
|
-
headers:
|
|
341
|
+
headers: getAuthHeaders(),
|
|
341
342
|
});
|
|
342
343
|
if (!res.ok) {
|
|
343
344
|
const error = await res.text();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { select, input, confirm } from "@inquirer/prompts";
|
|
2
2
|
import { execSync } from "child_process";
|
|
3
|
+
import { getAuthHeaders } from "../config.js";
|
|
3
4
|
import { ensureConfig, pressEnterToContinue, truncate, formatDate } from "./utils.js";
|
|
4
5
|
const TYPE_LABELS = {
|
|
5
6
|
feature: "New Features",
|
|
@@ -55,7 +56,7 @@ async function fetchChangelogs(config, projectId) {
|
|
|
55
56
|
url.searchParams.set("projectId", projectId);
|
|
56
57
|
}
|
|
57
58
|
const res = await fetch(url.toString(), {
|
|
58
|
-
headers:
|
|
59
|
+
headers: getAuthHeaders(),
|
|
59
60
|
});
|
|
60
61
|
if (!res.ok)
|
|
61
62
|
throw new Error(`API returned ${res.status}`);
|
|
@@ -63,7 +64,7 @@ async function fetchChangelogs(config, projectId) {
|
|
|
63
64
|
}
|
|
64
65
|
async function fetchProjects(config) {
|
|
65
66
|
const res = await fetch(`${config.apiUrl}/api/projects`, {
|
|
66
|
-
headers:
|
|
67
|
+
headers: getAuthHeaders(),
|
|
67
68
|
});
|
|
68
69
|
if (!res.ok)
|
|
69
70
|
throw new Error(`API returned ${res.status}`);
|
|
@@ -86,7 +87,7 @@ async function selectChangelog(config, message) {
|
|
|
86
87
|
return null;
|
|
87
88
|
// Fetch full changelog
|
|
88
89
|
const res = await fetch(`${config.apiUrl}/api/changelogs/${changelogId}`, {
|
|
89
|
-
headers:
|
|
90
|
+
headers: getAuthHeaders(),
|
|
90
91
|
});
|
|
91
92
|
if (!res.ok)
|
|
92
93
|
return null;
|
|
@@ -208,7 +209,7 @@ async function generateChangelog(config) {
|
|
|
208
209
|
method: "POST",
|
|
209
210
|
headers: {
|
|
210
211
|
"Content-Type": "application/json",
|
|
211
|
-
...(
|
|
212
|
+
...(getAuthHeaders()),
|
|
212
213
|
},
|
|
213
214
|
body: JSON.stringify({
|
|
214
215
|
projectId,
|
|
@@ -279,7 +280,7 @@ async function publishChangelog(config) {
|
|
|
279
280
|
method: "PATCH",
|
|
280
281
|
headers: {
|
|
281
282
|
"Content-Type": "application/json",
|
|
282
|
-
...(
|
|
283
|
+
...(getAuthHeaders()),
|
|
283
284
|
},
|
|
284
285
|
body: JSON.stringify({
|
|
285
286
|
status: "published",
|
|
@@ -315,7 +316,7 @@ async function deleteChangelog(config) {
|
|
|
315
316
|
}
|
|
316
317
|
const res = await fetch(`${config.apiUrl}/api/changelogs/${changelog.id}`, {
|
|
317
318
|
method: "DELETE",
|
|
318
|
-
headers:
|
|
319
|
+
headers: getAuthHeaders(),
|
|
319
320
|
});
|
|
320
321
|
if (!res.ok) {
|
|
321
322
|
console.error(`\n Error: API returned ${res.status}\n`);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { select, input, confirm } from "@inquirer/prompts";
|
|
7
7
|
import { ensureConfig, pressEnterToContinue, truncate, formatDate } from "./utils.js";
|
|
8
|
+
import { getAuthHeaders, ensureValidSession } from "../config.js";
|
|
8
9
|
export async function chatMenu() {
|
|
9
10
|
const config = ensureConfig();
|
|
10
11
|
console.log("\n CHAT");
|
|
@@ -67,7 +68,7 @@ async function showInbox(config) {
|
|
|
67
68
|
});
|
|
68
69
|
const url = `${config.apiUrl}/api/supervisor/inbox${unreadOnly ? "?unread=true" : ""}`;
|
|
69
70
|
const res = await fetch(url, {
|
|
70
|
-
headers:
|
|
71
|
+
headers: getAuthHeaders(),
|
|
71
72
|
});
|
|
72
73
|
if (!res.ok) {
|
|
73
74
|
console.error(`\n Error: API returned ${res.status}\n`);
|
|
@@ -102,7 +103,7 @@ async function showInbox(config) {
|
|
|
102
103
|
async function showPending(config) {
|
|
103
104
|
try {
|
|
104
105
|
const res = await fetch(`${config.apiUrl}/api/supervisor/pending`, {
|
|
105
|
-
headers:
|
|
106
|
+
headers: getAuthHeaders(),
|
|
106
107
|
});
|
|
107
108
|
if (!res.ok) {
|
|
108
109
|
console.error(`\n Error: API returned ${res.status}\n`);
|
|
@@ -142,7 +143,7 @@ async function sendMessage(config) {
|
|
|
142
143
|
method: "POST",
|
|
143
144
|
headers: {
|
|
144
145
|
"Content-Type": "application/json",
|
|
145
|
-
...(
|
|
146
|
+
...getAuthHeaders(),
|
|
146
147
|
},
|
|
147
148
|
body: JSON.stringify({ message }),
|
|
148
149
|
});
|
|
@@ -174,7 +175,7 @@ async function replyToMessage(config) {
|
|
|
174
175
|
method: "POST",
|
|
175
176
|
headers: {
|
|
176
177
|
"Content-Type": "application/json",
|
|
177
|
-
...(
|
|
178
|
+
...getAuthHeaders(),
|
|
178
179
|
},
|
|
179
180
|
body: JSON.stringify({ response }),
|
|
180
181
|
});
|
|
@@ -196,12 +197,15 @@ async function watchMessages(config) {
|
|
|
196
197
|
console.log("\n WATCH MODE");
|
|
197
198
|
console.log(" " + "-".repeat(50));
|
|
198
199
|
console.log(" Watching for new messages...");
|
|
200
|
+
console.log(" (Session token will auto-refresh if needed)");
|
|
199
201
|
console.log("");
|
|
200
202
|
let lastCheck = new Date().toISOString();
|
|
201
203
|
const checkMessages = async () => {
|
|
202
204
|
try {
|
|
205
|
+
// Ensure session is valid before each poll (auto-refreshes if needed)
|
|
206
|
+
await ensureValidSession();
|
|
203
207
|
const res = await fetch(`${config.apiUrl}/api/supervisor/inbox?since=${encodeURIComponent(lastCheck)}`, {
|
|
204
|
-
headers:
|
|
208
|
+
headers: getAuthHeaders(),
|
|
205
209
|
});
|
|
206
210
|
if (res.ok) {
|
|
207
211
|
const messages = await res.json();
|
|
@@ -211,6 +215,9 @@ async function watchMessages(config) {
|
|
|
211
215
|
console.log("");
|
|
212
216
|
}
|
|
213
217
|
}
|
|
218
|
+
else if (res.status === 401) {
|
|
219
|
+
console.log(` [${new Date().toLocaleTimeString()}] Auth error - will retry with refreshed token`);
|
|
220
|
+
}
|
|
214
221
|
lastCheck = new Date().toISOString();
|
|
215
222
|
}
|
|
216
223
|
catch {
|
|
@@ -229,7 +236,7 @@ async function watchMessages(config) {
|
|
|
229
236
|
async function showConversations(config) {
|
|
230
237
|
try {
|
|
231
238
|
const res = await fetch(`${config.apiUrl}/api/agent-conversations`, {
|
|
232
|
-
headers:
|
|
239
|
+
headers: getAuthHeaders(),
|
|
233
240
|
});
|
|
234
241
|
if (!res.ok) {
|
|
235
242
|
console.error(`\n Error: API returned ${res.status}\n`);
|
|
@@ -260,7 +267,7 @@ async function showConversations(config) {
|
|
|
260
267
|
async function showSpaces(config) {
|
|
261
268
|
try {
|
|
262
269
|
const res = await fetch(`${config.apiUrl}/api/chat/spaces`, {
|
|
263
|
-
headers:
|
|
270
|
+
headers: getAuthHeaders(),
|
|
264
271
|
});
|
|
265
272
|
if (!res.ok) {
|
|
266
273
|
console.error(`\n Error: API returned ${res.status}\n`);
|
|
@@ -303,7 +310,7 @@ async function askQuestion(config) {
|
|
|
303
310
|
method: "POST",
|
|
304
311
|
headers: {
|
|
305
312
|
"Content-Type": "application/json",
|
|
306
|
-
...(
|
|
313
|
+
...getAuthHeaders(),
|
|
307
314
|
},
|
|
308
315
|
body: JSON.stringify({ question, wait: waitForAnswer }),
|
|
309
316
|
});
|
|
@@ -342,7 +349,7 @@ async function requestReview(config) {
|
|
|
342
349
|
method: "POST",
|
|
343
350
|
headers: {
|
|
344
351
|
"Content-Type": "application/json",
|
|
345
|
-
...(
|
|
352
|
+
...getAuthHeaders(),
|
|
346
353
|
},
|
|
347
354
|
body: JSON.stringify({ question }),
|
|
348
355
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { select, input, confirm } from "@inquirer/prompts";
|
|
2
|
+
import { getAuthHeaders } from "../config.js";
|
|
2
3
|
import { ensureConfig, pressEnterToContinue, truncate } from "./utils.js";
|
|
3
4
|
export async function departmentsMenu() {
|
|
4
5
|
const config = ensureConfig();
|
|
@@ -36,7 +37,7 @@ export async function departmentsMenu() {
|
|
|
36
37
|
}
|
|
37
38
|
async function fetchDepartments(config) {
|
|
38
39
|
const res = await fetch(`${config.apiUrl}/api/departments`, {
|
|
39
|
-
headers:
|
|
40
|
+
headers: getAuthHeaders(),
|
|
40
41
|
});
|
|
41
42
|
if (!res.ok)
|
|
42
43
|
throw new Error(`API returned ${res.status}`);
|
|
@@ -87,7 +88,7 @@ async function viewDepartment(config) {
|
|
|
87
88
|
if (!dept)
|
|
88
89
|
return;
|
|
89
90
|
const res = await fetch(`${config.apiUrl}/api/departments/${dept.id}`, {
|
|
90
|
-
headers:
|
|
91
|
+
headers: getAuthHeaders(),
|
|
91
92
|
});
|
|
92
93
|
if (!res.ok) {
|
|
93
94
|
console.error(`\n Error: API returned ${res.status}\n`);
|
|
@@ -128,7 +129,7 @@ async function createDepartment(config) {
|
|
|
128
129
|
method: "POST",
|
|
129
130
|
headers: {
|
|
130
131
|
"Content-Type": "application/json",
|
|
131
|
-
...(
|
|
132
|
+
...(getAuthHeaders()),
|
|
132
133
|
},
|
|
133
134
|
body: JSON.stringify({
|
|
134
135
|
name,
|
|
@@ -192,7 +193,7 @@ async function updateDepartment(config) {
|
|
|
192
193
|
method: "PATCH",
|
|
193
194
|
headers: {
|
|
194
195
|
"Content-Type": "application/json",
|
|
195
|
-
...(
|
|
196
|
+
...(getAuthHeaders()),
|
|
196
197
|
},
|
|
197
198
|
body: JSON.stringify(updateData),
|
|
198
199
|
});
|
|
@@ -225,7 +226,7 @@ async function deleteDepartment(config) {
|
|
|
225
226
|
}
|
|
226
227
|
const res = await fetch(`${config.apiUrl}/api/departments/${dept.id}`, {
|
|
227
228
|
method: "DELETE",
|
|
228
|
-
headers:
|
|
229
|
+
headers: getAuthHeaders(),
|
|
229
230
|
});
|
|
230
231
|
if (!res.ok) {
|
|
231
232
|
console.error(`\n Error: API returned ${res.status}\n`);
|