lazy-gravity 0.2.0 → 0.4.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.
Files changed (66) hide show
  1. package/README.md +77 -15
  2. package/dist/bin/cli.js +0 -0
  3. package/dist/bin/commands/doctor.js +19 -2
  4. package/dist/bin/commands/open.js +1 -1
  5. package/dist/bin/commands/setup.js +286 -70
  6. package/dist/bot/eventRouter.js +70 -0
  7. package/dist/bot/index.js +355 -147
  8. package/dist/bot/telegramCommands.js +478 -0
  9. package/dist/bot/telegramMessageHandler.js +308 -0
  10. package/dist/bot/telegramProjectCommand.js +137 -0
  11. package/dist/bot/workspaceQueue.js +61 -0
  12. package/dist/commands/joinCommandHandler.js +4 -1
  13. package/dist/database/telegramBindingRepository.js +97 -0
  14. package/dist/database/userPreferenceRepository.js +46 -1
  15. package/dist/events/interactionCreateHandler.js +36 -0
  16. package/dist/events/messageCreateHandler.js +11 -7
  17. package/dist/handlers/approvalButtonAction.js +99 -0
  18. package/dist/handlers/autoAcceptButtonAction.js +43 -0
  19. package/dist/handlers/buttonHandler.js +55 -0
  20. package/dist/handlers/commandHandler.js +44 -0
  21. package/dist/handlers/errorPopupButtonAction.js +137 -0
  22. package/dist/handlers/messageHandler.js +70 -0
  23. package/dist/handlers/modeSelectAction.js +63 -0
  24. package/dist/handlers/modelButtonAction.js +102 -0
  25. package/dist/handlers/planningButtonAction.js +118 -0
  26. package/dist/handlers/selectHandler.js +41 -0
  27. package/dist/handlers/templateButtonAction.js +54 -0
  28. package/dist/platform/adapter.js +8 -0
  29. package/dist/platform/discord/discordAdapter.js +99 -0
  30. package/dist/platform/discord/index.js +15 -0
  31. package/dist/platform/discord/wrappers.js +331 -0
  32. package/dist/platform/index.js +18 -0
  33. package/dist/platform/richContentBuilder.js +76 -0
  34. package/dist/platform/telegram/index.js +16 -0
  35. package/dist/platform/telegram/telegramAdapter.js +195 -0
  36. package/dist/platform/telegram/telegramFormatter.js +134 -0
  37. package/dist/platform/telegram/wrappers.js +333 -0
  38. package/dist/platform/types.js +28 -0
  39. package/dist/services/approvalDetector.js +15 -2
  40. package/dist/services/cdpBridgeManager.js +91 -146
  41. package/dist/services/cdpService.js +88 -2
  42. package/dist/services/chatSessionService.js +50 -10
  43. package/dist/services/defaultModelApplicator.js +54 -0
  44. package/dist/services/modeService.js +16 -1
  45. package/dist/services/modelService.js +57 -16
  46. package/dist/services/notificationSender.js +149 -0
  47. package/dist/services/responseMonitor.js +1 -2
  48. package/dist/services/screenshotService.js +2 -2
  49. package/dist/ui/autoAcceptUi.js +37 -0
  50. package/dist/ui/modeUi.js +38 -1
  51. package/dist/ui/modelsUi.js +96 -0
  52. package/dist/ui/outputUi.js +32 -0
  53. package/dist/ui/projectListUi.js +55 -0
  54. package/dist/ui/screenshotUi.js +26 -0
  55. package/dist/ui/sessionPickerUi.js +35 -1
  56. package/dist/ui/templateUi.js +41 -0
  57. package/dist/utils/configLoader.js +63 -12
  58. package/dist/utils/lockfile.js +5 -5
  59. package/dist/utils/logger.js +7 -0
  60. package/dist/utils/telegramImageHandler.js +127 -0
  61. package/package.json +6 -3
  62. package/dist/commands/joinDetachCommandHandler.js +0 -285
  63. package/dist/services/retryStore.js +0 -46
  64. package/dist/ui/buttonUtils.js +0 -33
  65. package/dist/utils/antigravityPaths.js +0 -94
  66. package/dist/utils/logFileTransport.js +0 -147
@@ -1,285 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.JoinDetachCommandHandler = void 0;
4
- const i18n_1 = require("../utils/i18n");
5
- const discord_js_1 = require("discord.js");
6
- const cdpBridgeManager_1 = require("../services/cdpBridgeManager");
7
- const responseMonitor_1 = require("../services/responseMonitor");
8
- const sessionPickerUi_1 = require("../ui/sessionPickerUi");
9
- const logger_1 = require("../utils/logger");
10
- /** Maximum embed description length (Discord limit is 4096) */
11
- const MAX_EMBED_DESC = 4000;
12
- /**
13
- * Handler for /join and /mirror commands.
14
- *
15
- * /join — List Antigravity sessions and connect to one via a select menu.
16
- * /mirror — Toggle PC-to-Discord message mirroring ON/OFF.
17
- */
18
- class JoinDetachCommandHandler {
19
- chatSessionService;
20
- chatSessionRepo;
21
- bindingRepo;
22
- channelManager;
23
- pool;
24
- client;
25
- /** Active ResponseMonitors per workspace (for AI response mirroring) */
26
- activeResponseMonitors = new Map();
27
- constructor(chatSessionService, chatSessionRepo, bindingRepo, channelManager, pool, client) {
28
- this.chatSessionService = chatSessionService;
29
- this.chatSessionRepo = chatSessionRepo;
30
- this.bindingRepo = bindingRepo;
31
- this.channelManager = channelManager;
32
- this.pool = pool;
33
- this.client = client;
34
- }
35
- /**
36
- * /join — Show session picker for the workspace bound to this channel.
37
- */
38
- async handleJoin(interaction, bridge) {
39
- const binding = this.bindingRepo.findByChannelId(interaction.channelId);
40
- const session = this.chatSessionRepo.findByChannelId(interaction.channelId);
41
- const workspaceName = binding?.workspacePath ?? session?.workspacePath;
42
- if (!workspaceName) {
43
- await interaction.editReply({
44
- content: (0, i18n_1.t)('⚠️ No project is bound to this channel. Use `/project` first.'),
45
- });
46
- return;
47
- }
48
- let cdp;
49
- try {
50
- cdp = await this.pool.getOrConnect(workspaceName);
51
- }
52
- catch (e) {
53
- await interaction.editReply({
54
- content: (0, i18n_1.t)(`⚠️ Failed to connect to project: ${e.message}`),
55
- });
56
- return;
57
- }
58
- const sessions = await this.chatSessionService.listAllSessions(cdp);
59
- const { embeds, components } = (0, sessionPickerUi_1.buildSessionPickerUI)(sessions);
60
- await interaction.editReply({ embeds, components });
61
- }
62
- /**
63
- * Handle session selection from the /join picker.
64
- *
65
- * Flow:
66
- * 1. Check if a channel already exists for this session (by displayName)
67
- * 2. If yes → reply with a link to that channel
68
- * 3. If no → create a new channel, bind it, activate session, start mirroring
69
- */
70
- async handleJoinSelect(interaction, bridge) {
71
- const selectedTitle = interaction.values[0];
72
- const guild = interaction.guild;
73
- if (!guild) {
74
- await interaction.editReply({ content: (0, i18n_1.t)('⚠️ This command can only be used in a server.') });
75
- return;
76
- }
77
- const binding = this.bindingRepo.findByChannelId(interaction.channelId);
78
- const session = this.chatSessionRepo.findByChannelId(interaction.channelId);
79
- const workspaceName = binding?.workspacePath ?? session?.workspacePath;
80
- if (!workspaceName) {
81
- await interaction.editReply({ content: (0, i18n_1.t)('⚠️ No project is bound to this channel.') });
82
- return;
83
- }
84
- // Step 1: Check if a channel already exists for this session
85
- const existingSession = this.chatSessionRepo.findByDisplayName(workspaceName, selectedTitle);
86
- if (existingSession) {
87
- const embed = new discord_js_1.EmbedBuilder()
88
- .setTitle((0, i18n_1.t)('🔗 Session Already Connected'))
89
- .setDescription((0, i18n_1.t)(`This session already has a channel:\n→ <#${existingSession.channelId}>`))
90
- .setColor(0x3498DB)
91
- .setTimestamp();
92
- await interaction.editReply({ embeds: [embed], components: [] });
93
- return;
94
- }
95
- // Step 2: Connect to CDP
96
- let cdp;
97
- try {
98
- cdp = await this.pool.getOrConnect(workspaceName);
99
- }
100
- catch (e) {
101
- await interaction.editReply({ content: (0, i18n_1.t)(`⚠️ Failed to connect to project: ${e.message}`) });
102
- return;
103
- }
104
- // Step 3: Activate the session in Antigravity
105
- const activateResult = await this.chatSessionService.activateSessionByTitle(cdp, selectedTitle);
106
- if (!activateResult.ok) {
107
- await interaction.editReply({ content: (0, i18n_1.t)(`⚠️ Failed to join session: ${activateResult.error}`) });
108
- return;
109
- }
110
- // Step 4: Create a new Discord channel for this session
111
- const categoryResult = await this.channelManager.ensureCategory(guild, workspaceName);
112
- const categoryId = categoryResult.categoryId;
113
- const sessionNumber = this.chatSessionRepo.getNextSessionNumber(categoryId);
114
- const channelName = this.channelManager.sanitizeChannelName(`${sessionNumber}-${selectedTitle}`);
115
- const channelResult = await this.channelManager.createSessionChannel(guild, categoryId, channelName);
116
- const newChannelId = channelResult.channelId;
117
- // Step 5: Register binding and session
118
- this.bindingRepo.upsert({
119
- channelId: newChannelId,
120
- workspacePath: workspaceName,
121
- guildId: guild.id,
122
- });
123
- this.chatSessionRepo.create({
124
- channelId: newChannelId,
125
- categoryId,
126
- workspacePath: workspaceName,
127
- sessionNumber,
128
- guildId: guild.id,
129
- });
130
- this.chatSessionRepo.updateDisplayName(newChannelId, selectedTitle);
131
- // Step 6: Start mirroring (routes dynamically to all bound session channels)
132
- this.startMirroring(bridge, cdp, workspaceName);
133
- const embed = new discord_js_1.EmbedBuilder()
134
- .setTitle((0, i18n_1.t)('🔗 Joined Session'))
135
- .setDescription((0, i18n_1.t)(`Connected to: **${selectedTitle}**\n→ <#${newChannelId}>\n\n` +
136
- `📡 Mirroring is **ON** — PC messages will appear in the new channel.\n` +
137
- `Use \`/mirror\` to toggle.`))
138
- .setColor(0x2ECC71)
139
- .setTimestamp();
140
- await interaction.editReply({ embeds: [embed], components: [] });
141
- }
142
- /**
143
- * /mirror — Toggle mirroring ON/OFF for the current channel's workspace.
144
- */
145
- async handleMirror(interaction, bridge) {
146
- const binding = this.bindingRepo.findByChannelId(interaction.channelId);
147
- const session = this.chatSessionRepo.findByChannelId(interaction.channelId);
148
- const workspaceName = binding?.workspacePath ?? session?.workspacePath;
149
- const dirName = workspaceName ? this.pool.extractDirName(workspaceName) : null;
150
- if (!dirName || !workspaceName) {
151
- await interaction.editReply({
152
- content: (0, i18n_1.t)('⚠️ No project is bound to this channel. Use `/project` first.'),
153
- });
154
- return;
155
- }
156
- const detector = this.pool.getUserMessageDetector(dirName);
157
- if (detector?.isActive()) {
158
- // Turn OFF — stop user message detector and any active response monitor
159
- detector.stop();
160
- const responseMonitor = this.activeResponseMonitors.get(dirName);
161
- if (responseMonitor?.isActive()) {
162
- await responseMonitor.stop();
163
- this.activeResponseMonitors.delete(dirName);
164
- }
165
- const embed = new discord_js_1.EmbedBuilder()
166
- .setTitle((0, i18n_1.t)('📡 Mirroring OFF'))
167
- .setDescription((0, i18n_1.t)('PC-to-Discord message mirroring has been stopped.'))
168
- .setColor(0x95A5A6)
169
- .setTimestamp();
170
- await interaction.editReply({ embeds: [embed] });
171
- }
172
- else {
173
- // Turn ON
174
- let cdp;
175
- try {
176
- cdp = await this.pool.getOrConnect(workspaceName);
177
- }
178
- catch (e) {
179
- await interaction.editReply({
180
- content: (0, i18n_1.t)(`⚠️ Failed to connect to project: ${e.message}`),
181
- });
182
- return;
183
- }
184
- this.startMirroring(bridge, cdp, workspaceName);
185
- const embed = new discord_js_1.EmbedBuilder()
186
- .setTitle((0, i18n_1.t)('📡 Mirroring ON'))
187
- .setDescription((0, i18n_1.t)('PC-to-Discord message mirroring is now active.\n' +
188
- 'Messages typed in Antigravity will appear in the corresponding session channel.'))
189
- .setColor(0x2ECC71)
190
- .setTimestamp();
191
- await interaction.editReply({ embeds: [embed] });
192
- }
193
- }
194
- /**
195
- * Start user message mirroring for a workspace.
196
- *
197
- * When a PC message is detected, the callback resolves the correct Discord
198
- * channel via chatSessionRepo.findByDisplayName. Only explicitly joined
199
- * sessions (with a displayName binding) receive mirrored messages.
200
- */
201
- startMirroring(bridge, cdp, workspaceName) {
202
- const dirName = this.pool.extractDirName(workspaceName);
203
- (0, cdpBridgeManager_1.ensureUserMessageDetector)(bridge, cdp, dirName, (info) => {
204
- this.routeMirroredMessage(cdp, dirName, workspaceName, info)
205
- .catch((err) => {
206
- logger_1.logger.error('[Mirror] Error routing mirrored message:', err);
207
- });
208
- });
209
- }
210
- /**
211
- * Route a mirrored PC message to the correct Discord channel and
212
- * start a passive ResponseMonitor to capture the AI response.
213
- *
214
- * Routing: chatSessionRepo.findByDisplayName only — no fallbacks.
215
- * Sessions without an explicit channel binding are silently skipped.
216
- */
217
- async routeMirroredMessage(cdp, dirName, workspaceName, info) {
218
- const chatTitle = await (0, cdpBridgeManager_1.getCurrentChatTitle)(cdp);
219
- if (!chatTitle) {
220
- logger_1.logger.debug('[Mirror] No chat title detected, skipping');
221
- return;
222
- }
223
- const session = this.chatSessionRepo.findByDisplayName(workspaceName, chatTitle);
224
- if (!session) {
225
- logger_1.logger.debug(`[Mirror] No bound channel for session "${chatTitle}", skipping`);
226
- return;
227
- }
228
- const channel = this.client.channels.cache.get(session.channelId);
229
- if (!channel || !('send' in channel))
230
- return;
231
- const sendable = channel;
232
- // Mirror the user message
233
- const userEmbed = new discord_js_1.EmbedBuilder()
234
- .setDescription(`🖥️ ${info.text}`)
235
- .setColor(0x95A5A6)
236
- .setFooter({ text: `Typed in Antigravity · ${chatTitle}` })
237
- .setTimestamp();
238
- await sendable.send({ embeds: [userEmbed] }).catch((err) => {
239
- logger_1.logger.error('[Mirror] Failed to send user message:', err);
240
- });
241
- // Start passive ResponseMonitor to capture the AI response
242
- this.startResponseMirror(cdp, dirName, sendable, chatTitle);
243
- }
244
- /**
245
- * Start a passive ResponseMonitor that sends the AI response to Discord
246
- * when generation completes.
247
- */
248
- startResponseMirror(cdp, dirName, channel, chatTitle) {
249
- // Stop previous monitor if still running
250
- const prev = this.activeResponseMonitors.get(dirName);
251
- if (prev?.isActive()) {
252
- prev.stop().catch(() => { });
253
- }
254
- const monitor = new responseMonitor_1.ResponseMonitor({
255
- cdpService: cdp,
256
- pollIntervalMs: 2000,
257
- maxDurationMs: 300000,
258
- onComplete: (finalText) => {
259
- this.activeResponseMonitors.delete(dirName);
260
- if (!finalText || finalText.trim().length === 0)
261
- return;
262
- const text = finalText.length > MAX_EMBED_DESC
263
- ? finalText.slice(0, MAX_EMBED_DESC) + '\n…(truncated)'
264
- : finalText;
265
- const embed = new discord_js_1.EmbedBuilder()
266
- .setDescription(text)
267
- .setColor(0x5865F2)
268
- .setFooter({ text: `Antigravity response · ${chatTitle}` })
269
- .setTimestamp();
270
- channel.send({ embeds: [embed] }).catch((err) => {
271
- logger_1.logger.error('[Mirror] Failed to send AI response:', err);
272
- });
273
- },
274
- onTimeout: () => {
275
- this.activeResponseMonitors.delete(dirName);
276
- },
277
- });
278
- this.activeResponseMonitors.set(dirName, monitor);
279
- monitor.startPassive().catch((err) => {
280
- logger_1.logger.error('[Mirror] Failed to start response monitor:', err);
281
- this.activeResponseMonitors.delete(dirName);
282
- });
283
- }
284
- }
285
- exports.JoinDetachCommandHandler = JoinDetachCommandHandler;
@@ -1,46 +0,0 @@
1
- "use strict";
2
- // =============================================================================
3
- // Retry store — keeps retry info for the Retry button on errors
4
- // Extracted to avoid circular dependency between bot/index.ts and
5
- // interactionCreateHandler.ts.
6
- // =============================================================================
7
- Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.RETRY_BTN_PREFIX = void 0;
9
- exports.storeRetry = storeRetry;
10
- exports.getRetryInfo = getRetryInfo;
11
- exports.deleteRetryInfo = deleteRetryInfo;
12
- exports.RETRY_BTN_PREFIX = 'retry_prompt_';
13
- const MAX_RETRY_STORE_SIZE = 100;
14
- /** TTL for retry entries — matches Discord interaction token lifetime (15 min) */
15
- const RETRY_TTL_MS = 15 * 60 * 1000;
16
- const retryStore = new Map();
17
- /** Prune entries older than RETRY_TTL_MS */
18
- function pruneExpired() {
19
- const now = Date.now();
20
- for (const [k, v] of retryStore) {
21
- if (now - v.createdAt > RETRY_TTL_MS)
22
- retryStore.delete(k);
23
- }
24
- }
25
- function storeRetry(key, info) {
26
- pruneExpired();
27
- if (retryStore.size >= MAX_RETRY_STORE_SIZE) {
28
- const firstKey = retryStore.keys().next().value;
29
- if (firstKey !== undefined)
30
- retryStore.delete(firstKey);
31
- }
32
- retryStore.set(key, { ...info, createdAt: Date.now() });
33
- }
34
- function getRetryInfo(key) {
35
- const entry = retryStore.get(key);
36
- if (!entry)
37
- return undefined;
38
- if (Date.now() - entry.createdAt > RETRY_TTL_MS) {
39
- retryStore.delete(key);
40
- return undefined;
41
- }
42
- return entry;
43
- }
44
- function deleteRetryInfo(key) {
45
- retryStore.delete(key);
46
- }
@@ -1,33 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.disableAllButtons = disableAllButtons;
4
- const discord_js_1 = require("discord.js");
5
- /**
6
- * Disable all buttons in the given message component rows.
7
- * Returns new ActionRows with every button set to disabled.
8
- */
9
- function disableAllButtons(components) {
10
- return components
11
- .map((row) => {
12
- const rowAny = row;
13
- if (!Array.isArray(rowAny.components))
14
- return null;
15
- const nextRow = new discord_js_1.ActionRowBuilder();
16
- const disabledButtons = rowAny.components
17
- .map((component) => {
18
- const componentType = component?.type ?? component?.data?.type;
19
- if (componentType !== 2)
20
- return null;
21
- const payload = typeof component?.toJSON === 'function'
22
- ? component.toJSON()
23
- : component;
24
- return discord_js_1.ButtonBuilder.from(payload).setDisabled(true);
25
- })
26
- .filter((btn) => btn !== null);
27
- if (disabledButtons.length === 0)
28
- return null;
29
- nextRow.addComponents(...disabledButtons);
30
- return nextRow;
31
- })
32
- .filter((row) => row !== null);
33
- }
@@ -1,94 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.getAntigravityCliPath = getAntigravityCliPath;
37
- exports.getAntigravityFallback = getAntigravityFallback;
38
- exports.getAntigravityCdpHint = getAntigravityCdpHint;
39
- const os = __importStar(require("os"));
40
- const path = __importStar(require("path"));
41
- const APP_NAME = 'Antigravity';
42
- /**
43
- * Get the Antigravity CLI binary path for the current platform.
44
- *
45
- * - macOS: /Applications/Antigravity.app/Contents/Resources/app/bin/antigravity
46
- * - Windows: %LOCALAPPDATA%\Programs\Antigravity\Antigravity.exe
47
- * - Linux: antigravity (assumed in PATH)
48
- */
49
- function getAntigravityCliPath() {
50
- switch (process.platform) {
51
- case 'darwin':
52
- return '/Applications/Antigravity.app/Contents/Resources/app/bin/antigravity';
53
- case 'win32': {
54
- const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
55
- return path.join(localAppData, 'Programs', APP_NAME, `${APP_NAME}.exe`);
56
- }
57
- default:
58
- return APP_NAME.toLowerCase();
59
- }
60
- }
61
- /**
62
- * Get fallback launch command and args for opening a workspace.
63
- *
64
- * - macOS: open -a Antigravity <path>
65
- * - Windows: use full exe path with shell (handles spaces in paths)
66
- * - Linux: antigravity <path>
67
- */
68
- function getAntigravityFallback(workspacePath) {
69
- switch (process.platform) {
70
- case 'darwin':
71
- return { command: 'open', args: ['-a', APP_NAME, workspacePath] };
72
- case 'win32': {
73
- const exePath = getAntigravityCliPath();
74
- return { command: exePath, args: [workspacePath], options: { shell: true } };
75
- }
76
- default:
77
- return { command: APP_NAME.toLowerCase(), args: [workspacePath] };
78
- }
79
- }
80
- /**
81
- * Get a platform-appropriate hint for starting Antigravity with CDP.
82
- *
83
- * Used in user-facing messages (Discord embeds, CLI doctor, logs).
84
- */
85
- function getAntigravityCdpHint(port = 9222) {
86
- switch (process.platform) {
87
- case 'darwin':
88
- return `open -a ${APP_NAME} --args --remote-debugging-port=${port}`;
89
- case 'win32':
90
- return `${APP_NAME}.exe --remote-debugging-port=${port}`;
91
- default:
92
- return `${APP_NAME.toLowerCase()} --remote-debugging-port=${port}`;
93
- }
94
- }
@@ -1,147 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.LogFileTransportImpl = void 0;
37
- const fs = __importStar(require("fs"));
38
- const path = __importStar(require("path"));
39
- const os = __importStar(require("os"));
40
- const DEFAULT_LOG_DIR = path.join(os.homedir(), '.lazy-gravity', 'logs');
41
- const LOG_FILE_PREFIX = 'lazy-gravity-';
42
- const LOG_FILE_EXT = '.log';
43
- /** Maximum number of log files to keep (default 14 days). */
44
- const DEFAULT_MAX_FILES = 14;
45
- /** Maximum size in bytes for a single log file (default 10 MB). */
46
- const DEFAULT_MAX_SIZE_BYTES = 10 * 1024 * 1024;
47
- function formatDate(date) {
48
- const y = date.getFullYear();
49
- const m = String(date.getMonth() + 1).padStart(2, '0');
50
- const d = String(date.getDate()).padStart(2, '0');
51
- return `${y}-${m}-${d}`;
52
- }
53
- function buildFileName(dateStr) {
54
- return `${LOG_FILE_PREFIX}${dateStr}${LOG_FILE_EXT}`;
55
- }
56
- class LogFileTransportImpl {
57
- logDir;
58
- currentDate;
59
- currentFilePath;
60
- constructor(logDir = DEFAULT_LOG_DIR) {
61
- this.logDir = logDir;
62
- this.currentDate = formatDate(new Date());
63
- this.currentFilePath = path.join(this.logDir, buildFileName(this.currentDate));
64
- this.ensureDir();
65
- this.scheduleCleanup();
66
- }
67
- write(level, timestamp, message) {
68
- this.rollIfNeeded();
69
- const line = `${timestamp} [${level}] ${message}\n`;
70
- try {
71
- fs.appendFileSync(this.currentFilePath, line, 'utf-8');
72
- }
73
- catch {
74
- // Silently ignore write errors to avoid crashing the bot
75
- }
76
- }
77
- /**
78
- * Remove old log files that exceed maxFiles count or maxSizeBytes per file.
79
- * Runs asynchronously to avoid blocking startup.
80
- */
81
- cleanup(maxFiles = DEFAULT_MAX_FILES, maxSizeBytes = DEFAULT_MAX_SIZE_BYTES) {
82
- setImmediate(() => {
83
- try {
84
- this.cleanupSync(maxFiles, maxSizeBytes);
85
- }
86
- catch {
87
- // Silently ignore cleanup errors
88
- }
89
- });
90
- }
91
- /** Synchronous cleanup for testability. */
92
- cleanupSync(maxFiles = DEFAULT_MAX_FILES, maxSizeBytes = DEFAULT_MAX_SIZE_BYTES) {
93
- if (!fs.existsSync(this.logDir))
94
- return;
95
- const entries = fs
96
- .readdirSync(this.logDir)
97
- .filter((f) => f.startsWith(LOG_FILE_PREFIX) && f.endsWith(LOG_FILE_EXT))
98
- .sort(); // chronological order (YYYY-MM-DD sorts naturally)
99
- // Remove files exceeding size limit
100
- for (const entry of entries) {
101
- const filePath = path.join(this.logDir, entry);
102
- try {
103
- const stat = fs.statSync(filePath);
104
- if (stat.size > maxSizeBytes) {
105
- fs.unlinkSync(filePath);
106
- }
107
- }
108
- catch {
109
- // Ignore stat/unlink errors
110
- }
111
- }
112
- // Re-read after size-based cleanup
113
- const remaining = fs
114
- .readdirSync(this.logDir)
115
- .filter((f) => f.startsWith(LOG_FILE_PREFIX) && f.endsWith(LOG_FILE_EXT))
116
- .sort();
117
- // Remove oldest files if count exceeds limit
118
- const excess = remaining.length - maxFiles;
119
- if (excess > 0) {
120
- for (let i = 0; i < excess; i++) {
121
- const filePath = path.join(this.logDir, remaining[i]);
122
- try {
123
- fs.unlinkSync(filePath);
124
- }
125
- catch {
126
- // Ignore unlink errors
127
- }
128
- }
129
- }
130
- }
131
- ensureDir() {
132
- if (!fs.existsSync(this.logDir)) {
133
- fs.mkdirSync(this.logDir, { recursive: true });
134
- }
135
- }
136
- rollIfNeeded() {
137
- const today = formatDate(new Date());
138
- if (today !== this.currentDate) {
139
- this.currentDate = today;
140
- this.currentFilePath = path.join(this.logDir, buildFileName(today));
141
- }
142
- }
143
- scheduleCleanup() {
144
- this.cleanup();
145
- }
146
- }
147
- exports.LogFileTransportImpl = LogFileTransportImpl;