@simonfestl/husky-cli 1.38.4 → 1.38.5

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,16 +1,21 @@
1
1
  import { Command } from "commander";
2
- import { getConfig } from "./config.js";
2
+ import { getConfig, getAuthHeaders } from "./config.js";
3
3
  async function apiCall(path, options = {}) {
4
4
  const config = getConfig();
5
- if (!config.apiUrl || !config.apiKey) {
6
- console.error("Error: API URL and key required. Run: husky config test");
5
+ if (!config.apiUrl) {
6
+ console.error("Error: API URL is required. Run: husky config set api-url <url>");
7
+ process.exit(1);
8
+ }
9
+ const authHeaders = getAuthHeaders();
10
+ if (!authHeaders.Authorization) {
11
+ console.error("Error: Active session required. Run: husky auth login --agent <name>");
7
12
  process.exit(1);
8
13
  }
9
14
  const res = await fetch(`${config.apiUrl}${path}`, {
10
15
  ...options,
11
16
  headers: {
12
17
  "Content-Type": "application/json",
13
- "x-api-key": config.apiKey,
18
+ ...authHeaders,
14
19
  ...options.headers,
15
20
  },
16
21
  });
@@ -1,6 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import { GotessClient } from "../../lib/biz/gotess.js";
3
- import { getConfig, setGotessConfig } from "../config.js";
3
+ import { getConfig, getAuthHeaders, setGotessConfig } from "../config.js";
4
4
  import * as readline from "readline";
5
5
  function prompt(question) {
6
6
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -11,14 +11,16 @@ function prompt(question) {
11
11
  });
12
12
  });
13
13
  }
14
- async function pollSmsCode(apiUrl, afterTimestamp, maxRetries = 3) {
14
+ async function pollSmsCode(apiUrl, afterTimestamp, authHeaders, maxRetries = 3) {
15
15
  for (let retry = 0; retry < maxRetries; retry++) {
16
16
  if (retry > 0) {
17
17
  console.log(`\n Retry ${retry}/${maxRetries - 1}...`);
18
18
  }
19
19
  await new Promise(r => setTimeout(r, 60000));
20
20
  try {
21
- const res = await fetch(`${apiUrl}/api/webhooks/sms/latest?after=${afterTimestamp}`);
21
+ const res = await fetch(`${apiUrl}/api/webhooks/sms/latest?after=${afterTimestamp}`, {
22
+ headers: authHeaders,
23
+ });
22
24
  const data = await res.json();
23
25
  if (data.code) {
24
26
  return data.code;
@@ -56,7 +58,11 @@ gotessCommand
56
58
  if (!apiUrl) {
57
59
  throw new Error("API URL not configured. Run: husky config set api-url <url>");
58
60
  }
59
- const code = await pollSmsCode(apiUrl, smsRequestTime);
61
+ const authHeaders = getAuthHeaders();
62
+ if (Object.keys(authHeaders).length === 0) {
63
+ throw new Error("Not authenticated. Run: husky auth login --agent <name>");
64
+ }
65
+ const code = await pollSmsCode(apiUrl, smsRequestTime, authHeaders);
60
66
  if (!code) {
61
67
  console.error("\n āœ— Timeout waiting for SMS code");
62
68
  process.exit(1);
@@ -1,8 +1,8 @@
1
1
  import { Command } from "commander";
2
- import { getConfig } from "./config.js";
3
- import { exec } from "child_process";
2
+ import { getAuthHeaders, getConfig } from "./config.js";
3
+ import { execFile } from "child_process";
4
4
  import { promisify } from "util";
5
- const execAsync = promisify(exec);
5
+ const execFileAsync = promisify(execFile);
6
6
  // Helper to get the Husky API URL (for Google Chat integration)
7
7
  function getHuskyApiUrl() {
8
8
  const config = getConfig();
@@ -22,7 +22,7 @@ chatCommand
22
22
  }
23
23
  try {
24
24
  const res = await fetch(`${config.apiUrl}/api/chat/pending`, {
25
- headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
25
+ headers: getAuthHeaders(),
26
26
  });
27
27
  if (!res.ok) {
28
28
  throw new Error(`API error: ${res.status}`);
@@ -66,7 +66,7 @@ chatCommand
66
66
  }
67
67
  try {
68
68
  const res = await fetch(`${config.apiUrl}/api/chat?limit=${options.limit}`, {
69
- headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
69
+ headers: getAuthHeaders(),
70
70
  });
71
71
  if (!res.ok) {
72
72
  throw new Error(`API error: ${res.status}`);
@@ -115,7 +115,7 @@ chatCommand
115
115
  method: "POST",
116
116
  headers: {
117
117
  "Content-Type": "application/json",
118
- ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
118
+ ...getAuthHeaders(),
119
119
  },
120
120
  body: JSON.stringify({
121
121
  text: message,
@@ -134,7 +134,7 @@ chatCommand
134
134
  method: "POST",
135
135
  headers: {
136
136
  "Content-Type": "application/json",
137
- ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
137
+ ...getAuthHeaders(),
138
138
  },
139
139
  body: JSON.stringify({
140
140
  content: message,
@@ -167,7 +167,7 @@ chatCommand
167
167
  method: "POST",
168
168
  headers: {
169
169
  "Content-Type": "application/json",
170
- ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
170
+ ...getAuthHeaders(),
171
171
  },
172
172
  body: JSON.stringify({
173
173
  content: response,
@@ -181,7 +181,7 @@ chatCommand
181
181
  method: "PATCH",
182
182
  headers: {
183
183
  "Content-Type": "application/json",
184
- ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
184
+ ...getAuthHeaders(),
185
185
  },
186
186
  body: JSON.stringify({ messageIds: [messageId] }),
187
187
  });
@@ -214,7 +214,7 @@ chatCommand
214
214
  method: "POST",
215
215
  headers: {
216
216
  "Content-Type": "application/json",
217
- ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
217
+ ...getAuthHeaders(),
218
218
  },
219
219
  body: JSON.stringify({
220
220
  agentId: workerId,
@@ -248,7 +248,7 @@ chatCommand
248
248
  while (Date.now() - startTime < timeoutMs) {
249
249
  await new Promise((resolve) => setTimeout(resolve, pollInterval));
250
250
  const pollRes = await fetch(`${huskyApiUrl}/api/google-chat/review/${data.id}/poll`, {
251
- headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
251
+ headers: getAuthHeaders(),
252
252
  });
253
253
  if (!pollRes.ok)
254
254
  continue;
@@ -286,7 +286,7 @@ chatCommand
286
286
  }
287
287
  try {
288
288
  const res = await fetch(`${huskyApiUrl}/api/google-chat/review/${reviewId}`, {
289
- headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
289
+ headers: getAuthHeaders(),
290
290
  });
291
291
  if (!res.ok) {
292
292
  if (res.status === 404) {
@@ -336,7 +336,7 @@ chatCommand
336
336
  if (options.limit)
337
337
  params.set("limit", options.limit);
338
338
  const res = await fetch(`${huskyApiUrl}/api/google-chat/inbox?${params}`, {
339
- headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
339
+ headers: getAuthHeaders(),
340
340
  });
341
341
  if (!res.ok) {
342
342
  throw new Error(`API error: ${res.status}`);
@@ -382,7 +382,7 @@ chatCommand
382
382
  method: "POST",
383
383
  headers: {
384
384
  "Content-Type": "application/json",
385
- ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
385
+ ...getAuthHeaders(),
386
386
  },
387
387
  body: JSON.stringify({
388
388
  text: message,
@@ -486,7 +486,7 @@ chatCommand
486
486
  method: "POST",
487
487
  headers: {
488
488
  "Content-Type": "application/json",
489
- ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
489
+ ...getAuthHeaders(),
490
490
  },
491
491
  body: JSON.stringify({
492
492
  fileBase64,
@@ -522,7 +522,7 @@ chatCommand
522
522
  try {
523
523
  // Fetch inbox to find the message
524
524
  const inboxRes = await fetch(`${huskyApiUrl}/api/google-chat/inbox?limit=50`, {
525
- headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
525
+ headers: getAuthHeaders(),
526
526
  });
527
527
  if (!inboxRes.ok) {
528
528
  throw new Error(`Failed to fetch inbox: ${inboxRes.status}`);
@@ -545,7 +545,7 @@ chatCommand
545
545
  method: "POST",
546
546
  headers: {
547
547
  "Content-Type": "application/json",
548
- ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
548
+ ...getAuthHeaders(),
549
549
  },
550
550
  body: JSON.stringify({ text: response }),
551
551
  });
@@ -561,7 +561,7 @@ chatCommand
561
561
  method: "POST",
562
562
  headers: {
563
563
  "Content-Type": "application/json",
564
- ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
564
+ ...getAuthHeaders(),
565
565
  },
566
566
  body: JSON.stringify({
567
567
  text: response,
@@ -576,7 +576,7 @@ chatCommand
576
576
  // Mark as read
577
577
  await fetch(`${huskyApiUrl}/api/google-chat/inbox/${msg.id}/read`, {
578
578
  method: "POST",
579
- headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
579
+ headers: getAuthHeaders(),
580
580
  });
581
581
  // Add reaction to original message if messageName is available
582
582
  if (msg.messageName) {
@@ -585,7 +585,7 @@ chatCommand
585
585
  method: "POST",
586
586
  headers: {
587
587
  "Content-Type": "application/json",
588
- ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
588
+ ...getAuthHeaders(),
589
589
  },
590
590
  body: JSON.stringify({ emoji: "āœ…" }),
591
591
  });
@@ -614,7 +614,7 @@ chatCommand
614
614
  try {
615
615
  const res = await fetch(`${huskyApiUrl}/api/google-chat/inbox/${messageId}/read`, {
616
616
  method: "POST",
617
- headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
617
+ headers: getAuthHeaders(),
618
618
  });
619
619
  if (!res.ok) {
620
620
  throw new Error(`API error: ${res.status}`);
@@ -643,7 +643,7 @@ chatCommand
643
643
  const poll = async () => {
644
644
  try {
645
645
  const res = await fetch(`${huskyApiUrl}/api/google-chat/inbox?unread=true&limit=5`, {
646
- headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
646
+ headers: getAuthHeaders(),
647
647
  });
648
648
  if (!res.ok)
649
649
  return;
@@ -705,14 +705,10 @@ chatCommand
705
705
  formattedMessage += `\nšŸ’” Tip: Use \`husky chat reply-chat "your response"\` to reply`;
706
706
  }
707
707
  }
708
- const escapedMessage = formattedMessage
709
- .replace(/\\/g, "\\\\")
710
- .replace(/"/g, '\\"')
711
- .replace(/\$/g, "\\$")
712
- .replace(/`/g, "\\`")
713
- .replace(/'/g, "'\\''");
714
708
  try {
715
- await execAsync(`tmux send-keys -t "${tmuxTarget}" "${escapedMessage}" Enter`, { timeout: 5000 });
709
+ await execFileAsync("tmux", ["send-keys", "-t", tmuxTarget, formattedMessage, "Enter"], {
710
+ timeout: 5000,
711
+ });
716
712
  return true;
717
713
  }
718
714
  catch (error) {
@@ -725,7 +721,7 @@ chatCommand
725
721
  try {
726
722
  await fetch(`${huskyApiUrl}/api/google-chat/inbox/${messageId}/read`, {
727
723
  method: "POST",
728
- headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
724
+ headers: getAuthHeaders(),
729
725
  });
730
726
  }
731
727
  catch { }
@@ -733,7 +729,7 @@ chatCommand
733
729
  const poll = async () => {
734
730
  try {
735
731
  const res = await fetch(`${huskyApiUrl}/api/google-chat/inbox?unread=true&limit=10`, {
736
- headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
732
+ headers: getAuthHeaders(),
737
733
  });
738
734
  if (!res.ok)
739
735
  return;
@@ -824,7 +820,7 @@ chatCommand
824
820
  method: "POST",
825
821
  headers: {
826
822
  "Content-Type": "application/json",
827
- ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
823
+ ...getAuthHeaders(),
828
824
  },
829
825
  body: JSON.stringify({
830
826
  text: formattedMessage,
@@ -846,7 +842,7 @@ chatCommand
846
842
  method: "POST",
847
843
  headers: {
848
844
  "Content-Type": "application/json",
849
- ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
845
+ ...getAuthHeaders(),
850
846
  },
851
847
  body: JSON.stringify({
852
848
  agentId,
@@ -919,7 +915,7 @@ chatCommand
919
915
  method: "PATCH",
920
916
  headers: {
921
917
  "Content-Type": "application/json",
922
- ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
918
+ ...getAuthHeaders(),
923
919
  },
924
920
  body: JSON.stringify({ status: "resolved" }),
925
921
  });
@@ -958,7 +954,7 @@ chatCommand
958
954
  if (options.agent)
959
955
  params.set("agentId", options.agent);
960
956
  const res = await fetch(`${huskyApiUrl}/api/agent-conversations?${params}`, {
961
- headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
957
+ headers: getAuthHeaders(),
962
958
  });
963
959
  if (!res.ok) {
964
960
  throw new Error(`API error: ${res.status}`);
@@ -1004,7 +1000,7 @@ chatCommand
1004
1000
  }
1005
1001
  try {
1006
1002
  const res = await fetch(`${huskyApiUrl}/api/google-chat/spaces`, {
1007
- headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
1003
+ headers: getAuthHeaders(),
1008
1004
  });
1009
1005
  if (!res.ok) {
1010
1006
  throw new Error(`API error: ${res.status}`);
@@ -1059,7 +1055,7 @@ chatCommand
1059
1055
  try {
1060
1056
  while (Date.now() - startTime < timeoutMs) {
1061
1057
  const res = await fetch(`${huskyApiUrl}/api/google-chat/review/${reviewId}/poll`, {
1062
- headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
1058
+ headers: getAuthHeaders(),
1063
1059
  });
1064
1060
  if (res.ok) {
1065
1061
  const data = await res.json();
@@ -46,12 +46,16 @@ interface Config {
46
46
  gcsBucket?: string;
47
47
  shopifyDomain?: string;
48
48
  shopifyToken?: string;
49
+ redditClientId?: string;
50
+ redditClientSecret?: string;
51
+ youtubeApiKey?: string;
52
+ xBearerToken?: string;
49
53
  }
50
54
  export declare function getConfig(): Config;
51
55
  export declare function saveConfig(config: Config): void;
52
56
  /**
53
57
  * Fetch role and permissions from /api/auth/whoami
54
- * Uses session token (Bearer) if available, otherwise falls back to API key.
58
+ * Uses session token (Bearer) only.
55
59
  * Caches the result in config for 1 hour.
56
60
  */
57
61
  export declare function fetchAndCacheRole(): Promise<{
@@ -64,7 +68,7 @@ export declare function fetchAndCacheRole(): Promise<{
64
68
  export declare function hasPermission(permission: string): boolean;
65
69
  /**
66
70
  * Get current role from config.
67
- * Prefers sessionRole (from auth login) over role (from API key) when session is active.
71
+ * Uses sessionRole (from auth login) when session is active.
68
72
  * Validates that the role is a known valid role before returning.
69
73
  */
70
74
  export declare function getRole(): AgentRole | undefined;
@@ -88,19 +92,18 @@ export declare function clearSessionConfig(): void;
88
92
  export declare function isSessionActive(): boolean;
89
93
  /**
90
94
  * Check if the API is properly configured for making requests.
91
- * Returns true if we have either an active session or an API key.
95
+ * Returns true if we have an API URL and an active session.
92
96
  */
93
97
  export declare function isApiConfigured(): boolean;
94
98
  /**
95
99
  * Get authentication headers for API requests.
96
- * Returns Bearer token if session is active, otherwise x-api-key.
100
+ * Returns Bearer token if session is active.
97
101
  * Use this for all API calls to ensure consistent auth.
98
102
  */
99
103
  export declare function getAuthHeaders(): Record<string, string>;
100
104
  /**
101
105
  * Ensure the session is valid, refreshing if needed.
102
- * Call this before long-running operations (like watch modes).
103
- * Returns true if session is valid (or was refreshed), false if no session/refresh failed.
106
+ * Returns true if session is active, false otherwise.
104
107
  */
105
108
  export declare function ensureValidSession(): Promise<boolean>;
106
109
  export declare function getSessionConfig(): {