@simonfestl/husky-cli 1.0.0 → 1.3.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.
@@ -1,5 +1,13 @@
1
1
  import { Command } from "commander";
2
2
  import { getConfig } from "./config.js";
3
+ import { exec } from "child_process";
4
+ import { promisify } from "util";
5
+ const execAsync = promisify(exec);
6
+ // Helper to get the Husky API URL (for Google Chat integration)
7
+ function getHuskyApiUrl() {
8
+ const config = getConfig();
9
+ return config.apiUrl || null;
10
+ }
3
11
  export const chatCommand = new Command("chat")
4
12
  .description("Communicate with the dashboard chat");
5
13
  chatCommand
@@ -160,3 +168,502 @@ chatCommand
160
168
  process.exit(1);
161
169
  }
162
170
  });
171
+ chatCommand
172
+ .command("review <question>")
173
+ .description("Request human review via Google Chat")
174
+ .option("--task-id <id>", "Link to a specific task")
175
+ .option("--context <text>", "Additional context for the reviewer")
176
+ .option("--priority <level>", "Priority: low, normal, urgent", "normal")
177
+ .option("--wait", "Wait for human response (polling)")
178
+ .option("--timeout <seconds>", "Timeout for waiting (default: 300)", "300")
179
+ .option("--json", "Output as JSON")
180
+ .action(async (question, options) => {
181
+ const config = getConfig();
182
+ const huskyApiUrl = getHuskyApiUrl();
183
+ if (!huskyApiUrl) {
184
+ console.error("Error: API URL not configured. Set husky-api-url or api-url.");
185
+ process.exit(1);
186
+ }
187
+ const workerId = process.env.HUSKY_WORKER_ID || `agent-${process.pid}`;
188
+ try {
189
+ const res = await fetch(`${huskyApiUrl}/api/google-chat/request-review`, {
190
+ method: "POST",
191
+ headers: {
192
+ "Content-Type": "application/json",
193
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
194
+ },
195
+ body: JSON.stringify({
196
+ agentId: workerId,
197
+ taskId: options.taskId,
198
+ question,
199
+ context: options.context,
200
+ priority: options.priority,
201
+ }),
202
+ });
203
+ if (!res.ok) {
204
+ const error = await res.text();
205
+ throw new Error(`API error: ${res.status} - ${error}`);
206
+ }
207
+ const data = await res.json();
208
+ if (!options.wait) {
209
+ if (options.json) {
210
+ console.log(JSON.stringify(data, null, 2));
211
+ }
212
+ else {
213
+ console.log(`Review requested (ID: ${data.id})`);
214
+ console.log(`Status: ${data.status}`);
215
+ console.log(`\nTo check status: husky chat review-status ${data.id}`);
216
+ console.log(`To wait for response: husky chat review-wait ${data.id}`);
217
+ }
218
+ return;
219
+ }
220
+ console.log(`Review requested (ID: ${data.id}). Waiting for human response...`);
221
+ const timeoutMs = parseInt(options.timeout, 10) * 1000;
222
+ const startTime = Date.now();
223
+ const pollInterval = 5000;
224
+ while (Date.now() - startTime < timeoutMs) {
225
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
226
+ const pollRes = await fetch(`${huskyApiUrl}/api/google-chat/review/${data.id}/poll`, {
227
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
228
+ });
229
+ if (!pollRes.ok)
230
+ continue;
231
+ const pollData = await pollRes.json();
232
+ if (pollData.status === "answered" && pollData.response) {
233
+ if (options.json) {
234
+ console.log(JSON.stringify(pollData, null, 2));
235
+ }
236
+ else {
237
+ console.log(`\nHuman response received from ${pollData.respondedBy || "unknown"}:`);
238
+ console.log(`\n${pollData.response}`);
239
+ }
240
+ return;
241
+ }
242
+ process.stdout.write(".");
243
+ }
244
+ console.error("\nTimeout waiting for human response.");
245
+ process.exit(1);
246
+ }
247
+ catch (error) {
248
+ console.error("Error requesting review:", error);
249
+ process.exit(1);
250
+ }
251
+ });
252
+ chatCommand
253
+ .command("review-status <reviewId>")
254
+ .description("Check status of a human review request")
255
+ .option("--json", "Output as JSON")
256
+ .action(async (reviewId, options) => {
257
+ const config = getConfig();
258
+ const huskyApiUrl = getHuskyApiUrl();
259
+ if (!huskyApiUrl) {
260
+ console.error("Error: API URL not configured. Set husky-api-url or api-url.");
261
+ process.exit(1);
262
+ }
263
+ try {
264
+ const res = await fetch(`${huskyApiUrl}/api/google-chat/review/${reviewId}`, {
265
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
266
+ });
267
+ if (!res.ok) {
268
+ if (res.status === 404) {
269
+ console.error("Review not found.");
270
+ process.exit(1);
271
+ }
272
+ throw new Error(`API error: ${res.status}`);
273
+ }
274
+ const data = await res.json();
275
+ if (options.json) {
276
+ console.log(JSON.stringify(data, null, 2));
277
+ return;
278
+ }
279
+ console.log(`\nReview: ${data.id}`);
280
+ console.log(`Status: ${data.status}`);
281
+ console.log(`Question: ${data.question}`);
282
+ if (data.response) {
283
+ console.log(`\nResponse from ${data.respondedBy || "unknown"}:`);
284
+ console.log(data.response);
285
+ }
286
+ }
287
+ catch (error) {
288
+ console.error("Error checking review status:", error);
289
+ process.exit(1);
290
+ }
291
+ });
292
+ // ============================================
293
+ // SUPERVISOR INBOX COMMANDS (Google Chat <-> Supervisor)
294
+ // ============================================
295
+ chatCommand
296
+ .command("inbox")
297
+ .description("Get messages from Google Chat (supervisor inbox)")
298
+ .option("--unread", "Only show unread messages")
299
+ .option("--limit <n>", "Number of messages", "10")
300
+ .option("--json", "Output as JSON")
301
+ .action(async (options) => {
302
+ const config = getConfig();
303
+ const huskyApiUrl = getHuskyApiUrl();
304
+ if (!huskyApiUrl) {
305
+ console.error("Error: API URL not configured. Set husky-api-url or api-url.");
306
+ process.exit(1);
307
+ }
308
+ try {
309
+ const params = new URLSearchParams();
310
+ if (options.unread)
311
+ params.set("unread", "true");
312
+ if (options.limit)
313
+ params.set("limit", options.limit);
314
+ const res = await fetch(`${huskyApiUrl}/api/google-chat/inbox?${params}`, {
315
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
316
+ });
317
+ if (!res.ok) {
318
+ throw new Error(`API error: ${res.status}`);
319
+ }
320
+ const data = await res.json();
321
+ if (options.json) {
322
+ console.log(JSON.stringify(data, null, 2));
323
+ return;
324
+ }
325
+ if (!data.messages || data.messages.length === 0) {
326
+ console.log(options.unread ? "šŸ“­ No unread messages." : "šŸ“­ No messages in inbox.");
327
+ return;
328
+ }
329
+ console.log("\n šŸ“¬ Supervisor Inbox");
330
+ console.log(" " + "─".repeat(60));
331
+ for (const msg of data.messages) {
332
+ const time = new Date(msg.createdAt).toLocaleString();
333
+ const readIcon = msg.read ? "āœ“" : "ā—";
334
+ console.log(` ${readIcon} [${msg.id.slice(0, 8)}] ${msg.senderName} (${time})`);
335
+ console.log(` "${msg.text}"`);
336
+ console.log("");
337
+ }
338
+ }
339
+ catch (error) {
340
+ console.error("Error fetching inbox:", error);
341
+ process.exit(1);
342
+ }
343
+ });
344
+ chatCommand
345
+ .command("reply-chat <message>")
346
+ .description("Send a message to Google Chat (supervisor -> human)")
347
+ .option("--space <name>", "Target space (e.g., spaces/ABC123)")
348
+ .option("--thread <name>", "Reply in thread (e.g., spaces/ABC123/threads/XYZ)")
349
+ .action(async (message, options) => {
350
+ const config = getConfig();
351
+ const huskyApiUrl = getHuskyApiUrl();
352
+ if (!huskyApiUrl) {
353
+ console.error("Error: API URL not configured. Set husky-api-url or api-url.");
354
+ process.exit(1);
355
+ }
356
+ try {
357
+ const res = await fetch(`${huskyApiUrl}/api/google-chat/send`, {
358
+ method: "POST",
359
+ headers: {
360
+ "Content-Type": "application/json",
361
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
362
+ },
363
+ body: JSON.stringify({
364
+ text: message,
365
+ spaceName: options.space,
366
+ threadName: options.thread,
367
+ }),
368
+ });
369
+ if (!res.ok) {
370
+ const error = await res.text();
371
+ throw new Error(`API error: ${res.status} - ${error}`);
372
+ }
373
+ console.log("āœ… Message sent to Google Chat.");
374
+ }
375
+ catch (error) {
376
+ console.error("Error sending message:", error);
377
+ process.exit(1);
378
+ }
379
+ });
380
+ chatCommand
381
+ .command("reply-to <messageId> <response>")
382
+ .description("Reply to a specific inbox message in its thread (supports both GitHub and Google Chat)")
383
+ .action(async (messageId, response) => {
384
+ const config = getConfig();
385
+ const huskyApiUrl = getHuskyApiUrl();
386
+ if (!huskyApiUrl) {
387
+ console.error("Error: API URL not configured. Set husky-api-url or api-url.");
388
+ process.exit(1);
389
+ }
390
+ try {
391
+ // Fetch inbox to find the message
392
+ const inboxRes = await fetch(`${huskyApiUrl}/api/google-chat/inbox?limit=50`, {
393
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
394
+ });
395
+ if (!inboxRes.ok) {
396
+ throw new Error(`Failed to fetch inbox: ${inboxRes.status}`);
397
+ }
398
+ const data = await inboxRes.json();
399
+ // Require exact match or at least 8 characters for prefix matching to avoid misdirected replies
400
+ const msg = data.messages.find(m => m.id === messageId || (messageId.length >= 8 && m.id.startsWith(messageId)));
401
+ if (!msg) {
402
+ console.error(`Message ${messageId} not found in inbox.`);
403
+ if (messageId.length < 8) {
404
+ console.error("Hint: Provide at least 8 characters of the message ID for prefix matching.");
405
+ }
406
+ process.exit(1);
407
+ }
408
+ // Check if it's a GitHub message
409
+ const isGitHub = msg.spaceName?.startsWith("github:");
410
+ if (isGitHub) {
411
+ // Use GitHub reply endpoint
412
+ const sendRes = await fetch(`${huskyApiUrl}/api/github/inbox/${msg.id}/reply`, {
413
+ method: "POST",
414
+ headers: {
415
+ "Content-Type": "application/json",
416
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
417
+ },
418
+ body: JSON.stringify({ text: response }),
419
+ });
420
+ if (!sendRes.ok) {
421
+ const error = await sendRes.text();
422
+ throw new Error(`API error: ${sendRes.status} - ${error}`);
423
+ }
424
+ console.log("āœ… Reply posted to GitHub issue.");
425
+ }
426
+ else {
427
+ // Use Google Chat reply
428
+ const sendRes = await fetch(`${huskyApiUrl}/api/google-chat/send`, {
429
+ method: "POST",
430
+ headers: {
431
+ "Content-Type": "application/json",
432
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
433
+ },
434
+ body: JSON.stringify({
435
+ text: response,
436
+ spaceName: msg.spaceName,
437
+ threadName: msg.threadName,
438
+ }),
439
+ });
440
+ if (!sendRes.ok) {
441
+ const error = await sendRes.text();
442
+ throw new Error(`API error: ${sendRes.status} - ${error}`);
443
+ }
444
+ // Mark as read
445
+ await fetch(`${huskyApiUrl}/api/google-chat/inbox/${msg.id}/read`, {
446
+ method: "POST",
447
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
448
+ });
449
+ console.log("āœ… Reply sent to Google Chat and message marked as read.");
450
+ }
451
+ }
452
+ catch (error) {
453
+ console.error("Error replying:", error);
454
+ process.exit(1);
455
+ }
456
+ });
457
+ chatCommand
458
+ .command("mark-read <messageId>")
459
+ .description("Mark a message as read")
460
+ .action(async (messageId) => {
461
+ const config = getConfig();
462
+ const huskyApiUrl = getHuskyApiUrl();
463
+ if (!huskyApiUrl) {
464
+ console.error("Error: API URL not configured. Set husky-api-url or api-url.");
465
+ process.exit(1);
466
+ }
467
+ try {
468
+ const res = await fetch(`${huskyApiUrl}/api/google-chat/inbox/${messageId}/read`, {
469
+ method: "POST",
470
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
471
+ });
472
+ if (!res.ok) {
473
+ throw new Error(`API error: ${res.status}`);
474
+ }
475
+ console.log("āœ… Message marked as read.");
476
+ }
477
+ catch (error) {
478
+ console.error("Error marking message as read:", error);
479
+ process.exit(1);
480
+ }
481
+ });
482
+ chatCommand
483
+ .command("watch")
484
+ .description("Watch for new messages (blocking, for supervisor agent)")
485
+ .option("--poll-interval <seconds>", "Poll interval in seconds", "10")
486
+ .action(async (options) => {
487
+ const config = getConfig();
488
+ const huskyApiUrl = getHuskyApiUrl();
489
+ if (!huskyApiUrl) {
490
+ console.error("Error: API URL not configured. Set husky-api-url or api-url.");
491
+ process.exit(1);
492
+ }
493
+ console.log("šŸ‘€ Watching for new messages... (Ctrl+C to stop)");
494
+ const pollInterval = parseInt(options.pollInterval, 10) * 1000;
495
+ let lastSeenId = "";
496
+ const poll = async () => {
497
+ try {
498
+ const res = await fetch(`${huskyApiUrl}/api/google-chat/inbox?unread=true&limit=5`, {
499
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
500
+ });
501
+ if (!res.ok)
502
+ return;
503
+ const data = await res.json();
504
+ for (const msg of data.messages || []) {
505
+ if (msg.id !== lastSeenId) {
506
+ lastSeenId = msg.id;
507
+ const time = new Date(msg.createdAt).toLocaleTimeString();
508
+ console.log(`\nšŸ“Ø [${time}] ${msg.senderName}: ${msg.text}`);
509
+ }
510
+ }
511
+ }
512
+ catch { }
513
+ };
514
+ await poll();
515
+ setInterval(poll, pollInterval);
516
+ process.on("SIGINT", () => {
517
+ console.log("\nšŸ‘‹ Stopped watching.");
518
+ process.exit(0);
519
+ });
520
+ await new Promise(() => { });
521
+ });
522
+ chatCommand
523
+ .command("watch-inject")
524
+ .description("Watch for messages and inject them into a tmux session")
525
+ .option("--poll-interval <seconds>", "Poll interval in seconds", "2")
526
+ .option("--tmux-session <name>", "Target tmux session name", "supervisor")
527
+ .option("--tmux-window <name>", "Target tmux window name or index (default: 0)", "0")
528
+ .option("--hint", "Show reply hint after messages (default: true)", true)
529
+ .option("--no-hint", "Hide reply hint")
530
+ .action(async (options) => {
531
+ const config = getConfig();
532
+ const huskyApiUrl = getHuskyApiUrl();
533
+ if (!huskyApiUrl) {
534
+ console.error("Error: API URL not configured. Set husky-api-url or api-url.");
535
+ process.exit(1);
536
+ }
537
+ const tmuxSession = options.tmuxSession;
538
+ const tmuxWindow = options.tmuxWindow;
539
+ const tmuxTarget = `${tmuxSession}:${tmuxWindow}`;
540
+ const pollInterval = parseInt(options.pollInterval, 10) * 1000;
541
+ const processedIds = new Set();
542
+ console.log(`šŸ“” Watching for messages (Google Chat & GitHub)...`);
543
+ console.log(` Target: ${tmuxTarget}`);
544
+ console.log(` Poll interval: ${options.pollInterval}s`);
545
+ console.log(` Press Ctrl+C to stop\n`);
546
+ const injectToTmux = async (text, senderName, spaceName) => {
547
+ // Detect platform from spaceName
548
+ const isGitHub = spaceName?.startsWith("github:");
549
+ const platform = isGitHub ? "GitHub" : "Google Chat";
550
+ let formattedMessage = `[${platform}] ${senderName}: ${text}`;
551
+ if (options.hint) {
552
+ if (isGitHub) {
553
+ // Extract repo info from spaceName (format: github:owner/repo)
554
+ const repoInfo = spaceName?.replace("github:", "") || "";
555
+ formattedMessage += `\nšŸ’” Reply on GitHub: ${repoInfo}`;
556
+ }
557
+ else {
558
+ formattedMessage += `\nšŸ’” Tip: Use \`husky chat reply-chat "your response"\` to reply`;
559
+ }
560
+ }
561
+ const escapedMessage = formattedMessage
562
+ .replace(/\\/g, "\\\\")
563
+ .replace(/"/g, '\\"')
564
+ .replace(/\$/g, "\\$")
565
+ .replace(/`/g, "\\`")
566
+ .replace(/'/g, "'\\''");
567
+ try {
568
+ await execAsync(`tmux send-keys -t "${tmuxTarget}" "${escapedMessage}" Enter`, { timeout: 5000 });
569
+ return true;
570
+ }
571
+ catch (error) {
572
+ const err = error;
573
+ console.error(` āŒ Failed to inject: ${err.message}`);
574
+ return false;
575
+ }
576
+ };
577
+ const markAsRead = async (messageId) => {
578
+ try {
579
+ await fetch(`${huskyApiUrl}/api/google-chat/inbox/${messageId}/read`, {
580
+ method: "POST",
581
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
582
+ });
583
+ }
584
+ catch { }
585
+ };
586
+ const poll = async () => {
587
+ try {
588
+ const res = await fetch(`${huskyApiUrl}/api/google-chat/inbox?unread=true&limit=10`, {
589
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
590
+ });
591
+ if (!res.ok)
592
+ return;
593
+ const data = await res.json();
594
+ const messages = (data.messages || []).reverse();
595
+ for (const msg of messages) {
596
+ if (processedIds.has(msg.id))
597
+ continue;
598
+ processedIds.add(msg.id);
599
+ const time = new Date(msg.createdAt).toLocaleTimeString();
600
+ const platform = msg.spaceName?.startsWith("github:") ? "GitHub" : "Google Chat";
601
+ console.log(`šŸ“Ø [${time}] Injecting ${platform} message from ${msg.senderName}`);
602
+ const success = await injectToTmux(msg.text, msg.senderName, msg.spaceName);
603
+ if (success) {
604
+ await markAsRead(msg.id);
605
+ console.log(` āœ“ Injected and marked as read`);
606
+ }
607
+ }
608
+ }
609
+ catch (error) {
610
+ const err = error;
611
+ console.error(`Poll error: ${err.message}`);
612
+ }
613
+ };
614
+ await poll();
615
+ setInterval(poll, pollInterval);
616
+ process.on("SIGINT", () => {
617
+ console.log("\nšŸ‘‹ Stopped watching.");
618
+ process.exit(0);
619
+ });
620
+ await new Promise(() => { });
621
+ });
622
+ // ============================================
623
+ // REVIEW COMMANDS (kept for backwards compatibility)
624
+ // ============================================
625
+ chatCommand
626
+ .command("review-wait <reviewId>")
627
+ .description("Wait for a human review response")
628
+ .option("--timeout <seconds>", "Timeout in seconds (default: 300)", "300")
629
+ .option("--json", "Output as JSON")
630
+ .action(async (reviewId, options) => {
631
+ const config = getConfig();
632
+ const huskyApiUrl = getHuskyApiUrl();
633
+ if (!huskyApiUrl) {
634
+ console.error("Error: API URL not configured. Set husky-api-url or api-url.");
635
+ process.exit(1);
636
+ }
637
+ console.log(`Waiting for response to review ${reviewId}...`);
638
+ const timeoutMs = parseInt(options.timeout, 10) * 1000;
639
+ const startTime = Date.now();
640
+ const pollInterval = 5000;
641
+ try {
642
+ while (Date.now() - startTime < timeoutMs) {
643
+ const res = await fetch(`${huskyApiUrl}/api/google-chat/review/${reviewId}/poll`, {
644
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
645
+ });
646
+ if (res.ok) {
647
+ const data = await res.json();
648
+ if (data.status === "answered" && data.response) {
649
+ if (options.json) {
650
+ console.log(JSON.stringify(data, null, 2));
651
+ }
652
+ else {
653
+ console.log(`\nHuman response received from ${data.respondedBy || "unknown"}:`);
654
+ console.log(`\n${data.response}`);
655
+ }
656
+ return;
657
+ }
658
+ }
659
+ process.stdout.write(".");
660
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
661
+ }
662
+ console.error("\nTimeout waiting for human response.");
663
+ process.exit(1);
664
+ }
665
+ catch (error) {
666
+ console.error("Error waiting for review:", error);
667
+ process.exit(1);
668
+ }
669
+ });
@@ -135,7 +135,7 @@ ${subcommandCases}
135
135
  return 0
136
136
  ;;
137
137
  --agent)
138
- COMPREPLY=( $(compgen -W "claude-code gemini-cli aider custom" -- \${cur}) )
138
+ COMPREPLY=( $(compgen -W "claude-code gemini-cli custom" -- \${cur}) )
139
139
  return 0
140
140
  ;;
141
141
  --type)
@@ -268,7 +268,7 @@ complete -c husky -l project -d "Project ID" -r
268
268
  complete -c husky -l status -d "Filter by status" -r -a "backlog in_progress review done pending running completed failed"
269
269
  complete -c husky -l priority -d "Priority level" -r -a "low medium high urgent must should could wont"
270
270
  complete -c husky -l assignee -d "Assignee type" -r -a "human llm unassigned"
271
- complete -c husky -l agent -d "Agent type" -r -a "claude-code gemini-cli aider custom"
271
+ complete -c husky -l agent -d "Agent type" -r -a "claude-code gemini-cli custom"
272
272
  complete -c husky -l type -d "Type filter" -r -a "global project architecture patterns decisions learnings"
273
273
  complete -c husky -l value-stream -d "Value stream" -r -a "order_to_delivery procure_to_pay returns_management product_lifecycle customer_service marketing_sales finance_accounting hr_operations it_operations general"
274
274
  complete -c husky -l action -d "Action type" -r -a "manual semi_automated fully_automated"
@@ -1,9 +1,13 @@
1
1
  import { Command } from "commander";
2
+ type AgentRole = "supervisor" | "worker" | "reviewer" | "e2e_agent" | "pr_agent" | "support";
2
3
  interface Config {
3
4
  apiUrl?: string;
4
5
  apiKey?: string;
5
6
  workerId?: string;
6
7
  workerName?: string;
8
+ role?: AgentRole;
9
+ permissions?: string[];
10
+ roleLastChecked?: string;
7
11
  billbeeApiKey?: string;
8
12
  billbeeUsername?: string;
9
13
  billbeePassword?: string;
@@ -17,8 +21,31 @@ interface Config {
17
21
  qdrantApiKey?: string;
18
22
  gcpProjectId?: string;
19
23
  gcpLocation?: string;
24
+ gotessToken?: string;
25
+ gotessBookId?: string;
20
26
  }
21
27
  export declare function getConfig(): Config;
28
+ /**
29
+ * Fetch role and permissions from /api/auth/whoami
30
+ * Caches the result in config for 1 hour
31
+ */
32
+ export declare function fetchAndCacheRole(): Promise<{
33
+ role?: AgentRole;
34
+ permissions?: string[];
35
+ }>;
36
+ /**
37
+ * Check if current config has a specific permission
38
+ */
39
+ export declare function hasPermission(permission: string): boolean;
40
+ /**
41
+ * Get current role from config (may be undefined if not fetched)
42
+ */
43
+ export declare function getRole(): AgentRole | undefined;
44
+ /**
45
+ * Clear the role cache to force a refresh on next fetchAndCacheRole call
46
+ */
47
+ export declare function clearRoleCache(): void;
22
48
  export declare function setConfig(key: "apiUrl" | "apiKey" | "workerId" | "workerName", value: string): void;
49
+ export declare function setGotessConfig(token: string, bookId: string): void;
23
50
  export declare const configCommand: Command;
24
51
  export {};