lazy-gravity 0.1.0 → 0.2.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/README.md +18 -6
- package/dist/bin/cli.js +18 -18
- package/dist/bin/commands/doctor.js +2 -1
- package/dist/bin/commands/start.js +25 -2
- package/dist/bot/index.js +346 -152
- package/dist/commands/joinCommandHandler.js +302 -0
- package/dist/commands/joinDetachCommandHandler.js +285 -0
- package/dist/commands/registerSlashCommands.js +35 -0
- package/dist/database/chatSessionRepository.js +10 -0
- package/dist/database/userPreferenceRepository.js +72 -0
- package/dist/events/interactionCreateHandler.js +58 -36
- package/dist/events/messageCreateHandler.js +158 -53
- package/dist/services/antigravityLauncher.js +4 -3
- package/dist/services/approvalDetector.js +6 -0
- package/dist/services/cdpBridgeManager.js +184 -84
- package/dist/services/cdpConnectionPool.js +79 -51
- package/dist/services/cdpService.js +149 -51
- package/dist/services/chatSessionService.js +229 -8
- package/dist/services/errorPopupDetector.js +6 -0
- package/dist/services/planningDetector.js +6 -0
- package/dist/services/responseMonitor.js +125 -24
- package/dist/services/updateCheckService.js +147 -0
- package/dist/services/userMessageDetector.js +221 -0
- package/dist/ui/modeUi.js +11 -1
- package/dist/ui/outputUi.js +30 -0
- package/dist/ui/sessionPickerUi.js +48 -0
- package/dist/utils/antigravityPaths.js +94 -0
- package/dist/utils/configLoader.js +10 -0
- package/dist/utils/discordButtonUtils.js +33 -0
- package/dist/utils/logBuffer.js +47 -0
- package/dist/utils/logger.js +80 -20
- package/dist/utils/pathUtils.js +57 -0
- package/dist/utils/plainTextFormatter.js +70 -0
- package/package.json +4 -4
|
@@ -0,0 +1,147 @@
|
|
|
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.COOLDOWN_MS = exports.UPDATE_CHECK_FILE = void 0;
|
|
37
|
+
exports.shouldCheckForUpdates = shouldCheckForUpdates;
|
|
38
|
+
exports.fetchLatestVersion = fetchLatestVersion;
|
|
39
|
+
exports.checkForUpdates = checkForUpdates;
|
|
40
|
+
const https = __importStar(require("https"));
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const CONFIG_DIR = '.lazy-gravity';
|
|
45
|
+
exports.UPDATE_CHECK_FILE = 'update-check.json';
|
|
46
|
+
exports.COOLDOWN_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
47
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/lazy-gravity/latest';
|
|
48
|
+
const REQUEST_TIMEOUT_MS = 5000;
|
|
49
|
+
function getCachePath() {
|
|
50
|
+
return path.join(os.homedir(), CONFIG_DIR, exports.UPDATE_CHECK_FILE);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Determine whether enough time has elapsed since the last update check.
|
|
54
|
+
* Returns true if we should query the registry.
|
|
55
|
+
*/
|
|
56
|
+
function shouldCheckForUpdates() {
|
|
57
|
+
const cachePath = getCachePath();
|
|
58
|
+
try {
|
|
59
|
+
if (!fs.existsSync(cachePath))
|
|
60
|
+
return true;
|
|
61
|
+
const raw = fs.readFileSync(cachePath, 'utf-8');
|
|
62
|
+
const cache = JSON.parse(raw);
|
|
63
|
+
return Date.now() - cache.lastCheck >= exports.COOLDOWN_MS;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Query the npm registry for the latest published version.
|
|
71
|
+
*/
|
|
72
|
+
function fetchLatestVersion() {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
const req = https.get(REGISTRY_URL, (res) => {
|
|
75
|
+
if (res.statusCode !== 200) {
|
|
76
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
let body = '';
|
|
80
|
+
res.on('data', (chunk) => {
|
|
81
|
+
body += chunk;
|
|
82
|
+
});
|
|
83
|
+
res.on('end', () => {
|
|
84
|
+
try {
|
|
85
|
+
const data = JSON.parse(body);
|
|
86
|
+
resolve(data.version);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
reject(err);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
req.on('error', reject);
|
|
94
|
+
req.on('timeout', () => {
|
|
95
|
+
req.destroy();
|
|
96
|
+
reject(new Error('Request timed out'));
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function writeCache() {
|
|
101
|
+
const cachePath = getCachePath();
|
|
102
|
+
const dir = path.dirname(cachePath);
|
|
103
|
+
try {
|
|
104
|
+
if (!fs.existsSync(dir)) {
|
|
105
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
106
|
+
}
|
|
107
|
+
const cache = { lastCheck: Date.now() };
|
|
108
|
+
fs.writeFileSync(cachePath, JSON.stringify(cache), 'utf-8');
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Silently ignore cache write failures
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Compare two semver strings. Returns:
|
|
116
|
+
* -1 if a < b, 0 if a === b, 1 if a > b
|
|
117
|
+
*/
|
|
118
|
+
function compareSemver(a, b) {
|
|
119
|
+
const partsA = a.split('.').map(Number);
|
|
120
|
+
const partsB = b.split('.').map(Number);
|
|
121
|
+
for (let i = 0; i < 3; i++) {
|
|
122
|
+
const diff = (partsA[i] ?? 0) - (partsB[i] ?? 0);
|
|
123
|
+
if (diff < 0)
|
|
124
|
+
return -1;
|
|
125
|
+
if (diff > 0)
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Non-blocking update check. Call at startup (fire-and-forget).
|
|
132
|
+
* Respects a 24-hour cooldown via a local cache file.
|
|
133
|
+
*/
|
|
134
|
+
async function checkForUpdates(currentVersion) {
|
|
135
|
+
if (!shouldCheckForUpdates())
|
|
136
|
+
return;
|
|
137
|
+
try {
|
|
138
|
+
const latest = await fetchLatestVersion();
|
|
139
|
+
writeCache();
|
|
140
|
+
if (compareSemver(currentVersion, latest) < 0) {
|
|
141
|
+
console.info(`\n Update available: ${currentVersion} \u2192 ${latest} \u2014 run \x1b[36mnpm i -g lazy-gravity\x1b[0m\n`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Silently ignore — update check should never block startup
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UserMessageDetector = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const logger_1 = require("../utils/logger");
|
|
6
|
+
/**
|
|
7
|
+
* Script to detect the latest user message in the Antigravity chat.
|
|
8
|
+
*
|
|
9
|
+
* Antigravity user message DOM structure:
|
|
10
|
+
* <div class="bg-gray-500/15 p-2 rounded-lg w-full text-sm select-text">
|
|
11
|
+
* <div class="flex flex-row items-end gap-2">
|
|
12
|
+
* <div class="flex-1 flex flex-col gap-2">
|
|
13
|
+
* <div>
|
|
14
|
+
* <div class="whitespace-pre-wrap text-sm" style="word-break: break-word;">
|
|
15
|
+
* {user message text}
|
|
16
|
+
* </div>
|
|
17
|
+
* </div>
|
|
18
|
+
* </div>
|
|
19
|
+
* <div> <!-- undo button --> </div>
|
|
20
|
+
* </div>
|
|
21
|
+
* </div>
|
|
22
|
+
*/
|
|
23
|
+
const DETECT_USER_MESSAGE_SCRIPT = `(() => {
|
|
24
|
+
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
25
|
+
const scope = panel || document;
|
|
26
|
+
|
|
27
|
+
// Strategy A (primary): Query .whitespace-pre-wrap elements directly inside
|
|
28
|
+
// user bubble containers. This avoids the parent-container problem where
|
|
29
|
+
// querySelectorAll matches a wrapper that contains multiple bubbles.
|
|
30
|
+
const textEls = scope.querySelectorAll(
|
|
31
|
+
'[class*="bg-gray-500/15"][class*="select-text"] .whitespace-pre-wrap'
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (textEls.length > 0) {
|
|
35
|
+
const lastTextEl = textEls[textEls.length - 1];
|
|
36
|
+
const text = (lastTextEl.textContent || '').trim();
|
|
37
|
+
if (text.length > 0) return { text };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Strategy B (fallback): Find individual bubble containers, filtering out
|
|
41
|
+
// any element that itself contains nested bubble elements (i.e., a parent wrapper).
|
|
42
|
+
const userBubbles = Array.from(scope.querySelectorAll(
|
|
43
|
+
'[class*="bg-gray-500/15"][class*="rounded-lg"][class*="select-text"]'
|
|
44
|
+
)).filter(el => !el.querySelector('[class*="bg-gray-500/15"][class*="select-text"]'));
|
|
45
|
+
|
|
46
|
+
if (userBubbles.length === 0) return null;
|
|
47
|
+
|
|
48
|
+
const lastBubble = userBubbles[userBubbles.length - 1];
|
|
49
|
+
const textEl = lastBubble.querySelector('.whitespace-pre-wrap')
|
|
50
|
+
|| lastBubble.querySelector('[style*="word-break"]');
|
|
51
|
+
|
|
52
|
+
const text = textEl
|
|
53
|
+
? (textEl.textContent || '').trim()
|
|
54
|
+
: (lastBubble.textContent || '').trim();
|
|
55
|
+
|
|
56
|
+
if (!text || text.length < 1) return null;
|
|
57
|
+
|
|
58
|
+
return { text };
|
|
59
|
+
})()`;
|
|
60
|
+
/**
|
|
61
|
+
* Normalize text for echo hash comparison.
|
|
62
|
+
* Trims, collapses whitespace, and takes first 200 chars.
|
|
63
|
+
*/
|
|
64
|
+
function normalizeForHash(text) {
|
|
65
|
+
return text.trim().replace(/\s+/g, ' ').slice(0, 200);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Compute a short hash for echo prevention.
|
|
69
|
+
*/
|
|
70
|
+
function computeEchoHash(text) {
|
|
71
|
+
return (0, node_crypto_1.createHash)('sha256').update(normalizeForHash(text)).digest('hex').slice(0, 16);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Detects user messages posted directly in the Antigravity UI (e.g., from a PC).
|
|
75
|
+
* Follows the ApprovalDetector polling pattern.
|
|
76
|
+
*/
|
|
77
|
+
class UserMessageDetector {
|
|
78
|
+
cdpService;
|
|
79
|
+
pollIntervalMs;
|
|
80
|
+
onUserMessage;
|
|
81
|
+
pollTimer = null;
|
|
82
|
+
isRunning = false;
|
|
83
|
+
/** Hash of the last detected message (for duplicate prevention) */
|
|
84
|
+
lastDetectedHash = null;
|
|
85
|
+
/** Set of echo hashes — messages sent by LazyGravity that should be ignored */
|
|
86
|
+
echoHashes = new Set();
|
|
87
|
+
/** Set of all previously detected message hashes (defense-in-depth dedup) */
|
|
88
|
+
seenHashes = new Set();
|
|
89
|
+
static MAX_SEEN_HASHES = 50;
|
|
90
|
+
/** True during the first poll — seeds existing DOM state without firing callback */
|
|
91
|
+
isPriming = false;
|
|
92
|
+
constructor(options) {
|
|
93
|
+
this.cdpService = options.cdpService;
|
|
94
|
+
this.pollIntervalMs = options.pollIntervalMs ?? 2000;
|
|
95
|
+
this.onUserMessage = options.onUserMessage;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Register a message hash as an echo (sent by LazyGravity).
|
|
99
|
+
* When this message is detected in the DOM, it will be skipped.
|
|
100
|
+
*/
|
|
101
|
+
addEchoHash(text) {
|
|
102
|
+
const hash = computeEchoHash(text);
|
|
103
|
+
this.echoHashes.add(hash);
|
|
104
|
+
// Auto-cleanup: remove after 60s to prevent memory leak
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
this.echoHashes.delete(hash);
|
|
107
|
+
}, 60000);
|
|
108
|
+
}
|
|
109
|
+
/** Start monitoring. The first poll seeds the current DOM state without firing the callback. */
|
|
110
|
+
start() {
|
|
111
|
+
if (this.isRunning)
|
|
112
|
+
return;
|
|
113
|
+
this.isRunning = true;
|
|
114
|
+
this.lastDetectedHash = null;
|
|
115
|
+
this.seenHashes.clear();
|
|
116
|
+
this.isPriming = true;
|
|
117
|
+
// echoHashes are intentionally NOT cleared — they have their own 60s TTL
|
|
118
|
+
// and keeping them prevents false echo pickup during rapid stop/start cycles.
|
|
119
|
+
this.schedulePoll();
|
|
120
|
+
}
|
|
121
|
+
/** Stop monitoring. */
|
|
122
|
+
stop() {
|
|
123
|
+
this.isRunning = false;
|
|
124
|
+
if (this.pollTimer) {
|
|
125
|
+
clearTimeout(this.pollTimer);
|
|
126
|
+
this.pollTimer = null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Returns whether monitoring is currently active. */
|
|
130
|
+
isActive() {
|
|
131
|
+
return this.isRunning;
|
|
132
|
+
}
|
|
133
|
+
/** Add a hash to the seenHashes set, evicting the oldest entry if at capacity. */
|
|
134
|
+
addToSeenHashes(hash) {
|
|
135
|
+
if (this.seenHashes.size >= UserMessageDetector.MAX_SEEN_HASHES) {
|
|
136
|
+
// Evict the oldest entry (first inserted)
|
|
137
|
+
const oldest = this.seenHashes.values().next().value;
|
|
138
|
+
if (oldest !== undefined) {
|
|
139
|
+
this.seenHashes.delete(oldest);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
this.seenHashes.add(hash);
|
|
143
|
+
}
|
|
144
|
+
/** Schedule the next poll. */
|
|
145
|
+
schedulePoll() {
|
|
146
|
+
if (!this.isRunning)
|
|
147
|
+
return;
|
|
148
|
+
this.pollTimer = setTimeout(async () => {
|
|
149
|
+
await this.poll();
|
|
150
|
+
if (this.isRunning) {
|
|
151
|
+
this.schedulePoll();
|
|
152
|
+
}
|
|
153
|
+
}, this.pollIntervalMs);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Single poll iteration:
|
|
157
|
+
* 1. Get latest user message from DOM
|
|
158
|
+
* 2. Check for duplicates and echoes
|
|
159
|
+
* 3. Notify via callback on new detection
|
|
160
|
+
*/
|
|
161
|
+
async poll() {
|
|
162
|
+
try {
|
|
163
|
+
const contextId = this.cdpService.getPrimaryContextId();
|
|
164
|
+
const callParams = {
|
|
165
|
+
expression: DETECT_USER_MESSAGE_SCRIPT,
|
|
166
|
+
returnByValue: true,
|
|
167
|
+
awaitPromise: false,
|
|
168
|
+
};
|
|
169
|
+
if (contextId !== null) {
|
|
170
|
+
callParams.contextId = contextId;
|
|
171
|
+
}
|
|
172
|
+
const result = await this.cdpService.call('Runtime.evaluate', callParams);
|
|
173
|
+
const info = result?.result?.value ?? null;
|
|
174
|
+
// Clear priming flag even if DOM is empty (e.g., new/empty chat)
|
|
175
|
+
if (this.isPriming && (!info || !info.text)) {
|
|
176
|
+
this.isPriming = false;
|
|
177
|
+
logger_1.logger.debug('[UserMessageDetector] Primed with empty DOM');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (info && info.text) {
|
|
181
|
+
const hash = computeEchoHash(info.text);
|
|
182
|
+
const preview = info.text.slice(0, 40);
|
|
183
|
+
// First poll: seed the current DOM state without firing callback
|
|
184
|
+
if (this.isPriming) {
|
|
185
|
+
this.isPriming = false;
|
|
186
|
+
this.lastDetectedHash = hash;
|
|
187
|
+
this.addToSeenHashes(hash);
|
|
188
|
+
logger_1.logger.debug(`[UserMessageDetector] Primed with existing message: "${preview}..."`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// Skip if same as last detected message
|
|
192
|
+
if (hash === this.lastDetectedHash)
|
|
193
|
+
return;
|
|
194
|
+
// Skip if already seen (defense-in-depth dedup)
|
|
195
|
+
if (this.seenHashes.has(hash)) {
|
|
196
|
+
logger_1.logger.debug(`[UserMessageDetector] seenHash hit, skipping: "${preview}..."`);
|
|
197
|
+
this.lastDetectedHash = hash;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
// Skip if this is an echo (sent by LazyGravity)
|
|
201
|
+
if (this.echoHashes.has(hash)) {
|
|
202
|
+
logger_1.logger.debug(`[UserMessageDetector] Echo hash match, skipping: "${preview}..."`);
|
|
203
|
+
this.lastDetectedHash = hash;
|
|
204
|
+
this.addToSeenHashes(hash);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
this.lastDetectedHash = hash;
|
|
208
|
+
this.addToSeenHashes(hash);
|
|
209
|
+
logger_1.logger.debug(`[UserMessageDetector] New message detected: "${preview}..."`);
|
|
210
|
+
this.onUserMessage(info);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
215
|
+
if (message.includes('WebSocket is not connected'))
|
|
216
|
+
return;
|
|
217
|
+
logger_1.logger.error('[UserMessageDetector] Error during polling:', error);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
exports.UserMessageDetector = UserMessageDetector;
|
package/dist/ui/modeUi.js
CHANGED
|
@@ -6,7 +6,17 @@ const modeService_1 = require("../services/modeService");
|
|
|
6
6
|
/**
|
|
7
7
|
* Build and send the interactive UI for the /mode command (dropdown style)
|
|
8
8
|
*/
|
|
9
|
-
async function sendModeUI(target, modeService) {
|
|
9
|
+
async function sendModeUI(target, modeService, deps) {
|
|
10
|
+
// If CDP is available, query the live mode and sync modeService
|
|
11
|
+
if (deps?.getCurrentCdp) {
|
|
12
|
+
const cdp = deps.getCurrentCdp();
|
|
13
|
+
if (cdp) {
|
|
14
|
+
const liveMode = await cdp.getCurrentMode();
|
|
15
|
+
if (liveMode) {
|
|
16
|
+
modeService.setMode(liveMode);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
10
20
|
const currentMode = modeService.getCurrentMode();
|
|
11
21
|
const embed = new discord_js_1.EmbedBuilder()
|
|
12
22
|
.setTitle('Mode Management')
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OUTPUT_BTN_PLAIN = exports.OUTPUT_BTN_EMBED = void 0;
|
|
4
|
+
exports.sendOutputUI = sendOutputUI;
|
|
5
|
+
const discord_js_1 = require("discord.js");
|
|
6
|
+
exports.OUTPUT_BTN_EMBED = 'output_btn_embed';
|
|
7
|
+
exports.OUTPUT_BTN_PLAIN = 'output_btn_plain';
|
|
8
|
+
async function sendOutputUI(target, currentFormat) {
|
|
9
|
+
const isEmbed = currentFormat === 'embed';
|
|
10
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
11
|
+
.setTitle('Output Format')
|
|
12
|
+
.setColor(isEmbed ? 0x5865F2 : 0x2ECC71)
|
|
13
|
+
.setDescription(`**Current Format:** ${isEmbed ? '📋 Embed' : '📝 Plain Text'}\n\n` +
|
|
14
|
+
'Embed: Rich formatting with colored borders (default).\n' +
|
|
15
|
+
'Plain Text: Simple text output, easy to copy on mobile.')
|
|
16
|
+
.setFooter({ text: 'Use buttons below to change format' })
|
|
17
|
+
.setTimestamp();
|
|
18
|
+
const row = new discord_js_1.ActionRowBuilder().addComponents(new discord_js_1.ButtonBuilder()
|
|
19
|
+
.setCustomId(exports.OUTPUT_BTN_EMBED)
|
|
20
|
+
.setLabel('Embed')
|
|
21
|
+
.setStyle(isEmbed ? discord_js_1.ButtonStyle.Primary : discord_js_1.ButtonStyle.Secondary), new discord_js_1.ButtonBuilder()
|
|
22
|
+
.setCustomId(exports.OUTPUT_BTN_PLAIN)
|
|
23
|
+
.setLabel('Plain Text')
|
|
24
|
+
.setStyle(!isEmbed ? discord_js_1.ButtonStyle.Success : discord_js_1.ButtonStyle.Secondary));
|
|
25
|
+
await target.editReply({
|
|
26
|
+
content: '',
|
|
27
|
+
embeds: [embed],
|
|
28
|
+
components: [row],
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SESSION_SELECT_ID = void 0;
|
|
4
|
+
exports.isSessionSelectId = isSessionSelectId;
|
|
5
|
+
exports.buildSessionPickerUI = buildSessionPickerUI;
|
|
6
|
+
const discord_js_1 = require("discord.js");
|
|
7
|
+
const i18n_1 = require("../utils/i18n");
|
|
8
|
+
/** Select menu custom ID for session picker */
|
|
9
|
+
exports.SESSION_SELECT_ID = 'session_select';
|
|
10
|
+
/** Maximum items per select menu (Discord limit) */
|
|
11
|
+
const MAX_SELECT_OPTIONS = 25;
|
|
12
|
+
/**
|
|
13
|
+
* Check if a customId belongs to the session select menu.
|
|
14
|
+
*/
|
|
15
|
+
function isSessionSelectId(customId) {
|
|
16
|
+
return customId === exports.SESSION_SELECT_ID;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build the session picker UI with a select menu.
|
|
20
|
+
*
|
|
21
|
+
* @param sessions - List of sessions from the side panel
|
|
22
|
+
* @returns Object with embeds and components arrays ready for Discord reply
|
|
23
|
+
*/
|
|
24
|
+
function buildSessionPickerUI(sessions) {
|
|
25
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
26
|
+
.setTitle((0, i18n_1.t)('🔗 Join Session'))
|
|
27
|
+
.setColor(0x5865F2)
|
|
28
|
+
.setTimestamp();
|
|
29
|
+
if (sessions.length === 0) {
|
|
30
|
+
embed.setDescription((0, i18n_1.t)('No sessions found in the Antigravity side panel.'));
|
|
31
|
+
return { embeds: [embed], components: [] };
|
|
32
|
+
}
|
|
33
|
+
embed.setDescription((0, i18n_1.t)(`Select a session to join (${sessions.length} found)`));
|
|
34
|
+
const pageItems = sessions.slice(0, MAX_SELECT_OPTIONS);
|
|
35
|
+
const options = pageItems.map((session) => ({
|
|
36
|
+
label: session.title.slice(0, 100),
|
|
37
|
+
value: session.title.slice(0, 100),
|
|
38
|
+
description: session.isActive ? (0, i18n_1.t)('Current') : undefined,
|
|
39
|
+
}));
|
|
40
|
+
const selectMenu = new discord_js_1.StringSelectMenuBuilder()
|
|
41
|
+
.setCustomId(exports.SESSION_SELECT_ID)
|
|
42
|
+
.setPlaceholder((0, i18n_1.t)('Select a session...'))
|
|
43
|
+
.addOptions(options);
|
|
44
|
+
const components = [
|
|
45
|
+
new discord_js_1.ActionRowBuilder().addComponents(selectMenu),
|
|
46
|
+
];
|
|
47
|
+
return { embeds: [embed], components };
|
|
48
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
}
|
|
@@ -92,6 +92,7 @@ function mergeConfig(persisted) {
|
|
|
92
92
|
const workspaceBaseDir = expandTilde(rawDir);
|
|
93
93
|
const guildId = process.env.GUILD_ID ?? persisted.guildId ?? undefined;
|
|
94
94
|
const autoApproveFileEdits = resolveBoolean(process.env.AUTO_APPROVE_FILE_EDITS, persisted.autoApproveFileEdits, false);
|
|
95
|
+
const logLevel = resolveLogLevel(process.env.LOG_LEVEL, persisted.logLevel);
|
|
95
96
|
const extractionMode = resolveExtractionMode(process.env.EXTRACTION_MODE, persisted.extractionMode);
|
|
96
97
|
return {
|
|
97
98
|
discordToken: token,
|
|
@@ -100,6 +101,7 @@ function mergeConfig(persisted) {
|
|
|
100
101
|
allowedUserIds,
|
|
101
102
|
workspaceBaseDir,
|
|
102
103
|
autoApproveFileEdits,
|
|
104
|
+
logLevel,
|
|
103
105
|
extractionMode,
|
|
104
106
|
};
|
|
105
107
|
}
|
|
@@ -116,6 +118,14 @@ function resolveAllowedUserIds(persisted) {
|
|
|
116
118
|
}
|
|
117
119
|
return [];
|
|
118
120
|
}
|
|
121
|
+
const VALID_LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'none'];
|
|
122
|
+
function resolveLogLevel(envValue, persistedValue) {
|
|
123
|
+
const raw = envValue?.toLowerCase() ?? persistedValue;
|
|
124
|
+
if (raw && VALID_LOG_LEVELS.includes(raw)) {
|
|
125
|
+
return raw;
|
|
126
|
+
}
|
|
127
|
+
return 'info';
|
|
128
|
+
}
|
|
119
129
|
function resolveExtractionMode(envValue, persistedValue) {
|
|
120
130
|
const raw = envValue ?? persistedValue;
|
|
121
131
|
if (raw === 'legacy')
|
|
@@ -0,0 +1,33 @@
|
|
|
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 message component rows.
|
|
7
|
+
* Shared utility used by interaction handlers and detector callbacks.
|
|
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((button) => button !== null);
|
|
27
|
+
if (disabledButtons.length === 0)
|
|
28
|
+
return null;
|
|
29
|
+
nextRow.addComponents(...disabledButtons);
|
|
30
|
+
return nextRow;
|
|
31
|
+
})
|
|
32
|
+
.filter((row) => row !== null);
|
|
33
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logBuffer = exports.LogBuffer = void 0;
|
|
4
|
+
const MAX_ENTRIES = 200;
|
|
5
|
+
// Strip ANSI escape codes for clean buffer storage
|
|
6
|
+
const ANSI_REGEX = /\x1b\[[0-9;]*m/g;
|
|
7
|
+
function stripAnsi(text) {
|
|
8
|
+
return text.replace(ANSI_REGEX, '');
|
|
9
|
+
}
|
|
10
|
+
class LogBuffer {
|
|
11
|
+
buffer = [];
|
|
12
|
+
head = 0;
|
|
13
|
+
count = 0;
|
|
14
|
+
append(level, message) {
|
|
15
|
+
const entry = {
|
|
16
|
+
timestamp: new Date().toISOString(),
|
|
17
|
+
level,
|
|
18
|
+
message: stripAnsi(message),
|
|
19
|
+
};
|
|
20
|
+
if (this.count < MAX_ENTRIES) {
|
|
21
|
+
this.buffer.push(entry);
|
|
22
|
+
this.count++;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
this.buffer[this.head] = entry;
|
|
26
|
+
}
|
|
27
|
+
this.head = (this.head + 1) % MAX_ENTRIES;
|
|
28
|
+
}
|
|
29
|
+
getRecent(count, levelFilter) {
|
|
30
|
+
const all = [];
|
|
31
|
+
for (let i = 0; i < this.count; i++) {
|
|
32
|
+
const idx = (this.head - this.count + i + MAX_ENTRIES * 2) % MAX_ENTRIES;
|
|
33
|
+
all.push(this.buffer[idx]);
|
|
34
|
+
}
|
|
35
|
+
const filtered = levelFilter
|
|
36
|
+
? all.filter((e) => e.level === levelFilter)
|
|
37
|
+
: all;
|
|
38
|
+
return filtered.slice(-count);
|
|
39
|
+
}
|
|
40
|
+
clear() {
|
|
41
|
+
this.buffer.length = 0;
|
|
42
|
+
this.head = 0;
|
|
43
|
+
this.count = 0;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.LogBuffer = LogBuffer;
|
|
47
|
+
exports.logBuffer = new LogBuffer();
|