@simonfestl/husky-cli 1.25.3 ā 1.26.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 +9 -47
- package/dist/commands/business.d.ts +2 -0
- package/dist/commands/business.js +865 -0
- package/dist/commands/config.js +33 -45
- 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/commands/task.js +90 -293
- package/dist/index.js +2 -0
- package/dist/lib/worker.d.ts +3 -3
- package/dist/lib/worker.js +37 -39
- package/package.json +3 -1
package/dist/commands/config.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from "f
|
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import { ErrorHelpers, errorWithHint, ExplainTopic } from "../lib/error-hints.js";
|
|
6
|
+
import { getApiClient } from "../lib/api-client.js";
|
|
6
7
|
// Valid agent roles - used for runtime validation
|
|
7
8
|
const VALID_ROLES = ["admin", "supervisor", "worker", "reviewer", "e2e_agent", "pr_agent", "support", "devops", "purchasing", "ops"];
|
|
8
9
|
const CONFIG_DIR = join(homedir(), ".husky");
|
|
@@ -118,19 +119,14 @@ export async function fetchAndCacheRole() {
|
|
|
118
119
|
return {};
|
|
119
120
|
}
|
|
120
121
|
try {
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
config.permissions = data.permissions;
|
|
130
|
-
config.roleLastChecked = new Date().toISOString();
|
|
131
|
-
saveConfig(config);
|
|
132
|
-
return { role: data.role, permissions: data.permissions };
|
|
133
|
-
}
|
|
122
|
+
const api = getApiClient();
|
|
123
|
+
const data = await api.get("/api/auth/whoami");
|
|
124
|
+
// Update config cache
|
|
125
|
+
config.role = data.role;
|
|
126
|
+
config.permissions = data.permissions;
|
|
127
|
+
config.roleLastChecked = new Date().toISOString();
|
|
128
|
+
saveConfig(config);
|
|
129
|
+
return { role: data.role, permissions: data.permissions };
|
|
134
130
|
}
|
|
135
131
|
catch {
|
|
136
132
|
// Ignore fetch errors, return cached or empty
|
|
@@ -458,29 +454,9 @@ configCommand
|
|
|
458
454
|
}
|
|
459
455
|
console.log("Testing API connection...");
|
|
460
456
|
try {
|
|
457
|
+
const api = getApiClient();
|
|
461
458
|
// First test basic connectivity with /api/tasks
|
|
462
|
-
|
|
463
|
-
const apiKey = config.apiKey; // We already checked it's defined above
|
|
464
|
-
const tasksRes = await fetch(tasksUrl.toString(), {
|
|
465
|
-
headers: { "x-api-key": apiKey },
|
|
466
|
-
});
|
|
467
|
-
if (!tasksRes.ok) {
|
|
468
|
-
if (tasksRes.status === 401) {
|
|
469
|
-
console.error(`API connection failed: Unauthorized (HTTP 401)`);
|
|
470
|
-
console.error(" Check your API key with: husky config set api-key <key>");
|
|
471
|
-
console.error("\nš” For configuration help: husky explain config");
|
|
472
|
-
process.exit(1);
|
|
473
|
-
}
|
|
474
|
-
else if (tasksRes.status === 403) {
|
|
475
|
-
console.error(`API connection failed: Forbidden (HTTP 403)`);
|
|
476
|
-
console.error(" Your API key may not have the required permissions");
|
|
477
|
-
console.error("\nš” For configuration help: husky explain config");
|
|
478
|
-
process.exit(1);
|
|
479
|
-
}
|
|
480
|
-
else {
|
|
481
|
-
errorWithHint(`API connection failed: HTTP ${tasksRes.status}`, ExplainTopic.CONFIG, "Check your API configuration");
|
|
482
|
-
}
|
|
483
|
-
}
|
|
459
|
+
await api.get("/api/tasks");
|
|
484
460
|
console.log(`API connection successful (API URL: ${config.apiUrl})`);
|
|
485
461
|
// Check if there's an active session
|
|
486
462
|
const hasActiveSession = isSessionActive();
|
|
@@ -494,12 +470,8 @@ configCommand
|
|
|
494
470
|
}
|
|
495
471
|
else {
|
|
496
472
|
// No session - fetch API key role from whoami
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
headers: { "x-api-key": apiKey },
|
|
500
|
-
});
|
|
501
|
-
if (whoamiRes.ok) {
|
|
502
|
-
const data = await whoamiRes.json();
|
|
473
|
+
try {
|
|
474
|
+
const data = await api.get("/api/auth/whoami");
|
|
503
475
|
// Cache the role/permissions (only if no session)
|
|
504
476
|
const updatedConfig = getConfig();
|
|
505
477
|
updatedConfig.role = data.role;
|
|
@@ -515,17 +487,33 @@ configCommand
|
|
|
515
487
|
console.log(` Agent ID: ${data.agentId}`);
|
|
516
488
|
}
|
|
517
489
|
}
|
|
490
|
+
catch {
|
|
491
|
+
// whoami failed, but tasks worked - connection is fine
|
|
492
|
+
}
|
|
518
493
|
}
|
|
519
494
|
}
|
|
520
495
|
catch (error) {
|
|
521
|
-
|
|
496
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
497
|
+
if (errorMsg.includes("401")) {
|
|
498
|
+
console.error(`API connection failed: Unauthorized (HTTP 401)`);
|
|
499
|
+
console.error(" Check your API key with: husky config set api-key <key>");
|
|
500
|
+
console.error("\n For configuration help: husky explain config");
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
else if (errorMsg.includes("403")) {
|
|
504
|
+
console.error(`API connection failed: Forbidden (HTTP 403)`);
|
|
505
|
+
console.error(" Your API key may not have the required permissions");
|
|
506
|
+
console.error("\n For configuration help: husky explain config");
|
|
507
|
+
process.exit(1);
|
|
508
|
+
}
|
|
509
|
+
else if (error instanceof TypeError && errorMsg.includes("fetch")) {
|
|
522
510
|
console.error(`API connection failed: Could not connect to ${config.apiUrl}`);
|
|
523
511
|
console.error(" Check your API URL with: husky config set api-url <url>");
|
|
524
|
-
console.error("\n
|
|
512
|
+
console.error("\n For configuration help: husky explain config");
|
|
525
513
|
}
|
|
526
514
|
else {
|
|
527
|
-
console.error(`API connection failed: ${
|
|
528
|
-
console.error("\n
|
|
515
|
+
console.error(`API connection failed: ${errorMsg}`);
|
|
516
|
+
console.error("\n For configuration help: husky explain config");
|
|
529
517
|
}
|
|
530
518
|
process.exit(1);
|
|
531
519
|
}
|
|
@@ -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)
|