@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.
- package/dist/commands/agent-msg.js +9 -4
- package/dist/commands/biz/gotess.js +10 -4
- package/dist/commands/chat.js +33 -37
- package/dist/commands/config.d.ts +9 -6
- package/dist/commands/config.js +103 -127
- package/dist/commands/interactive/auth.js +7 -2
- package/dist/commands/research.d.ts +2 -0
- package/dist/commands/research.js +774 -0
- package/dist/index.js +46 -1
- package/dist/lib/api-client.d.ts +1 -0
- package/dist/lib/api-client.js +16 -83
- package/dist/lib/biz/api-brain.js +6 -23
- package/dist/lib/biz/index.d.ts +2 -0
- package/dist/lib/biz/index.js +2 -0
- package/dist/lib/biz/reddit.d.ts +83 -0
- package/dist/lib/biz/reddit.js +168 -0
- package/dist/lib/biz/x-client.d.ts +27 -0
- package/dist/lib/biz/x-client.js +123 -0
- package/dist/lib/biz/youtube-monitor.d.ts +72 -0
- package/dist/lib/biz/youtube-monitor.js +180 -0
- package/dist/lib/permissions-cache.js +9 -35
- package/package.json +1 -1
|
@@ -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
|
|
6
|
-
console.error("Error: API URL
|
|
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
|
-
|
|
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
|
|
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);
|
package/dist/commands/chat.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { getConfig } from "./config.js";
|
|
3
|
-
import {
|
|
2
|
+
import { getAuthHeaders, getConfig } from "./config.js";
|
|
3
|
+
import { execFile } from "child_process";
|
|
4
4
|
import { promisify } from "util";
|
|
5
|
-
const
|
|
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:
|
|
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:
|
|
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
|
-
...(
|
|
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
|
-
...(
|
|
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
|
-
...(
|
|
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
|
-
...(
|
|
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
|
-
...(
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
...(
|
|
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
|
-
...(
|
|
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:
|
|
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
|
-
...(
|
|
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
|
-
...(
|
|
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:
|
|
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
|
-
...(
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
...(
|
|
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
|
-
...(
|
|
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
|
-
...(
|
|
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:
|
|
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:
|
|
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:
|
|
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)
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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(): {
|