@simonfestl/husky-cli 1.15.0 â 1.16.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/interactive/auth.d.ts +6 -0
- package/dist/commands/interactive/auth.js +227 -0
- package/dist/commands/interactive/brain.d.ts +6 -0
- package/dist/commands/interactive/brain.js +356 -0
- package/dist/commands/interactive/chat.d.ts +6 -0
- package/dist/commands/interactive/chat.js +367 -0
- package/dist/commands/interactive/infra.d.ts +6 -0
- package/dist/commands/interactive/infra.js +243 -0
- package/dist/commands/interactive/pr.d.ts +6 -0
- package/dist/commands/interactive/pr.js +323 -0
- package/dist/commands/interactive/preview.d.ts +6 -0
- package/dist/commands/interactive/preview.js +238 -0
- package/dist/commands/interactive/tools.d.ts +6 -0
- package/dist/commands/interactive/tools.js +375 -0
- package/dist/commands/interactive/worker.d.ts +6 -0
- package/dist/commands/interactive/worker.js +196 -0
- package/dist/commands/interactive.js +59 -15
- package/dist/lib/permissions.js +28 -1
- package/package.json +1 -1
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Mode: Chat Module
|
|
3
|
+
*
|
|
4
|
+
* Provides menu-based Google Chat integration and messaging.
|
|
5
|
+
*/
|
|
6
|
+
import { select, input, confirm } from "@inquirer/prompts";
|
|
7
|
+
import { ensureConfig, pressEnterToContinue, truncate, formatDate } from "./utils.js";
|
|
8
|
+
export async function chatMenu() {
|
|
9
|
+
const config = ensureConfig();
|
|
10
|
+
console.log("\n CHAT");
|
|
11
|
+
console.log(" " + "-".repeat(50));
|
|
12
|
+
console.log(" Google Chat integration and messaging");
|
|
13
|
+
console.log("");
|
|
14
|
+
const menuItems = [
|
|
15
|
+
{ name: "đĨ Inbox", value: "inbox" },
|
|
16
|
+
{ name: "âŗ Pending Messages", value: "pending" },
|
|
17
|
+
{ name: "đŦ Send Message", value: "send" },
|
|
18
|
+
{ name: "âŠī¸ Reply to Message", value: "reply" },
|
|
19
|
+
{ name: "đī¸ Watch for Messages", value: "watch" },
|
|
20
|
+
{ name: "đ¨ī¸ Conversations", value: "conversations" },
|
|
21
|
+
{ name: "đ Spaces", value: "spaces" },
|
|
22
|
+
{ name: "â Ask Question (with wait)", value: "ask" },
|
|
23
|
+
{ name: "â
Request Review", value: "review" },
|
|
24
|
+
{ name: "â Back", value: "back" },
|
|
25
|
+
];
|
|
26
|
+
const choice = await select({
|
|
27
|
+
message: "Chat Action:",
|
|
28
|
+
choices: menuItems,
|
|
29
|
+
});
|
|
30
|
+
switch (choice) {
|
|
31
|
+
case "inbox":
|
|
32
|
+
await showInbox(config);
|
|
33
|
+
break;
|
|
34
|
+
case "pending":
|
|
35
|
+
await showPending(config);
|
|
36
|
+
break;
|
|
37
|
+
case "send":
|
|
38
|
+
await sendMessage(config);
|
|
39
|
+
break;
|
|
40
|
+
case "reply":
|
|
41
|
+
await replyToMessage(config);
|
|
42
|
+
break;
|
|
43
|
+
case "watch":
|
|
44
|
+
await watchMessages(config);
|
|
45
|
+
break;
|
|
46
|
+
case "conversations":
|
|
47
|
+
await showConversations(config);
|
|
48
|
+
break;
|
|
49
|
+
case "spaces":
|
|
50
|
+
await showSpaces(config);
|
|
51
|
+
break;
|
|
52
|
+
case "ask":
|
|
53
|
+
await askQuestion(config);
|
|
54
|
+
break;
|
|
55
|
+
case "review":
|
|
56
|
+
await requestReview(config);
|
|
57
|
+
break;
|
|
58
|
+
case "back":
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function showInbox(config) {
|
|
63
|
+
try {
|
|
64
|
+
const unreadOnly = await confirm({
|
|
65
|
+
message: "Show only unread messages?",
|
|
66
|
+
default: false,
|
|
67
|
+
});
|
|
68
|
+
const url = `${config.apiUrl}/api/supervisor/inbox${unreadOnly ? "?unread=true" : ""}`;
|
|
69
|
+
const res = await fetch(url, {
|
|
70
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
71
|
+
});
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
74
|
+
await pressEnterToContinue();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const messages = await res.json();
|
|
78
|
+
console.log("\n INBOX");
|
|
79
|
+
console.log(" " + "-".repeat(70));
|
|
80
|
+
if (messages.length === 0) {
|
|
81
|
+
console.log(" No messages found.");
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.log(` ${"ID".padEnd(12)} ${"FROM".padEnd(20)} ${"STATUS".padEnd(10)} ${"TEXT"}`);
|
|
85
|
+
console.log(" " + "-".repeat(70));
|
|
86
|
+
for (const msg of messages.slice(0, 20)) {
|
|
87
|
+
const status = msg.read ? "read" : "unread";
|
|
88
|
+
console.log(` ${msg.id.substring(0, 10).padEnd(12)} ${truncate(msg.senderName, 18).padEnd(20)} ${status.padEnd(10)} ${truncate(msg.text, 30)}`);
|
|
89
|
+
}
|
|
90
|
+
if (messages.length > 20) {
|
|
91
|
+
console.log(`\n ... and ${messages.length - 20} more messages`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
console.log("");
|
|
95
|
+
await pressEnterToContinue();
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.error("\n Error fetching inbox:", error);
|
|
99
|
+
await pressEnterToContinue();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function showPending(config) {
|
|
103
|
+
try {
|
|
104
|
+
const res = await fetch(`${config.apiUrl}/api/supervisor/pending`, {
|
|
105
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
106
|
+
});
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
109
|
+
await pressEnterToContinue();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const messages = await res.json();
|
|
113
|
+
console.log("\n PENDING MESSAGES");
|
|
114
|
+
console.log(" " + "-".repeat(70));
|
|
115
|
+
if (messages.length === 0) {
|
|
116
|
+
console.log(" No pending messages.");
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
for (const msg of messages) {
|
|
120
|
+
console.log(` ID: ${msg.id}`);
|
|
121
|
+
console.log(` From: ${msg.senderName}`);
|
|
122
|
+
console.log(` Time: ${formatDate(msg.createdAt)}`);
|
|
123
|
+
console.log(` Text: ${truncate(msg.text, 60)}`);
|
|
124
|
+
console.log(" " + "-".repeat(40));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
console.log("");
|
|
128
|
+
await pressEnterToContinue();
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.error("\n Error fetching pending:", error);
|
|
132
|
+
await pressEnterToContinue();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function sendMessage(config) {
|
|
136
|
+
try {
|
|
137
|
+
const message = await input({
|
|
138
|
+
message: "Message to send:",
|
|
139
|
+
validate: (v) => (v.length > 0 ? true : "Message is required"),
|
|
140
|
+
});
|
|
141
|
+
const res = await fetch(`${config.apiUrl}/api/chat/send`, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: {
|
|
144
|
+
"Content-Type": "application/json",
|
|
145
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
146
|
+
},
|
|
147
|
+
body: JSON.stringify({ message }),
|
|
148
|
+
});
|
|
149
|
+
if (!res.ok) {
|
|
150
|
+
const error = await res.text();
|
|
151
|
+
console.error(`\n Error: ${error}\n`);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
console.log("\n Message sent successfully!\n");
|
|
155
|
+
}
|
|
156
|
+
await pressEnterToContinue();
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
console.error("\n Error sending message:", error);
|
|
160
|
+
await pressEnterToContinue();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function replyToMessage(config) {
|
|
164
|
+
try {
|
|
165
|
+
const messageId = await input({
|
|
166
|
+
message: "Message ID to reply to:",
|
|
167
|
+
validate: (v) => (v.length > 0 ? true : "Message ID is required"),
|
|
168
|
+
});
|
|
169
|
+
const response = await input({
|
|
170
|
+
message: "Your reply:",
|
|
171
|
+
validate: (v) => (v.length > 0 ? true : "Reply is required"),
|
|
172
|
+
});
|
|
173
|
+
const res = await fetch(`${config.apiUrl}/api/chat/reply-to/${messageId}`, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: {
|
|
176
|
+
"Content-Type": "application/json",
|
|
177
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
178
|
+
},
|
|
179
|
+
body: JSON.stringify({ response }),
|
|
180
|
+
});
|
|
181
|
+
if (!res.ok) {
|
|
182
|
+
const error = await res.text();
|
|
183
|
+
console.error(`\n Error: ${error}\n`);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
console.log("\n Reply sent successfully!\n");
|
|
187
|
+
}
|
|
188
|
+
await pressEnterToContinue();
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
console.error("\n Error replying:", error);
|
|
192
|
+
await pressEnterToContinue();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function watchMessages(config) {
|
|
196
|
+
console.log("\n WATCH MODE");
|
|
197
|
+
console.log(" " + "-".repeat(50));
|
|
198
|
+
console.log(" Watching for new messages...");
|
|
199
|
+
console.log("");
|
|
200
|
+
let lastCheck = new Date().toISOString();
|
|
201
|
+
const checkMessages = async () => {
|
|
202
|
+
try {
|
|
203
|
+
const res = await fetch(`${config.apiUrl}/api/supervisor/inbox?since=${encodeURIComponent(lastCheck)}`, {
|
|
204
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
205
|
+
});
|
|
206
|
+
if (res.ok) {
|
|
207
|
+
const messages = await res.json();
|
|
208
|
+
for (const msg of messages) {
|
|
209
|
+
console.log(` [${new Date().toLocaleTimeString()}] From: ${msg.senderName}`);
|
|
210
|
+
console.log(` ${msg.text.substring(0, 80)}`);
|
|
211
|
+
console.log("");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
lastCheck = new Date().toISOString();
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// Ignore errors during watch
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
// Check every 5 seconds
|
|
221
|
+
const interval = setInterval(checkMessages, 5000);
|
|
222
|
+
// Wait for user to press Enter
|
|
223
|
+
await input({
|
|
224
|
+
message: "Press Enter to stop watching...",
|
|
225
|
+
});
|
|
226
|
+
clearInterval(interval);
|
|
227
|
+
console.log("\n Stopped watching.\n");
|
|
228
|
+
}
|
|
229
|
+
async function showConversations(config) {
|
|
230
|
+
try {
|
|
231
|
+
const res = await fetch(`${config.apiUrl}/api/agent-conversations`, {
|
|
232
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
233
|
+
});
|
|
234
|
+
if (!res.ok) {
|
|
235
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
236
|
+
await pressEnterToContinue();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const conversations = await res.json();
|
|
240
|
+
console.log("\n CONVERSATIONS");
|
|
241
|
+
console.log(" " + "-".repeat(70));
|
|
242
|
+
if (conversations.length === 0) {
|
|
243
|
+
console.log(" No active conversations.");
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
console.log(` ${"AGENT".padEnd(20)} ${"STATUS".padEnd(12)} ${"QUESTION"}`);
|
|
247
|
+
console.log(" " + "-".repeat(70));
|
|
248
|
+
for (const conv of conversations) {
|
|
249
|
+
console.log(` ${truncate(conv.agentId, 18).padEnd(20)} ${conv.status.padEnd(12)} ${truncate(conv.question, 35)}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
console.log("");
|
|
253
|
+
await pressEnterToContinue();
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
console.error("\n Error fetching conversations:", error);
|
|
257
|
+
await pressEnterToContinue();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async function showSpaces(config) {
|
|
261
|
+
try {
|
|
262
|
+
const res = await fetch(`${config.apiUrl}/api/chat/spaces`, {
|
|
263
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
264
|
+
});
|
|
265
|
+
if (!res.ok) {
|
|
266
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
267
|
+
await pressEnterToContinue();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const spaces = await res.json();
|
|
271
|
+
console.log("\n GOOGLE CHAT SPACES");
|
|
272
|
+
console.log(" " + "-".repeat(70));
|
|
273
|
+
if (spaces.length === 0) {
|
|
274
|
+
console.log(" No spaces found.");
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
console.log(` ${"NAME".padEnd(30)} ${"TYPE".padEnd(15)} ${"DISPLAY NAME"}`);
|
|
278
|
+
console.log(" " + "-".repeat(70));
|
|
279
|
+
for (const space of spaces) {
|
|
280
|
+
console.log(` ${truncate(space.name, 28).padEnd(30)} ${space.type.padEnd(15)} ${truncate(space.displayName, 25)}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
console.log("");
|
|
284
|
+
await pressEnterToContinue();
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
console.error("\n Error fetching spaces:", error);
|
|
288
|
+
await pressEnterToContinue();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async function askQuestion(config) {
|
|
292
|
+
try {
|
|
293
|
+
const question = await input({
|
|
294
|
+
message: "Question to ask:",
|
|
295
|
+
validate: (v) => (v.length > 0 ? true : "Question is required"),
|
|
296
|
+
});
|
|
297
|
+
const waitForAnswer = await confirm({
|
|
298
|
+
message: "Wait for answer?",
|
|
299
|
+
default: true,
|
|
300
|
+
});
|
|
301
|
+
console.log("\n Sending question...");
|
|
302
|
+
const res = await fetch(`${config.apiUrl}/api/chat/ask`, {
|
|
303
|
+
method: "POST",
|
|
304
|
+
headers: {
|
|
305
|
+
"Content-Type": "application/json",
|
|
306
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
307
|
+
},
|
|
308
|
+
body: JSON.stringify({ question, wait: waitForAnswer }),
|
|
309
|
+
});
|
|
310
|
+
if (!res.ok) {
|
|
311
|
+
const error = await res.text();
|
|
312
|
+
console.error(`\n Error: ${error}\n`);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
const data = await res.json();
|
|
316
|
+
if (data.answer) {
|
|
317
|
+
console.log("\n ANSWER:");
|
|
318
|
+
console.log(" " + "-".repeat(50));
|
|
319
|
+
console.log(` ${data.answer}`);
|
|
320
|
+
}
|
|
321
|
+
else if (data.reviewId) {
|
|
322
|
+
console.log(`\n Question sent. Review ID: ${data.reviewId}`);
|
|
323
|
+
console.log(" Use 'husky chat review-status <id>' to check status.");
|
|
324
|
+
}
|
|
325
|
+
console.log("");
|
|
326
|
+
}
|
|
327
|
+
await pressEnterToContinue();
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
console.error("\n Error asking question:", error);
|
|
331
|
+
await pressEnterToContinue();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async function requestReview(config) {
|
|
335
|
+
try {
|
|
336
|
+
const question = await input({
|
|
337
|
+
message: "What needs review?",
|
|
338
|
+
validate: (v) => (v.length > 0 ? true : "Description is required"),
|
|
339
|
+
});
|
|
340
|
+
console.log("\n Requesting review...");
|
|
341
|
+
const res = await fetch(`${config.apiUrl}/api/chat/review`, {
|
|
342
|
+
method: "POST",
|
|
343
|
+
headers: {
|
|
344
|
+
"Content-Type": "application/json",
|
|
345
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
346
|
+
},
|
|
347
|
+
body: JSON.stringify({ question }),
|
|
348
|
+
});
|
|
349
|
+
if (!res.ok) {
|
|
350
|
+
const error = await res.text();
|
|
351
|
+
console.error(`\n Error: ${error}\n`);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
const data = await res.json();
|
|
355
|
+
console.log(`\n Review requested successfully!`);
|
|
356
|
+
if (data.reviewId) {
|
|
357
|
+
console.log(` Review ID: ${data.reviewId}`);
|
|
358
|
+
}
|
|
359
|
+
console.log("");
|
|
360
|
+
}
|
|
361
|
+
await pressEnterToContinue();
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
console.error("\n Error requesting review:", error);
|
|
365
|
+
await pressEnterToContinue();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Mode: Infrastructure Module
|
|
3
|
+
*
|
|
4
|
+
* Provides menu-based infrastructure monitoring and management.
|
|
5
|
+
*/
|
|
6
|
+
import { select, input, confirm } from "@inquirer/prompts";
|
|
7
|
+
import { ensureConfig, pressEnterToContinue, truncate, formatDate } from "./utils.js";
|
|
8
|
+
export async function infraMenu() {
|
|
9
|
+
const config = ensureConfig();
|
|
10
|
+
console.log("\n INFRASTRUCTURE");
|
|
11
|
+
console.log(" " + "-".repeat(50));
|
|
12
|
+
console.log(" Monitoring and management");
|
|
13
|
+
console.log("");
|
|
14
|
+
const menuItems = [
|
|
15
|
+
{ name: "â¤ī¸ Health Check", value: "health" },
|
|
16
|
+
{ name: "đ Service Status", value: "services" },
|
|
17
|
+
{ name: "đģ VM Status", value: "vms" },
|
|
18
|
+
{ name: "đ Metrics", value: "metrics" },
|
|
19
|
+
{ name: "â ī¸ Alerts", value: "alerts" },
|
|
20
|
+
{ name: "đ Restart Service", value: "restart" },
|
|
21
|
+
{ name: "â Back", value: "back" },
|
|
22
|
+
];
|
|
23
|
+
const choice = await select({
|
|
24
|
+
message: "Infra Action:",
|
|
25
|
+
choices: menuItems,
|
|
26
|
+
});
|
|
27
|
+
switch (choice) {
|
|
28
|
+
case "health":
|
|
29
|
+
await healthCheck(config);
|
|
30
|
+
break;
|
|
31
|
+
case "services":
|
|
32
|
+
await serviceStatus(config);
|
|
33
|
+
break;
|
|
34
|
+
case "vms":
|
|
35
|
+
await vmStatus(config);
|
|
36
|
+
break;
|
|
37
|
+
case "metrics":
|
|
38
|
+
await showMetrics(config);
|
|
39
|
+
break;
|
|
40
|
+
case "alerts":
|
|
41
|
+
await showAlerts(config);
|
|
42
|
+
break;
|
|
43
|
+
case "restart":
|
|
44
|
+
await restartService(config);
|
|
45
|
+
break;
|
|
46
|
+
case "back":
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function healthCheck(config) {
|
|
51
|
+
try {
|
|
52
|
+
console.log("\n Running health checks...\n");
|
|
53
|
+
const res = await fetch(`${config.apiUrl}/api/infra/health`, {
|
|
54
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
58
|
+
await pressEnterToContinue();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const health = await res.json();
|
|
62
|
+
console.log(" HEALTH STATUS");
|
|
63
|
+
console.log(" " + "-".repeat(70));
|
|
64
|
+
if (health.length === 0) {
|
|
65
|
+
console.log(" No services configured.");
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.log(` ${"SERVICE".padEnd(25)} ${"STATUS".padEnd(12)} ${"LATENCY".padEnd(12)} ${"LAST CHECK"}`);
|
|
69
|
+
console.log(" " + "-".repeat(70));
|
|
70
|
+
for (const svc of health) {
|
|
71
|
+
const statusIcon = svc.status === "healthy" ? "â
" : svc.status === "degraded" ? "â ī¸" : "â";
|
|
72
|
+
const latency = svc.latency ? `${svc.latency}ms` : "-";
|
|
73
|
+
console.log(` ${truncate(svc.name, 23).padEnd(25)} ${statusIcon} ${svc.status.padEnd(9)} ${latency.padEnd(12)} ${formatDate(svc.lastCheck)}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
console.log("");
|
|
77
|
+
await pressEnterToContinue();
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error("\n Error running health check:", error);
|
|
81
|
+
await pressEnterToContinue();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function serviceStatus(config) {
|
|
85
|
+
try {
|
|
86
|
+
const res = await fetch(`${config.apiUrl}/api/infra/services`, {
|
|
87
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
88
|
+
});
|
|
89
|
+
if (!res.ok) {
|
|
90
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
91
|
+
await pressEnterToContinue();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const services = await res.json();
|
|
95
|
+
console.log("\n CLOUD RUN SERVICES");
|
|
96
|
+
console.log(" " + "-".repeat(70));
|
|
97
|
+
if (!services || services.length === 0) {
|
|
98
|
+
console.log(" No services found.");
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
for (const svc of services) {
|
|
102
|
+
console.log(` ${svc.name}`);
|
|
103
|
+
console.log(` URL: ${svc.url || "-"}`);
|
|
104
|
+
console.log(` Region: ${svc.region || "-"}`);
|
|
105
|
+
console.log(` Status: ${svc.status || "-"}`);
|
|
106
|
+
console.log("");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
await pressEnterToContinue();
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
console.error("\n Error fetching services:", error);
|
|
113
|
+
await pressEnterToContinue();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function vmStatus(config) {
|
|
117
|
+
try {
|
|
118
|
+
const res = await fetch(`${config.apiUrl}/api/vm-sessions`, {
|
|
119
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
120
|
+
});
|
|
121
|
+
if (!res.ok) {
|
|
122
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
123
|
+
await pressEnterToContinue();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const vms = await res.json();
|
|
127
|
+
console.log("\n VM STATUS");
|
|
128
|
+
console.log(" " + "-".repeat(70));
|
|
129
|
+
if (vms.length === 0) {
|
|
130
|
+
console.log(" No VMs found.");
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
console.log(` ${"NAME".padEnd(25)} ${"STATUS".padEnd(12)} ${"ZONE".padEnd(20)} ${"TYPE"}`);
|
|
134
|
+
console.log(" " + "-".repeat(70));
|
|
135
|
+
for (const vm of vms) {
|
|
136
|
+
const statusIcon = vm.status === "running" ? "đĸ" : vm.status === "stopped" ? "đ´" : "đĄ";
|
|
137
|
+
console.log(` ${truncate(vm.name, 23).padEnd(25)} ${statusIcon} ${vm.status.padEnd(9)} ${vm.zone.padEnd(20)} ${vm.machineType || "-"}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
console.log("");
|
|
141
|
+
await pressEnterToContinue();
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
console.error("\n Error fetching VMs:", error);
|
|
145
|
+
await pressEnterToContinue();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function showMetrics(config) {
|
|
149
|
+
try {
|
|
150
|
+
const res = await fetch(`${config.apiUrl}/api/infra/metrics`, {
|
|
151
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
152
|
+
});
|
|
153
|
+
if (!res.ok) {
|
|
154
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
155
|
+
await pressEnterToContinue();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const metrics = await res.json();
|
|
159
|
+
console.log("\n METRICS");
|
|
160
|
+
console.log(" " + "-".repeat(50));
|
|
161
|
+
if (!metrics || Object.keys(metrics).length === 0) {
|
|
162
|
+
console.log(" No metrics available.");
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
for (const [key, value] of Object.entries(metrics)) {
|
|
166
|
+
console.log(` ${key}: ${value}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
console.log("");
|
|
170
|
+
await pressEnterToContinue();
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
console.error("\n Error fetching metrics:", error);
|
|
174
|
+
await pressEnterToContinue();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function showAlerts(config) {
|
|
178
|
+
try {
|
|
179
|
+
const res = await fetch(`${config.apiUrl}/api/infra/alerts`, {
|
|
180
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
181
|
+
});
|
|
182
|
+
if (!res.ok) {
|
|
183
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
184
|
+
await pressEnterToContinue();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const alerts = await res.json();
|
|
188
|
+
console.log("\n ALERTS");
|
|
189
|
+
console.log(" " + "-".repeat(70));
|
|
190
|
+
if (!alerts || alerts.length === 0) {
|
|
191
|
+
console.log(" â
No active alerts.");
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
for (const alert of alerts) {
|
|
195
|
+
const icon = alert.severity === "critical" ? "đ´" : alert.severity === "warning" ? "đĄ" : "đĩ";
|
|
196
|
+
console.log(` ${icon} ${alert.title || alert.name}`);
|
|
197
|
+
console.log(` Severity: ${alert.severity}`);
|
|
198
|
+
console.log(` Message: ${alert.message || "-"}`);
|
|
199
|
+
console.log(` Time: ${formatDate(alert.createdAt)}`);
|
|
200
|
+
console.log("");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
await pressEnterToContinue();
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
console.error("\n Error fetching alerts:", error);
|
|
207
|
+
await pressEnterToContinue();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function restartService(config) {
|
|
211
|
+
try {
|
|
212
|
+
const serviceName = await input({
|
|
213
|
+
message: "Service name to restart:",
|
|
214
|
+
validate: (v) => (v.length > 0 ? true : "Service name is required"),
|
|
215
|
+
});
|
|
216
|
+
const confirmed = await confirm({
|
|
217
|
+
message: `Are you sure you want to restart ${serviceName}?`,
|
|
218
|
+
default: false,
|
|
219
|
+
});
|
|
220
|
+
if (!confirmed) {
|
|
221
|
+
console.log("\n Cancelled.\n");
|
|
222
|
+
await pressEnterToContinue();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
console.log(`\n Restarting ${serviceName}...`);
|
|
226
|
+
const res = await fetch(`${config.apiUrl}/api/infra/services/${encodeURIComponent(serviceName)}/restart`, {
|
|
227
|
+
method: "POST",
|
|
228
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
229
|
+
});
|
|
230
|
+
if (!res.ok) {
|
|
231
|
+
const error = await res.text();
|
|
232
|
+
console.error(`\n Error: ${error}\n`);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
console.log(`\n Service ${serviceName} restarted successfully!\n`);
|
|
236
|
+
}
|
|
237
|
+
await pressEnterToContinue();
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
console.error("\n Error restarting service:", error);
|
|
241
|
+
await pressEnterToContinue();
|
|
242
|
+
}
|
|
243
|
+
}
|