@gowelle/stint-agent 1.2.16 → 1.2.18
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/assets/logo.png +0 -0
- package/dist/{StatusDashboard-LCGOELR4.js → StatusDashboard-KCHNLVD7.js} +2 -3
- package/dist/api-6EGHLTCT.js +7 -0
- package/dist/{chunk-QQP6IASS.js → chunk-A2CVSQ3K.js} +1 -1
- package/dist/{chunk-Z44NPHXG.js → chunk-SXPP272L.js} +217 -160
- package/dist/{chunk-XHXSWLUC.js → chunk-WETVBZ6Z.js} +89 -1
- package/dist/{chunk-I6DTKIFX.js → chunk-XPZNWXB4.js} +3 -5
- package/dist/daemon/runner.js +34 -150
- package/dist/index.js +33 -13
- package/package.json +3 -1
- package/dist/api-IB5F32WJ.js +0 -8
- package/dist/chunk-7TIF7QZL.js +0 -95
- package/dist/chunk-DCY3EXDX.js +0 -40
- package/dist/notify-NXUEEO7M.js +0 -7
package/assets/logo.png
CHANGED
|
Binary file
|
|
@@ -2,11 +2,10 @@ import {
|
|
|
2
2
|
gitService,
|
|
3
3
|
projectService,
|
|
4
4
|
validatePidFile
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-A2CVSQ3K.js";
|
|
6
6
|
import {
|
|
7
7
|
authService
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-XHXSWLUC.js";
|
|
8
|
+
} from "./chunk-WETVBZ6Z.js";
|
|
10
9
|
|
|
11
10
|
// src/components/StatusDashboard.tsx
|
|
12
11
|
import { useState, useEffect } from "react";
|
|
@@ -1,20 +1,47 @@
|
|
|
1
1
|
import {
|
|
2
2
|
apiService
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-XPZNWXB4.js";
|
|
4
4
|
import {
|
|
5
5
|
gitService,
|
|
6
6
|
projectService
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import {
|
|
9
|
-
authService
|
|
10
|
-
} from "./chunk-7TIF7QZL.js";
|
|
11
|
-
import {
|
|
12
|
-
notify
|
|
13
|
-
} from "./chunk-DCY3EXDX.js";
|
|
7
|
+
} from "./chunk-A2CVSQ3K.js";
|
|
14
8
|
import {
|
|
9
|
+
authService,
|
|
15
10
|
config,
|
|
16
11
|
logger
|
|
17
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-WETVBZ6Z.js";
|
|
13
|
+
|
|
14
|
+
// src/utils/notify.ts
|
|
15
|
+
import notifier from "node-notifier";
|
|
16
|
+
import path from "path";
|
|
17
|
+
import { fileURLToPath } from "url";
|
|
18
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
var __dirname = path.dirname(__filename);
|
|
20
|
+
var DEFAULT_ICON = path.resolve(__dirname, "../assets/logo.png");
|
|
21
|
+
function notify(options) {
|
|
22
|
+
if (!config.areNotificationsEnabled()) {
|
|
23
|
+
logger.debug("notify", "Notifications disabled, skipping notification");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
notifier.notify({
|
|
28
|
+
title: options.title,
|
|
29
|
+
message: options.message,
|
|
30
|
+
open: options.open,
|
|
31
|
+
icon: options.icon || DEFAULT_ICON,
|
|
32
|
+
sound: true,
|
|
33
|
+
wait: false,
|
|
34
|
+
appID: "Stint Agent"
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
}, (error) => {
|
|
37
|
+
if (error) {
|
|
38
|
+
logger.error("notify", "Failed to send notification", error);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
} catch (error) {
|
|
42
|
+
logger.error("notify", "Failed to send notification", error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
18
45
|
|
|
19
46
|
// src/daemon/queue.ts
|
|
20
47
|
var CommitQueueProcessor = class {
|
|
@@ -100,7 +127,7 @@ var CommitQueueProcessor = class {
|
|
|
100
127
|
pushed = true;
|
|
101
128
|
logger.success("queue", `Pushed commit ${sha} to remote`);
|
|
102
129
|
notify({
|
|
103
|
-
title:
|
|
130
|
+
title: `Commit Pushed - ${project.name}`,
|
|
104
131
|
message: `Commit "${commit.message}" successfully pushed.`
|
|
105
132
|
});
|
|
106
133
|
} catch (error) {
|
|
@@ -109,14 +136,14 @@ var CommitQueueProcessor = class {
|
|
|
109
136
|
if (isConflict) {
|
|
110
137
|
logger.warn("queue", `Push failed due to remote conflict: ${pushError}`);
|
|
111
138
|
notify({
|
|
112
|
-
title:
|
|
139
|
+
title: `Push Conflict - ${project.name}`,
|
|
113
140
|
message: `Commit "${commit.message}" created but push failed.
|
|
114
|
-
Run "git pull --rebase
|
|
141
|
+
Run "git pull --rebase" to resolve.`
|
|
115
142
|
});
|
|
116
143
|
} else {
|
|
117
144
|
logger.error("queue", `Push failed: ${pushError}`);
|
|
118
145
|
notify({
|
|
119
|
-
title:
|
|
146
|
+
title: `Push Failed - ${project.name}`,
|
|
120
147
|
message: `Commit created but push failed: ${pushError}`
|
|
121
148
|
});
|
|
122
149
|
}
|
|
@@ -126,16 +153,16 @@ Run "git pull --rebase && git push" to resolve.`
|
|
|
126
153
|
await this.reportSuccess(commit.id, sha, pushed, pushError);
|
|
127
154
|
if (!pushed && !pushError) {
|
|
128
155
|
notify({
|
|
129
|
-
title:
|
|
156
|
+
title: `Commit Created - ${project.name}`,
|
|
130
157
|
message: `Commit "${commit.message}" created locally.`
|
|
131
158
|
});
|
|
132
159
|
}
|
|
133
160
|
return sha;
|
|
134
161
|
} catch (error) {
|
|
135
|
-
msg = error.message;
|
|
162
|
+
const msg = error.message;
|
|
136
163
|
logger.error("queue", `Commit execution failed: ${msg}`);
|
|
137
164
|
notify({
|
|
138
|
-
title:
|
|
165
|
+
title: `Commit Failed - ${project.name}`,
|
|
139
166
|
message: `Failed to execute commit "${commit.message}": ${msg}`
|
|
140
167
|
});
|
|
141
168
|
await this.reportFailure(commit.id, msg);
|
|
@@ -174,9 +201,9 @@ Run "git pull --rebase && git push" to resolve.`
|
|
|
174
201
|
*/
|
|
175
202
|
findProjectPath(projectId) {
|
|
176
203
|
const allProjects = projectService.getAllLinkedProjects();
|
|
177
|
-
for (const [
|
|
204
|
+
for (const [path3, linkedProject] of Object.entries(allProjects)) {
|
|
178
205
|
if (linkedProject.projectId === projectId) {
|
|
179
|
-
return
|
|
206
|
+
return path3;
|
|
180
207
|
}
|
|
181
208
|
}
|
|
182
209
|
return null;
|
|
@@ -197,11 +224,33 @@ Run "git pull --rebase && git push" to resolve.`
|
|
|
197
224
|
var commitQueue = new CommitQueueProcessor();
|
|
198
225
|
|
|
199
226
|
// src/services/websocket.ts
|
|
200
|
-
import
|
|
227
|
+
import Echo from "laravel-echo";
|
|
228
|
+
import Pusher from "pusher-js";
|
|
229
|
+
import fs from "fs";
|
|
230
|
+
import os from "os";
|
|
231
|
+
import path2 from "path";
|
|
232
|
+
var STATUS_FILE_PATH = path2.join(os.homedir(), ".config", "stint", "daemon.status.json");
|
|
233
|
+
function writeStatus(update) {
|
|
234
|
+
try {
|
|
235
|
+
const dir = path2.dirname(STATUS_FILE_PATH);
|
|
236
|
+
if (!fs.existsSync(dir)) {
|
|
237
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
238
|
+
}
|
|
239
|
+
let status = { websocket: { connected: false } };
|
|
240
|
+
if (fs.existsSync(STATUS_FILE_PATH)) {
|
|
241
|
+
try {
|
|
242
|
+
status = JSON.parse(fs.readFileSync(STATUS_FILE_PATH, "utf8"));
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
status.websocket = { ...status.websocket, ...update };
|
|
247
|
+
fs.writeFileSync(STATUS_FILE_PATH, JSON.stringify(status, null, 2));
|
|
248
|
+
} catch {
|
|
249
|
+
}
|
|
250
|
+
}
|
|
201
251
|
var WebSocketServiceImpl = class {
|
|
202
|
-
|
|
252
|
+
echo = null;
|
|
203
253
|
userId = null;
|
|
204
|
-
socketId = null;
|
|
205
254
|
reconnectAttempts = 0;
|
|
206
255
|
maxReconnectAttempts = 10;
|
|
207
256
|
reconnectTimer = null;
|
|
@@ -215,7 +264,7 @@ var WebSocketServiceImpl = class {
|
|
|
215
264
|
agentDisconnectedHandlers = [];
|
|
216
265
|
syncRequestedHandlers = [];
|
|
217
266
|
/**
|
|
218
|
-
* Connect to the WebSocket server
|
|
267
|
+
* Connect to the WebSocket server using Laravel Echo
|
|
219
268
|
* @throws Error if connection fails or no auth token available
|
|
220
269
|
*/
|
|
221
270
|
async connect() {
|
|
@@ -224,31 +273,117 @@ var WebSocketServiceImpl = class {
|
|
|
224
273
|
if (!token) {
|
|
225
274
|
throw new Error("No authentication token available");
|
|
226
275
|
}
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
276
|
+
const reverbAppKey = config.getReverbAppKey();
|
|
277
|
+
if (!reverbAppKey) {
|
|
278
|
+
throw new Error("Reverb app key not configured");
|
|
279
|
+
}
|
|
280
|
+
const apiUrl = config.getApiUrl();
|
|
281
|
+
const environment = config.getEnvironment();
|
|
282
|
+
let wsHost;
|
|
283
|
+
let wsPort;
|
|
284
|
+
let forceTLS;
|
|
285
|
+
if (environment === "development") {
|
|
286
|
+
wsHost = "localhost";
|
|
287
|
+
wsPort = 8080;
|
|
288
|
+
forceTLS = false;
|
|
289
|
+
} else {
|
|
290
|
+
wsHost = "stint.codes";
|
|
291
|
+
wsPort = 443;
|
|
292
|
+
forceTLS = true;
|
|
293
|
+
}
|
|
294
|
+
logger.info("websocket", `Connecting to ${wsHost}:${wsPort} with key ${reverbAppKey}...`);
|
|
295
|
+
const pusherClient = new Pusher(reverbAppKey, {
|
|
296
|
+
wsHost,
|
|
297
|
+
wsPort,
|
|
298
|
+
forceTLS,
|
|
299
|
+
enabledTransports: ["ws", "wss"],
|
|
300
|
+
disableStats: true,
|
|
301
|
+
cluster: "",
|
|
302
|
+
// Required but unused for Reverb
|
|
303
|
+
authorizer: (channel) => ({
|
|
304
|
+
authorize: async (socketId, callback) => {
|
|
305
|
+
try {
|
|
306
|
+
const response = await fetch(`${apiUrl}/api/broadcasting/auth`, {
|
|
307
|
+
method: "POST",
|
|
308
|
+
headers: {
|
|
309
|
+
"Authorization": `Bearer ${token}`,
|
|
310
|
+
"Accept": "application/json",
|
|
311
|
+
"Content-Type": "application/json"
|
|
312
|
+
},
|
|
313
|
+
body: JSON.stringify({
|
|
314
|
+
socket_id: socketId,
|
|
315
|
+
channel_name: channel.name
|
|
316
|
+
})
|
|
317
|
+
});
|
|
318
|
+
if (!response.ok) {
|
|
319
|
+
const errorText = await response.text();
|
|
320
|
+
logger.error("websocket", `Auth failed (${response.status}): ${errorText}`);
|
|
321
|
+
callback(new Error(`Auth failed: ${response.status}`));
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const data = await response.json();
|
|
325
|
+
callback(null, data);
|
|
326
|
+
} catch (error) {
|
|
327
|
+
logger.error("websocket", "Channel auth error", error);
|
|
328
|
+
callback(error);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
});
|
|
333
|
+
this.echo = new Echo({
|
|
334
|
+
broadcaster: "reverb",
|
|
335
|
+
key: reverbAppKey,
|
|
336
|
+
wsHost,
|
|
337
|
+
wsPort,
|
|
338
|
+
forceTLS,
|
|
339
|
+
disableStats: true,
|
|
340
|
+
enabledTransports: ["ws", "wss"],
|
|
341
|
+
authEndpoint: `${apiUrl}/api/broadcasting/auth`,
|
|
342
|
+
auth: {
|
|
343
|
+
headers: {
|
|
344
|
+
Authorization: `Bearer ${token}`,
|
|
345
|
+
Accept: "application/json"
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
client: pusherClient
|
|
349
|
+
});
|
|
350
|
+
logger.info("websocket", "Echo instance created, setting up connection handlers...");
|
|
231
351
|
return new Promise((resolve, reject) => {
|
|
232
|
-
if (!this.
|
|
233
|
-
reject(new Error("
|
|
352
|
+
if (!this.echo) {
|
|
353
|
+
reject(new Error("Echo not initialized"));
|
|
234
354
|
return;
|
|
235
355
|
}
|
|
236
|
-
|
|
237
|
-
|
|
356
|
+
const connectionTimeout = setTimeout(() => {
|
|
357
|
+
const state = this.echo?.connector.pusher.connection.state || "unknown";
|
|
358
|
+
logger.error("websocket", `Connection timeout after 15s (state: ${state})`);
|
|
359
|
+
reject(new Error(`Connection timeout - stuck in state: ${state}`));
|
|
360
|
+
}, 15e3);
|
|
361
|
+
this.echo.connector.pusher.connection.bind("state_change", (states) => {
|
|
362
|
+
logger.info("websocket", `Connection state: ${states.previous} -> ${states.current}`);
|
|
363
|
+
});
|
|
364
|
+
this.echo.connector.pusher.connection.bind("connected", () => {
|
|
365
|
+
clearTimeout(connectionTimeout);
|
|
366
|
+
logger.success("websocket", "\u2705 Connected to Broadcaster via Sanctum");
|
|
367
|
+
writeStatus({ connected: true });
|
|
238
368
|
this.reconnectAttempts = 0;
|
|
239
369
|
this.isManualDisconnect = false;
|
|
240
370
|
resolve();
|
|
241
371
|
});
|
|
242
|
-
this.
|
|
243
|
-
|
|
372
|
+
this.echo.connector.pusher.connection.bind("error", (error) => {
|
|
373
|
+
clearTimeout(connectionTimeout);
|
|
374
|
+
const errorMessage = error instanceof Error ? error.message : JSON.stringify(error) || "Unknown connection error";
|
|
375
|
+
logger.error("websocket", `WebSocket error: ${errorMessage}`);
|
|
376
|
+
reject(new Error(errorMessage));
|
|
244
377
|
});
|
|
245
|
-
this.
|
|
378
|
+
this.echo.connector.pusher.connection.bind("disconnected", () => {
|
|
246
379
|
logger.warn("websocket", "WebSocket disconnected");
|
|
380
|
+
writeStatus({ connected: false });
|
|
247
381
|
this.handleDisconnect();
|
|
248
382
|
});
|
|
249
|
-
this.
|
|
250
|
-
|
|
251
|
-
|
|
383
|
+
this.echo.connector.pusher.connection.bind("failed", () => {
|
|
384
|
+
clearTimeout(connectionTimeout);
|
|
385
|
+
logger.error("websocket", "WebSocket connection failed");
|
|
386
|
+
reject(new Error("WebSocket connection failed"));
|
|
252
387
|
});
|
|
253
388
|
});
|
|
254
389
|
} catch (error) {
|
|
@@ -266,17 +401,12 @@ var WebSocketServiceImpl = class {
|
|
|
266
401
|
clearTimeout(this.reconnectTimer);
|
|
267
402
|
this.reconnectTimer = null;
|
|
268
403
|
}
|
|
269
|
-
if (this.
|
|
404
|
+
if (this.echo) {
|
|
270
405
|
if (this.userId) {
|
|
271
|
-
this.
|
|
272
|
-
event: "pusher:unsubscribe",
|
|
273
|
-
data: {
|
|
274
|
-
channel: `private-user.${this.userId}`
|
|
275
|
-
}
|
|
276
|
-
});
|
|
406
|
+
this.echo.leave(`user.${this.userId}`);
|
|
277
407
|
}
|
|
278
|
-
this.
|
|
279
|
-
this.
|
|
408
|
+
this.echo.disconnect();
|
|
409
|
+
this.echo = null;
|
|
280
410
|
logger.info("websocket", "WebSocket disconnected");
|
|
281
411
|
}
|
|
282
412
|
}
|
|
@@ -285,38 +415,55 @@ var WebSocketServiceImpl = class {
|
|
|
285
415
|
* @returns True if connected and ready
|
|
286
416
|
*/
|
|
287
417
|
isConnected() {
|
|
288
|
-
return this.
|
|
418
|
+
return this.echo !== null && this.echo.connector.pusher.connection.state === "connected";
|
|
289
419
|
}
|
|
290
420
|
/**
|
|
291
|
-
* Subscribe to user-specific channel for real-time updates
|
|
421
|
+
* Subscribe to user-specific private channel for real-time updates
|
|
292
422
|
* @param userId - User ID to subscribe to
|
|
293
423
|
*/
|
|
294
424
|
async subscribeToUserChannel(userId) {
|
|
295
425
|
this.userId = userId;
|
|
296
|
-
if (this.
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
this.sendMessage({
|
|
300
|
-
event: "pusher:subscribe",
|
|
301
|
-
data: {
|
|
302
|
-
channel
|
|
303
|
-
}
|
|
304
|
-
});
|
|
426
|
+
if (!this.echo) {
|
|
427
|
+
logger.warn("websocket", "Cannot subscribe: not connected");
|
|
428
|
+
return;
|
|
305
429
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
430
|
+
if (!this.isConnected()) {
|
|
431
|
+
logger.warn("websocket", "Cannot subscribe: not connected");
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const channel = `user.${userId}`;
|
|
435
|
+
logger.info("websocket", `Subscribing to private channel: ${channel}`);
|
|
436
|
+
const privateChannel = this.echo.private(channel);
|
|
437
|
+
writeStatus({ channel });
|
|
438
|
+
privateChannel.listen(".commit.approved", (data) => {
|
|
439
|
+
logger.info("websocket", `Commit approved: ${data.pendingCommit.id}`);
|
|
440
|
+
writeStatus({ lastEvent: "commit.approved", lastEventTime: (/* @__PURE__ */ new Date()).toISOString() });
|
|
441
|
+
this.commitApprovedHandlers.forEach(
|
|
442
|
+
(handler) => handler(data.pendingCommit, data.pendingCommit.project)
|
|
443
|
+
);
|
|
444
|
+
}).listen(".commit.pending", (data) => {
|
|
445
|
+
logger.info("websocket", `Commit pending: ${data.pendingCommit.id}`);
|
|
446
|
+
writeStatus({ lastEvent: "commit.pending", lastEventTime: (/* @__PURE__ */ new Date()).toISOString() });
|
|
447
|
+
this.commitPendingHandlers.forEach((handler) => handler(data.pendingCommit));
|
|
448
|
+
}).listen(".suggestion.created", (data) => {
|
|
449
|
+
logger.info("websocket", `Suggestion created: ${data.suggestion.id}`);
|
|
450
|
+
writeStatus({ lastEvent: "suggestion.created", lastEventTime: (/* @__PURE__ */ new Date()).toISOString() });
|
|
451
|
+
this.suggestionCreatedHandlers.forEach((handler) => handler(data.suggestion));
|
|
452
|
+
}).listen(".project.updated", (data) => {
|
|
453
|
+
logger.info("websocket", `Project updated: ${data.project.id}`);
|
|
454
|
+
writeStatus({ lastEvent: "project.updated", lastEventTime: (/* @__PURE__ */ new Date()).toISOString() });
|
|
455
|
+
this.projectUpdatedHandlers.forEach((handler) => handler(data.project));
|
|
456
|
+
}).listen(".sync.requested", (data) => {
|
|
457
|
+
logger.info("websocket", `Sync requested for project: ${data.project.id}`);
|
|
458
|
+
writeStatus({ lastEvent: "sync.requested", lastEventTime: (/* @__PURE__ */ new Date()).toISOString() });
|
|
459
|
+
this.syncRequestedHandlers.forEach((handler) => handler(data.project.id));
|
|
460
|
+
}).listen(".agent.disconnected", (data) => {
|
|
461
|
+
const reason = data.reason ?? "Server requested disconnect";
|
|
462
|
+
logger.warn("websocket", `Agent disconnected by server: ${reason}`);
|
|
463
|
+
writeStatus({ lastEvent: "agent.disconnected", lastEventTime: (/* @__PURE__ */ new Date()).toISOString() });
|
|
464
|
+
this.agentDisconnectedHandlers.forEach((handler) => handler(reason));
|
|
318
465
|
});
|
|
319
|
-
|
|
466
|
+
logger.success("websocket", `Subscribed to private channel: ${channel}`);
|
|
320
467
|
}
|
|
321
468
|
/**
|
|
322
469
|
* Register handler for commit approved events
|
|
@@ -343,98 +490,7 @@ var WebSocketServiceImpl = class {
|
|
|
343
490
|
onSyncRequested(handler) {
|
|
344
491
|
this.syncRequestedHandlers.push(handler);
|
|
345
492
|
}
|
|
346
|
-
sendMessage(message) {
|
|
347
|
-
if (!this.isConnected()) {
|
|
348
|
-
logger.warn("websocket", "Cannot send message: not connected");
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
this.ws.send(JSON.stringify(message));
|
|
352
|
-
}
|
|
353
|
-
async handleMessage(data) {
|
|
354
|
-
try {
|
|
355
|
-
const message = JSON.parse(data.toString());
|
|
356
|
-
logger.info("websocket", `Received message: ${message.event}`);
|
|
357
|
-
if (message.event === "pusher:connection_established") {
|
|
358
|
-
try {
|
|
359
|
-
const connectionData = typeof message.data === "string" ? JSON.parse(message.data) : message.data;
|
|
360
|
-
this.socketId = connectionData.socket_id;
|
|
361
|
-
logger.success("websocket", `Connection established (socket_id: ${this.socketId})`);
|
|
362
|
-
if (this.userId) {
|
|
363
|
-
this.subscribeToUserChannel(this.userId);
|
|
364
|
-
}
|
|
365
|
-
} catch (error) {
|
|
366
|
-
logger.success("websocket", "Connection established");
|
|
367
|
-
}
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
if (message.event === "pusher_internal:subscription_succeeded") {
|
|
371
|
-
logger.success("websocket", `Subscribed to channel: ${message.channel}`);
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
if (message.event === "pusher:error") {
|
|
375
|
-
try {
|
|
376
|
-
const errorData = typeof message.data === "string" ? JSON.parse(message.data) : message.data;
|
|
377
|
-
const errorCode = errorData.code;
|
|
378
|
-
const errorMessage = errorData.message;
|
|
379
|
-
logger.error("websocket", `WebSocket error (${errorCode}): ${errorMessage}`);
|
|
380
|
-
if (errorCode === 4001) {
|
|
381
|
-
logger.error("websocket", "Application does not exist - check Reverb app key configuration");
|
|
382
|
-
} else if (errorCode === 4009) {
|
|
383
|
-
logger.error("websocket", "Connection is unauthorized - authentication token may be invalid or expired");
|
|
384
|
-
const { notify: notify2 } = await import("./notify-NXUEEO7M.js");
|
|
385
|
-
notify2({
|
|
386
|
-
title: "Stint Agent - Connection Issue",
|
|
387
|
-
message: "WebSocket authentication failed. Notifications may be delayed (falling back to polling)."
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
} catch (parseError) {
|
|
391
|
-
logger.error("websocket", `WebSocket error: ${JSON.stringify(message.data)}`);
|
|
392
|
-
}
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
if (message.event === "commit.approved") {
|
|
396
|
-
const { pendingCommit } = message.data;
|
|
397
|
-
logger.info("websocket", `Commit approved: ${pendingCommit.id}`);
|
|
398
|
-
this.commitApprovedHandlers.forEach((handler) => handler(pendingCommit, pendingCommit.project));
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
if (message.event === "commit.pending") {
|
|
402
|
-
const { pendingCommit } = message.data;
|
|
403
|
-
logger.info("websocket", `Commit pending: ${pendingCommit.id}`);
|
|
404
|
-
this.commitPendingHandlers.forEach((handler) => handler(pendingCommit));
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
if (message.event === "suggestion.created") {
|
|
408
|
-
const { suggestion } = message.data;
|
|
409
|
-
logger.info("websocket", `Suggestion created: ${suggestion.id}`);
|
|
410
|
-
this.suggestionCreatedHandlers.forEach((handler) => handler(suggestion));
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
if (message.event === "project.updated") {
|
|
414
|
-
const { project } = message.data;
|
|
415
|
-
logger.info("websocket", `Project updated: ${project.id}`);
|
|
416
|
-
this.projectUpdatedHandlers.forEach((handler) => handler(project));
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
if (message.event === "sync.requested") {
|
|
420
|
-
const { project } = message.data;
|
|
421
|
-
logger.info("websocket", `Sync requested for project: ${project.id}`);
|
|
422
|
-
this.syncRequestedHandlers.forEach((handler) => handler(project.id));
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
if (message.event === "agent.disconnected") {
|
|
426
|
-
const { reason } = message.data;
|
|
427
|
-
logger.warn("websocket", `Agent disconnected by server: ${reason ?? "Server requested disconnect"}`);
|
|
428
|
-
this.agentDisconnectedHandlers.forEach((handler) => handler(reason ?? "Server requested disconnect"));
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
logger.info("websocket", `Unhandled event: ${message.event}, payload: ${JSON.stringify(message)}`);
|
|
432
|
-
} catch (error) {
|
|
433
|
-
logger.error("websocket", "Failed to parse message", error);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
493
|
handleDisconnect() {
|
|
437
|
-
this.ws = null;
|
|
438
494
|
this.disconnectHandlers.forEach((handler) => handler());
|
|
439
495
|
if (this.isManualDisconnect) {
|
|
440
496
|
return;
|
|
@@ -472,6 +528,7 @@ var WebSocketServiceImpl = class {
|
|
|
472
528
|
var websocketService = new WebSocketServiceImpl();
|
|
473
529
|
|
|
474
530
|
export {
|
|
531
|
+
notify,
|
|
475
532
|
commitQueue,
|
|
476
533
|
websocketService
|
|
477
534
|
};
|
|
@@ -214,7 +214,95 @@ var Logger = class {
|
|
|
214
214
|
};
|
|
215
215
|
var logger = new Logger();
|
|
216
216
|
|
|
217
|
+
// src/utils/crypto.ts
|
|
218
|
+
import crypto from "crypto";
|
|
219
|
+
import os3 from "os";
|
|
220
|
+
function getMachineKey() {
|
|
221
|
+
const machineInfo = `${os3.hostname()}-${os3.platform()}-${os3.arch()}`;
|
|
222
|
+
return crypto.createHash("sha256").update(machineInfo).digest();
|
|
223
|
+
}
|
|
224
|
+
var ALGORITHM = "aes-256-gcm";
|
|
225
|
+
var IV_LENGTH = 16;
|
|
226
|
+
var AUTH_TAG_LENGTH = 16;
|
|
227
|
+
function encrypt(text) {
|
|
228
|
+
const key = getMachineKey();
|
|
229
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
230
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
231
|
+
let encrypted = cipher.update(text, "utf8", "hex");
|
|
232
|
+
encrypted += cipher.final("hex");
|
|
233
|
+
const authTag = cipher.getAuthTag();
|
|
234
|
+
return iv.toString("hex") + authTag.toString("hex") + encrypted;
|
|
235
|
+
}
|
|
236
|
+
function decrypt(encryptedText) {
|
|
237
|
+
const key = getMachineKey();
|
|
238
|
+
const iv = Buffer.from(encryptedText.slice(0, IV_LENGTH * 2), "hex");
|
|
239
|
+
const authTag = Buffer.from(
|
|
240
|
+
encryptedText.slice(IV_LENGTH * 2, (IV_LENGTH + AUTH_TAG_LENGTH) * 2),
|
|
241
|
+
"hex"
|
|
242
|
+
);
|
|
243
|
+
const encrypted = encryptedText.slice((IV_LENGTH + AUTH_TAG_LENGTH) * 2);
|
|
244
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
245
|
+
decipher.setAuthTag(authTag);
|
|
246
|
+
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
247
|
+
decrypted += decipher.final("utf8");
|
|
248
|
+
return decrypted;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/services/auth.ts
|
|
252
|
+
var AuthServiceImpl = class {
|
|
253
|
+
async saveToken(token) {
|
|
254
|
+
try {
|
|
255
|
+
const encryptedToken = encrypt(token);
|
|
256
|
+
config.setToken(encryptedToken);
|
|
257
|
+
logger.info("auth", "Token saved successfully");
|
|
258
|
+
} catch (error) {
|
|
259
|
+
logger.error("auth", "Failed to save token", error);
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async getToken() {
|
|
264
|
+
try {
|
|
265
|
+
const encryptedToken = config.getToken();
|
|
266
|
+
if (!encryptedToken) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
return decrypt(encryptedToken);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
logger.error("auth", "Failed to decrypt token", error);
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async clearToken() {
|
|
276
|
+
config.clearToken();
|
|
277
|
+
logger.info("auth", "Token cleared");
|
|
278
|
+
}
|
|
279
|
+
async validateToken() {
|
|
280
|
+
const token = await this.getToken();
|
|
281
|
+
if (!token) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
const { apiService } = await import("./api-6EGHLTCT.js");
|
|
286
|
+
const user = await apiService.getCurrentUser();
|
|
287
|
+
logger.info("auth", `Token validated for user: ${user.email}`);
|
|
288
|
+
return user;
|
|
289
|
+
} catch (error) {
|
|
290
|
+
logger.warn("auth", "Token validation failed");
|
|
291
|
+
logger.error("auth", "Failed to validate token", error);
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
getMachineId() {
|
|
296
|
+
return config.getMachineId();
|
|
297
|
+
}
|
|
298
|
+
getMachineName() {
|
|
299
|
+
return config.getMachineName();
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
var authService = new AuthServiceImpl();
|
|
303
|
+
|
|
217
304
|
export {
|
|
218
305
|
config,
|
|
219
|
-
logger
|
|
306
|
+
logger,
|
|
307
|
+
authService
|
|
220
308
|
};
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
authService
|
|
3
|
-
} from "./chunk-7TIF7QZL.js";
|
|
4
|
-
import {
|
|
2
|
+
authService,
|
|
5
3
|
config,
|
|
6
4
|
logger
|
|
7
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-WETVBZ6Z.js";
|
|
8
6
|
|
|
9
7
|
// src/utils/circuit-breaker.ts
|
|
10
8
|
var CircuitBreaker = class {
|
|
@@ -100,7 +98,7 @@ var CircuitBreaker = class {
|
|
|
100
98
|
};
|
|
101
99
|
|
|
102
100
|
// src/services/api.ts
|
|
103
|
-
var AGENT_VERSION = "1.2.
|
|
101
|
+
var AGENT_VERSION = "1.2.18";
|
|
104
102
|
var ApiServiceImpl = class {
|
|
105
103
|
sessionId = null;
|
|
106
104
|
circuitBreaker = new CircuitBreaker({
|
package/dist/daemon/runner.js
CHANGED
|
@@ -1,149 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
commitQueue,
|
|
4
|
+
notify,
|
|
4
5
|
websocketService
|
|
5
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-SXPP272L.js";
|
|
6
7
|
import {
|
|
7
8
|
apiService
|
|
8
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-XPZNWXB4.js";
|
|
9
10
|
import {
|
|
10
11
|
gitService,
|
|
11
12
|
projectService,
|
|
12
13
|
removePidFile,
|
|
13
14
|
writePidFile
|
|
14
|
-
} from "../chunk-
|
|
15
|
-
import {
|
|
16
|
-
authService
|
|
17
|
-
} from "../chunk-7TIF7QZL.js";
|
|
18
|
-
import {
|
|
19
|
-
notify
|
|
20
|
-
} from "../chunk-DCY3EXDX.js";
|
|
15
|
+
} from "../chunk-A2CVSQ3K.js";
|
|
21
16
|
import {
|
|
17
|
+
authService,
|
|
22
18
|
logger
|
|
23
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-WETVBZ6Z.js";
|
|
24
20
|
|
|
25
21
|
// src/daemon/runner.ts
|
|
26
22
|
import "dotenv/config";
|
|
27
23
|
|
|
28
|
-
// src/services/polling.ts
|
|
29
|
-
var PollingServiceImpl = class {
|
|
30
|
-
interval = null;
|
|
31
|
-
knownCommitIds = /* @__PURE__ */ new Set();
|
|
32
|
-
knownProjects = /* @__PURE__ */ new Map();
|
|
33
|
-
commitApprovedHandlers = [];
|
|
34
|
-
projectUpdatedHandlers = [];
|
|
35
|
-
isFirstRun = true;
|
|
36
|
-
isPolling = false;
|
|
37
|
-
/**
|
|
38
|
-
* Start polling for pending commits
|
|
39
|
-
* @param intervalMs - Polling interval in milliseconds (default: 10000)
|
|
40
|
-
*/
|
|
41
|
-
start(intervalMs = 1e4) {
|
|
42
|
-
if (this.interval) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
logger.info("polling", `Starting polling service (interval: ${intervalMs}ms)`);
|
|
46
|
-
this.poll();
|
|
47
|
-
this.interval = setInterval(() => {
|
|
48
|
-
this.poll();
|
|
49
|
-
}, intervalMs);
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Stop polling
|
|
53
|
-
*/
|
|
54
|
-
stop() {
|
|
55
|
-
if (this.interval) {
|
|
56
|
-
clearInterval(this.interval);
|
|
57
|
-
this.interval = null;
|
|
58
|
-
logger.info("polling", "Stopping polling service");
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Register handler for commit approved event
|
|
63
|
-
* @param handler - Function to call when a commit is approved where it was previously unknown
|
|
64
|
-
*/
|
|
65
|
-
onCommitApproved(handler) {
|
|
66
|
-
this.commitApprovedHandlers.push(handler);
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Register handler for project updated event
|
|
70
|
-
* @param handler - Function to call when a project is updated
|
|
71
|
-
*/
|
|
72
|
-
onProjectUpdated(handler) {
|
|
73
|
-
this.projectUpdatedHandlers.push(handler);
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Poll for updates
|
|
77
|
-
*/
|
|
78
|
-
async poll() {
|
|
79
|
-
if (this.isPolling) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
this.isPolling = true;
|
|
83
|
-
try {
|
|
84
|
-
const linkedProjects = projectService.getAllLinkedProjects();
|
|
85
|
-
const projectIds = Object.values(linkedProjects).map((p) => p.projectId);
|
|
86
|
-
if (projectIds.length === 0) {
|
|
87
|
-
this.isFirstRun = false;
|
|
88
|
-
this.isPolling = false;
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const apiProjects = await apiService.getLinkedProjects();
|
|
92
|
-
const projects = apiProjects.filter((p) => projectIds.includes(p.id));
|
|
93
|
-
for (const project of projects) {
|
|
94
|
-
const cachedProject = this.knownProjects.get(project.id);
|
|
95
|
-
if (cachedProject) {
|
|
96
|
-
if (cachedProject.updatedAt !== project.updatedAt) {
|
|
97
|
-
if (!this.isFirstRun) {
|
|
98
|
-
logger.info("polling", `Project update detected: ${project.id}`);
|
|
99
|
-
this.notifyProjectUpdated(project);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
this.knownProjects.set(project.id, project);
|
|
104
|
-
try {
|
|
105
|
-
const commits = await apiService.getPendingCommits(project.id);
|
|
106
|
-
for (const commit of commits) {
|
|
107
|
-
if (!this.knownCommitIds.has(commit.id)) {
|
|
108
|
-
this.knownCommitIds.add(commit.id);
|
|
109
|
-
if (!this.isFirstRun) {
|
|
110
|
-
logger.info("polling", `New pending commit detected: ${commit.id}`);
|
|
111
|
-
this.notifyCommitApproved(commit, project);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
} catch (error) {
|
|
116
|
-
logger.debug("polling", `Failed to poll project ${project.id}: ${error.message}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
this.isFirstRun = false;
|
|
120
|
-
} catch (error) {
|
|
121
|
-
logger.error("polling", "Poll cycle failed", error);
|
|
122
|
-
} finally {
|
|
123
|
-
this.isPolling = false;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
notifyCommitApproved(commit, project) {
|
|
127
|
-
this.commitApprovedHandlers.forEach((handler) => {
|
|
128
|
-
try {
|
|
129
|
-
handler(commit, project);
|
|
130
|
-
} catch (error) {
|
|
131
|
-
logger.error("polling", "Error in commit approved handler", error);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
notifyProjectUpdated(project) {
|
|
136
|
-
this.projectUpdatedHandlers.forEach((handler) => {
|
|
137
|
-
try {
|
|
138
|
-
handler(project);
|
|
139
|
-
} catch (error) {
|
|
140
|
-
logger.error("polling", "Error in project updated handler", error);
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
var pollingService = new PollingServiceImpl();
|
|
146
|
-
|
|
147
24
|
// src/daemon/watcher.ts
|
|
148
25
|
import fs from "fs";
|
|
149
26
|
var FileWatcher = class {
|
|
@@ -286,9 +163,16 @@ var FileWatcher = class {
|
|
|
286
163
|
const repoInfo = await gitService.getRepoInfo(projectPath);
|
|
287
164
|
await apiService.syncProject(projectId, repoInfo);
|
|
288
165
|
logger.success("watcher", `Synced project ${projectId}`);
|
|
166
|
+
let projectName = projectId;
|
|
167
|
+
try {
|
|
168
|
+
const projects = await apiService.getLinkedProjects();
|
|
169
|
+
const p = projects.find((proj) => proj.id === projectId);
|
|
170
|
+
if (p) projectName = p.name;
|
|
171
|
+
} catch (_e) {
|
|
172
|
+
}
|
|
289
173
|
notify({
|
|
290
174
|
title: "Sync Complete",
|
|
291
|
-
message: `Project ${
|
|
175
|
+
message: `Project "${projectName}" synced successfully.`
|
|
292
176
|
});
|
|
293
177
|
} catch (error) {
|
|
294
178
|
logger.error("watcher", `Failed to sync project ${projectId}`, error);
|
|
@@ -384,19 +268,18 @@ Project: ${project.name}`
|
|
|
384
268
|
});
|
|
385
269
|
commitQueue.addToQueue(commit, project);
|
|
386
270
|
});
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
notify({
|
|
390
|
-
title: "Commit Approved",
|
|
391
|
-
message: `${commit.message}
|
|
392
|
-
Project: ${project.name}`
|
|
393
|
-
});
|
|
394
|
-
commitQueue.addToQueue(commit, project);
|
|
395
|
-
});
|
|
396
|
-
websocketService.onCommitPending((commit) => {
|
|
271
|
+
websocketService.onCommitPending(async (data) => {
|
|
272
|
+
const commit = data;
|
|
397
273
|
logger.info("daemon", `Commit pending: ${commit.id}`);
|
|
274
|
+
let projectName = commit.projectId;
|
|
275
|
+
try {
|
|
276
|
+
const projects = await apiService.getLinkedProjects();
|
|
277
|
+
const p = projects.find((proj) => proj.id === commit.projectId);
|
|
278
|
+
if (p) projectName = p.name;
|
|
279
|
+
} catch (_e) {
|
|
280
|
+
}
|
|
398
281
|
notify({
|
|
399
|
-
title:
|
|
282
|
+
title: `New Proposal - ${projectName}`,
|
|
400
283
|
message: commit.message
|
|
401
284
|
});
|
|
402
285
|
});
|
|
@@ -407,13 +290,6 @@ Project: ${project.name}`
|
|
|
407
290
|
message: project.name
|
|
408
291
|
});
|
|
409
292
|
});
|
|
410
|
-
pollingService.onProjectUpdated((project) => {
|
|
411
|
-
logger.info("daemon", `Project updated (via polling): ${project.id} - ${project.name}`);
|
|
412
|
-
notify({
|
|
413
|
-
title: "Project Updated",
|
|
414
|
-
message: project.name
|
|
415
|
-
});
|
|
416
|
-
});
|
|
417
293
|
websocketService.onDisconnect(() => {
|
|
418
294
|
logger.warn("daemon", "WebSocket disconnected, will attempt to reconnect");
|
|
419
295
|
});
|
|
@@ -436,9 +312,18 @@ Priority: ${suggestion.priority}`,
|
|
|
436
312
|
websocketService.onSyncRequested(async (projectId) => {
|
|
437
313
|
logger.info("daemon", `Server requested sync for project: ${projectId}`);
|
|
438
314
|
try {
|
|
315
|
+
let projectName = projectId;
|
|
316
|
+
try {
|
|
317
|
+
const projects = await apiService.getLinkedProjects();
|
|
318
|
+
const project = projects.find((p) => p.id === projectId);
|
|
319
|
+
if (project) {
|
|
320
|
+
projectName = project.name;
|
|
321
|
+
}
|
|
322
|
+
} catch (_e) {
|
|
323
|
+
}
|
|
439
324
|
notify({
|
|
440
325
|
title: "Sync Requested",
|
|
441
|
-
message: `Syncing project ${
|
|
326
|
+
message: `Syncing project "${projectName}"...`
|
|
442
327
|
});
|
|
443
328
|
await fileWatcher.syncProjectById(projectId);
|
|
444
329
|
} catch (error) {
|
|
@@ -453,7 +338,6 @@ Priority: ${suggestion.priority}`,
|
|
|
453
338
|
logger.info("daemon", `Linked project: ${project.projectId}`);
|
|
454
339
|
fileWatcher.watchProject(project.projectId);
|
|
455
340
|
});
|
|
456
|
-
pollingService.start();
|
|
457
341
|
logger.info("daemon", "Daemon started successfully");
|
|
458
342
|
const projectEntries = Object.entries(linkedProjects);
|
|
459
343
|
if (projectEntries.length > 0) {
|
package/dist/index.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
commitQueue,
|
|
4
4
|
websocketService
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-SXPP272L.js";
|
|
6
6
|
import {
|
|
7
7
|
apiService
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-XPZNWXB4.js";
|
|
9
9
|
import {
|
|
10
10
|
getPidFilePath,
|
|
11
11
|
gitService,
|
|
@@ -14,15 +14,12 @@ import {
|
|
|
14
14
|
projectService,
|
|
15
15
|
spawnDetached,
|
|
16
16
|
validatePidFile
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import {
|
|
19
|
-
authService
|
|
20
|
-
} from "./chunk-7TIF7QZL.js";
|
|
21
|
-
import "./chunk-DCY3EXDX.js";
|
|
17
|
+
} from "./chunk-A2CVSQ3K.js";
|
|
22
18
|
import {
|
|
19
|
+
authService,
|
|
23
20
|
config,
|
|
24
21
|
logger
|
|
25
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-WETVBZ6Z.js";
|
|
26
23
|
|
|
27
24
|
// src/index.ts
|
|
28
25
|
import "dotenv/config";
|
|
@@ -623,7 +620,7 @@ function registerStatusCommand(program2) {
|
|
|
623
620
|
try {
|
|
624
621
|
const { render } = await import("ink");
|
|
625
622
|
const { createElement } = await import("react");
|
|
626
|
-
const { StatusDashboard } = await import("./StatusDashboard-
|
|
623
|
+
const { StatusDashboard } = await import("./StatusDashboard-KCHNLVD7.js");
|
|
627
624
|
render(createElement(StatusDashboard, { cwd }));
|
|
628
625
|
return;
|
|
629
626
|
} catch (error) {
|
|
@@ -784,9 +781,8 @@ function getProcessStats(pid) {
|
|
|
784
781
|
} else if (platform === "win32") {
|
|
785
782
|
return getWindowsStats(pid);
|
|
786
783
|
}
|
|
787
|
-
|
|
788
|
-
} catch
|
|
789
|
-
logger.error("monitor", `Failed to get process stats for PID ${pid}`, error);
|
|
784
|
+
return null;
|
|
785
|
+
} catch {
|
|
790
786
|
return null;
|
|
791
787
|
}
|
|
792
788
|
}
|
|
@@ -1025,6 +1021,30 @@ function registerDaemonCommands(program2) {
|
|
|
1025
1021
|
console.log(`${chalk8.bold("Threads:")} ${stats.threads}`);
|
|
1026
1022
|
console.log(`${chalk8.bold("Uptime:")} ${formatUptime(stats.uptime)}`);
|
|
1027
1023
|
}
|
|
1024
|
+
const statusPath = path3.join(os3.homedir(), ".config", "stint", "daemon.status.json");
|
|
1025
|
+
if (fs.existsSync(statusPath)) {
|
|
1026
|
+
try {
|
|
1027
|
+
const statusData = JSON.parse(fs.readFileSync(statusPath, "utf8"));
|
|
1028
|
+
console.log(chalk8.blue("\n\u{1F4E1} WebSocket Status:"));
|
|
1029
|
+
console.log(chalk8.gray("\u2500".repeat(50)));
|
|
1030
|
+
if (statusData.websocket?.connected) {
|
|
1031
|
+
console.log(`${chalk8.bold("Connected:")} ${chalk8.green("\u2713 Yes")}`);
|
|
1032
|
+
if (statusData.websocket.channel) {
|
|
1033
|
+
console.log(`${chalk8.bold("Channel:")} ${statusData.websocket.channel}`);
|
|
1034
|
+
}
|
|
1035
|
+
} else {
|
|
1036
|
+
console.log(`${chalk8.bold("Connected:")} ${chalk8.yellow("\u2717 No")}`);
|
|
1037
|
+
}
|
|
1038
|
+
if (statusData.websocket?.lastEvent) {
|
|
1039
|
+
console.log(`${chalk8.bold("Last Event:")} ${statusData.websocket.lastEvent}`);
|
|
1040
|
+
if (statusData.websocket.lastEventTime) {
|
|
1041
|
+
const ago = Math.floor((Date.now() - new Date(statusData.websocket.lastEventTime).getTime()) / 1e3);
|
|
1042
|
+
console.log(`${chalk8.bold("Event Time:")} ${formatUptime(ago)} ago`);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
} catch {
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1028
1048
|
} else {
|
|
1029
1049
|
console.log(`${chalk8.bold("Status:")} ${chalk8.yellow("Not running")}`);
|
|
1030
1050
|
console.log(chalk8.gray('Run "stint daemon start" to start the daemon.'));
|
|
@@ -2003,7 +2023,7 @@ function registerDoctorCommand(program2) {
|
|
|
2003
2023
|
}
|
|
2004
2024
|
|
|
2005
2025
|
// src/index.ts
|
|
2006
|
-
var AGENT_VERSION = "1.2.
|
|
2026
|
+
var AGENT_VERSION = "1.2.18";
|
|
2007
2027
|
var program = new Command();
|
|
2008
2028
|
program.name("stint").description("Stint Agent - Local daemon for Stint Project Assistant").version(AGENT_VERSION, "-v, --version", "output the current version").addHelpText("after", `
|
|
2009
2029
|
${chalk13.bold("Examples:")}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gowelle/stint-agent",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.18",
|
|
4
4
|
"description": "Local agent for Stint - Project Assistant",
|
|
5
5
|
"author": "Gowelle John <gowelle.john@icloud.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,10 +44,12 @@
|
|
|
44
44
|
"conf": "^12.0.0",
|
|
45
45
|
"dotenv": "^17.2.3",
|
|
46
46
|
"ink": "^5.2.1",
|
|
47
|
+
"laravel-echo": "^2.2.6",
|
|
47
48
|
"node-fetch": "^3.3.2",
|
|
48
49
|
"node-notifier": "^10.0.1",
|
|
49
50
|
"open": "^10.0.0",
|
|
50
51
|
"ora": "^8.0.1",
|
|
52
|
+
"pusher-js": "^8.4.0",
|
|
51
53
|
"react": "^18.3.1",
|
|
52
54
|
"simple-git": "^3.22.0",
|
|
53
55
|
"ws": "^8.16.0"
|
package/dist/api-IB5F32WJ.js
DELETED
package/dist/chunk-7TIF7QZL.js
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
config,
|
|
3
|
-
logger
|
|
4
|
-
} from "./chunk-XHXSWLUC.js";
|
|
5
|
-
|
|
6
|
-
// src/utils/crypto.ts
|
|
7
|
-
import crypto from "crypto";
|
|
8
|
-
import os from "os";
|
|
9
|
-
function getMachineKey() {
|
|
10
|
-
const machineInfo = `${os.hostname()}-${os.platform()}-${os.arch()}`;
|
|
11
|
-
return crypto.createHash("sha256").update(machineInfo).digest();
|
|
12
|
-
}
|
|
13
|
-
var ALGORITHM = "aes-256-gcm";
|
|
14
|
-
var IV_LENGTH = 16;
|
|
15
|
-
var AUTH_TAG_LENGTH = 16;
|
|
16
|
-
function encrypt(text) {
|
|
17
|
-
const key = getMachineKey();
|
|
18
|
-
const iv = crypto.randomBytes(IV_LENGTH);
|
|
19
|
-
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
20
|
-
let encrypted = cipher.update(text, "utf8", "hex");
|
|
21
|
-
encrypted += cipher.final("hex");
|
|
22
|
-
const authTag = cipher.getAuthTag();
|
|
23
|
-
return iv.toString("hex") + authTag.toString("hex") + encrypted;
|
|
24
|
-
}
|
|
25
|
-
function decrypt(encryptedText) {
|
|
26
|
-
const key = getMachineKey();
|
|
27
|
-
const iv = Buffer.from(encryptedText.slice(0, IV_LENGTH * 2), "hex");
|
|
28
|
-
const authTag = Buffer.from(
|
|
29
|
-
encryptedText.slice(IV_LENGTH * 2, (IV_LENGTH + AUTH_TAG_LENGTH) * 2),
|
|
30
|
-
"hex"
|
|
31
|
-
);
|
|
32
|
-
const encrypted = encryptedText.slice((IV_LENGTH + AUTH_TAG_LENGTH) * 2);
|
|
33
|
-
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
34
|
-
decipher.setAuthTag(authTag);
|
|
35
|
-
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
36
|
-
decrypted += decipher.final("utf8");
|
|
37
|
-
return decrypted;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// src/services/auth.ts
|
|
41
|
-
var AuthServiceImpl = class {
|
|
42
|
-
async saveToken(token) {
|
|
43
|
-
try {
|
|
44
|
-
const encryptedToken = encrypt(token);
|
|
45
|
-
config.setToken(encryptedToken);
|
|
46
|
-
logger.info("auth", "Token saved successfully");
|
|
47
|
-
} catch (error) {
|
|
48
|
-
logger.error("auth", "Failed to save token", error);
|
|
49
|
-
throw error;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
async getToken() {
|
|
53
|
-
try {
|
|
54
|
-
const encryptedToken = config.getToken();
|
|
55
|
-
if (!encryptedToken) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
return decrypt(encryptedToken);
|
|
59
|
-
} catch (error) {
|
|
60
|
-
logger.error("auth", "Failed to decrypt token", error);
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
async clearToken() {
|
|
65
|
-
config.clearToken();
|
|
66
|
-
logger.info("auth", "Token cleared");
|
|
67
|
-
}
|
|
68
|
-
async validateToken() {
|
|
69
|
-
const token = await this.getToken();
|
|
70
|
-
if (!token) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
try {
|
|
74
|
-
const { apiService } = await import("./api-IB5F32WJ.js");
|
|
75
|
-
const user = await apiService.getCurrentUser();
|
|
76
|
-
logger.info("auth", `Token validated for user: ${user.email}`);
|
|
77
|
-
return user;
|
|
78
|
-
} catch (error) {
|
|
79
|
-
logger.warn("auth", "Token validation failed");
|
|
80
|
-
logger.error("auth", "Failed to validate token", error);
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
getMachineId() {
|
|
85
|
-
return config.getMachineId();
|
|
86
|
-
}
|
|
87
|
-
getMachineName() {
|
|
88
|
-
return config.getMachineName();
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
var authService = new AuthServiceImpl();
|
|
92
|
-
|
|
93
|
-
export {
|
|
94
|
-
authService
|
|
95
|
-
};
|
package/dist/chunk-DCY3EXDX.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
config,
|
|
3
|
-
logger
|
|
4
|
-
} from "./chunk-XHXSWLUC.js";
|
|
5
|
-
|
|
6
|
-
// src/utils/notify.ts
|
|
7
|
-
import notifier from "node-notifier";
|
|
8
|
-
import path from "path";
|
|
9
|
-
import { fileURLToPath } from "url";
|
|
10
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
11
|
-
var __dirname = path.dirname(__filename);
|
|
12
|
-
var DEFAULT_ICON = path.resolve(__dirname, "../assets/logo.png");
|
|
13
|
-
function notify(options) {
|
|
14
|
-
if (!config.areNotificationsEnabled()) {
|
|
15
|
-
logger.debug("notify", "Notifications disabled, skipping notification");
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
try {
|
|
19
|
-
notifier.notify({
|
|
20
|
-
title: options.title,
|
|
21
|
-
message: options.message,
|
|
22
|
-
open: options.open,
|
|
23
|
-
icon: options.icon || DEFAULT_ICON,
|
|
24
|
-
sound: true,
|
|
25
|
-
wait: false,
|
|
26
|
-
appID: "Stint Agent"
|
|
27
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
-
}, (error) => {
|
|
29
|
-
if (error) {
|
|
30
|
-
logger.error("notify", "Failed to send notification", error);
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
} catch (error) {
|
|
34
|
-
logger.error("notify", "Failed to send notification", error);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export {
|
|
39
|
-
notify
|
|
40
|
-
};
|