@simonfestl/husky-cli 1.25.2 → 1.25.4
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/brain.js +4 -1
- package/dist/commands/interactive/business.js +3 -0
- package/dist/commands/interactive/infra.js +3 -0
- package/dist/commands/interactive/pr.js +5 -0
- package/dist/commands/interactive/tasks.js +5 -0
- package/dist/commands/interactive/vm-sessions.js +9 -0
- package/dist/lib/biz/api-brain.js +39 -7
- package/package.json +1 -1
package/dist/commands/brain.js
CHANGED
|
@@ -7,7 +7,10 @@ function toDate(value) {
|
|
|
7
7
|
return value instanceof Date ? value : new Date(value);
|
|
8
8
|
}
|
|
9
9
|
function createBrain(agentId, agentType, options) {
|
|
10
|
-
|
|
10
|
+
// Use API if:
|
|
11
|
+
// 1. Explicitly requested via --use-api
|
|
12
|
+
// 2. shouldUseApi() returns true (no Qdrant configured or session token available)
|
|
13
|
+
if (options?.useApi || shouldUseApi()) {
|
|
11
14
|
return new ApiBrain({
|
|
12
15
|
agentId,
|
|
13
16
|
agentType: isValidAgentType(agentType) ? agentType : undefined,
|
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
import { select, input } from "@inquirer/prompts";
|
|
6
6
|
import { pressEnterToContinue } from "./utils.js";
|
|
7
7
|
import { ZendeskClient, BillbeeClient, SeaTableClient, QdrantClient, EmbeddingService } from "../../lib/biz/index.js";
|
|
8
|
+
import { requireAnyPermission } from "../../lib/permissions.js";
|
|
8
9
|
// ============================================
|
|
9
10
|
// MAIN BUSINESS MENU
|
|
10
11
|
// ============================================
|
|
11
12
|
export async function businessMenu() {
|
|
13
|
+
// RBAC: Require biz permissions
|
|
14
|
+
requireAnyPermission(["biz:tickets", "biz:orders", "biz:customers", "biz:*"]);
|
|
12
15
|
const menuItems = [
|
|
13
16
|
{ name: "🎫 Tickets (Zendesk)", value: "tickets" },
|
|
14
17
|
{ name: "👤 Customers (Billbee)", value: "customers" },
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { select, input, confirm } from "@inquirer/prompts";
|
|
7
7
|
import { getAuthHeaders } from "../config.js";
|
|
8
8
|
import { ensureConfig, pressEnterToContinue, truncate, formatDate } from "./utils.js";
|
|
9
|
+
import { requireAnyPermission } from "../../lib/permissions.js";
|
|
9
10
|
export async function infraMenu() {
|
|
10
11
|
const config = ensureConfig();
|
|
11
12
|
console.log("\n INFRASTRUCTURE");
|
|
@@ -209,6 +210,8 @@ async function showAlerts(config) {
|
|
|
209
210
|
}
|
|
210
211
|
}
|
|
211
212
|
async function restartService(config) {
|
|
213
|
+
// RBAC: Require infra update permission for destructive operations
|
|
214
|
+
requireAnyPermission(["infra:update", "infra:*"]);
|
|
212
215
|
try {
|
|
213
216
|
const serviceName = await input({
|
|
214
217
|
message: "Service name to restart:",
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { select, input, confirm } from "@inquirer/prompts";
|
|
7
7
|
import { pressEnterToContinue, truncate } from "./utils.js";
|
|
8
8
|
import { spawnSync } from "child_process";
|
|
9
|
+
import { requireAnyPermission } from "../../lib/permissions.js";
|
|
9
10
|
/**
|
|
10
11
|
* Safe command execution using spawnSync
|
|
11
12
|
*/
|
|
@@ -158,6 +159,8 @@ async function checkStatus() {
|
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
async function createPR() {
|
|
162
|
+
// RBAC: Require PR create permission
|
|
163
|
+
requireAnyPermission(["pr:create", "pr:*"]);
|
|
161
164
|
try {
|
|
162
165
|
const title = await input({
|
|
163
166
|
message: "PR title:",
|
|
@@ -196,6 +199,8 @@ async function createPR() {
|
|
|
196
199
|
}
|
|
197
200
|
}
|
|
198
201
|
async function mergePR() {
|
|
202
|
+
// RBAC: Require PR merge permission
|
|
203
|
+
requireAnyPermission(["pr:merge", "pr:*"]);
|
|
199
204
|
try {
|
|
200
205
|
const prNumber = await input({
|
|
201
206
|
message: "PR number to merge:",
|
|
@@ -2,6 +2,7 @@ import { select, input, confirm } from "@inquirer/prompts";
|
|
|
2
2
|
import { getAuthHeaders } from "../config.js";
|
|
3
3
|
import { ensureConfig, pressEnterToContinue, truncate } from "./utils.js";
|
|
4
4
|
import { resolveProject } from "../../lib/project-resolver.js";
|
|
5
|
+
import { requirePermission } from "../../lib/permissions.js";
|
|
5
6
|
export async function tasksMenu() {
|
|
6
7
|
const config = ensureConfig();
|
|
7
8
|
const menuItems = [
|
|
@@ -386,6 +387,8 @@ async function updateTask(config) {
|
|
|
386
387
|
}
|
|
387
388
|
}
|
|
388
389
|
async function startTask(config) {
|
|
390
|
+
// RBAC: Require task:start permission
|
|
391
|
+
requirePermission("task:start");
|
|
389
392
|
try {
|
|
390
393
|
const task = await selectTask(config, "Select task to start:");
|
|
391
394
|
if (!task)
|
|
@@ -408,6 +411,8 @@ async function startTask(config) {
|
|
|
408
411
|
}
|
|
409
412
|
}
|
|
410
413
|
async function markTaskDone(config) {
|
|
414
|
+
// RBAC: Require task:done permission
|
|
415
|
+
requirePermission("task:done");
|
|
411
416
|
try {
|
|
412
417
|
const task = await selectTask(config, "Select task to mark as done:");
|
|
413
418
|
if (!task)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { select, input, confirm } from "@inquirer/prompts";
|
|
2
2
|
import { getAuthHeaders } from "../config.js";
|
|
3
3
|
import { ensureConfig, pressEnterToContinue, truncate } from "./utils.js";
|
|
4
|
+
import { requireAnyPermission } from "../../lib/permissions.js";
|
|
4
5
|
export async function vmSessionsMenu() {
|
|
5
6
|
const config = ensureConfig();
|
|
6
7
|
const menuItems = [
|
|
@@ -127,6 +128,8 @@ async function viewVMSession(config) {
|
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
130
|
async function createVMSession(config) {
|
|
131
|
+
// RBAC: Require VM create permission
|
|
132
|
+
requireAnyPermission(["vm:create", "vm:*"]);
|
|
130
133
|
try {
|
|
131
134
|
const name = await input({
|
|
132
135
|
message: "Session name:",
|
|
@@ -179,6 +182,8 @@ async function createVMSession(config) {
|
|
|
179
182
|
}
|
|
180
183
|
}
|
|
181
184
|
async function startVM(config) {
|
|
185
|
+
// RBAC: Require VM manage permission
|
|
186
|
+
requireAnyPermission(["vm:manage", "vm:*"]);
|
|
182
187
|
try {
|
|
183
188
|
const session = await selectVMSession(config, "Select session to start:");
|
|
184
189
|
if (!session)
|
|
@@ -202,6 +207,8 @@ async function startVM(config) {
|
|
|
202
207
|
}
|
|
203
208
|
}
|
|
204
209
|
async function stopVM(config) {
|
|
210
|
+
// RBAC: Require VM manage permission
|
|
211
|
+
requireAnyPermission(["vm:manage", "vm:*"]);
|
|
205
212
|
try {
|
|
206
213
|
const session = await selectVMSession(config, "Select session to stop:");
|
|
207
214
|
if (!session)
|
|
@@ -288,6 +295,8 @@ async function approvePlan(config) {
|
|
|
288
295
|
}
|
|
289
296
|
}
|
|
290
297
|
async function deleteVMSession(config) {
|
|
298
|
+
// RBAC: Require VM manage permission
|
|
299
|
+
requireAnyPermission(["vm:manage", "vm:*"]);
|
|
291
300
|
try {
|
|
292
301
|
const session = await selectVMSession(config, "Select session to delete:");
|
|
293
302
|
if (!session)
|
|
@@ -2,20 +2,45 @@ import { getConfig } from "../../commands/config.js";
|
|
|
2
2
|
import { canAccessKnowledgeBase as checkKbAccess } from "../permissions-cache.js";
|
|
3
3
|
async function apiRequest(path, options = {}) {
|
|
4
4
|
const config = getConfig();
|
|
5
|
-
if (!config.apiUrl
|
|
6
|
-
throw new Error("API not configured. Run: husky config set api-url <url>
|
|
5
|
+
if (!config.apiUrl) {
|
|
6
|
+
throw new Error("API not configured. Run: husky config set api-url <url>");
|
|
7
|
+
}
|
|
8
|
+
// Prefer session token (JWT), fall back to API key for backwards compatibility
|
|
9
|
+
const headers = {
|
|
10
|
+
"Content-Type": "application/json",
|
|
11
|
+
};
|
|
12
|
+
if (config.sessionToken) {
|
|
13
|
+
// Check if session is expired
|
|
14
|
+
if (config.sessionExpiresAt) {
|
|
15
|
+
const expiresAt = new Date(config.sessionExpiresAt);
|
|
16
|
+
if (expiresAt < new Date()) {
|
|
17
|
+
throw new Error("Session expired. Run: husky auth login --agent <name>");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
headers["Authorization"] = `Bearer ${config.sessionToken}`;
|
|
21
|
+
}
|
|
22
|
+
else if (config.apiKey) {
|
|
23
|
+
// Legacy fallback - will be rejected by new API but kept for error message
|
|
24
|
+
headers["x-api-key"] = config.apiKey;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
throw new Error("Not authenticated. Run: husky auth login --agent <name>");
|
|
7
28
|
}
|
|
8
29
|
const url = new URL(`/api/brain${path}`, config.apiUrl);
|
|
9
30
|
const res = await fetch(url.toString(), {
|
|
10
31
|
method: options.method || "POST",
|
|
11
|
-
headers
|
|
12
|
-
"x-api-key": config.apiKey,
|
|
13
|
-
"Content-Type": "application/json",
|
|
14
|
-
},
|
|
32
|
+
headers,
|
|
15
33
|
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
16
34
|
});
|
|
17
35
|
if (!res.ok) {
|
|
18
36
|
const error = await res.json().catch(() => ({ error: res.statusText }));
|
|
37
|
+
if (res.status === 401) {
|
|
38
|
+
// Check if it's the new API rejecting API key auth
|
|
39
|
+
if (error.upgrade) {
|
|
40
|
+
throw new Error("Session required. Run: husky auth login --agent <name>");
|
|
41
|
+
}
|
|
42
|
+
throw new Error(error.message || "Authentication failed");
|
|
43
|
+
}
|
|
19
44
|
if (res.status === 403) {
|
|
20
45
|
throw new Error(`Access denied: ${error.error || 'Permission denied to this resource'}`);
|
|
21
46
|
}
|
|
@@ -191,6 +216,13 @@ export class ApiBrain {
|
|
|
191
216
|
}
|
|
192
217
|
export function shouldUseApi() {
|
|
193
218
|
const config = getConfig();
|
|
194
|
-
|
|
219
|
+
// Use API if:
|
|
220
|
+
// 1. No Qdrant URL configured (can't use direct access)
|
|
221
|
+
// 2. Or session token is available (preferred auth method)
|
|
222
|
+
if (!config.qdrantUrl) {
|
|
223
|
+
return Boolean(config.apiUrl);
|
|
224
|
+
}
|
|
225
|
+
// If Qdrant is configured, still prefer API if session token exists
|
|
226
|
+
return Boolean(config.apiUrl && config.sessionToken);
|
|
195
227
|
}
|
|
196
228
|
export default ApiBrain;
|