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.
- package/README.md +77 -15
- package/dist/bin/cli.js +0 -0
- package/dist/bin/commands/doctor.js +19 -2
- package/dist/bin/commands/open.js +1 -1
- package/dist/bin/commands/setup.js +286 -70
- package/dist/bot/eventRouter.js +70 -0
- package/dist/bot/index.js +355 -147
- package/dist/bot/telegramCommands.js +478 -0
- package/dist/bot/telegramMessageHandler.js +308 -0
- package/dist/bot/telegramProjectCommand.js +137 -0
- package/dist/bot/workspaceQueue.js +61 -0
- package/dist/commands/joinCommandHandler.js +4 -1
- package/dist/database/telegramBindingRepository.js +97 -0
- package/dist/database/userPreferenceRepository.js +46 -1
- package/dist/events/interactionCreateHandler.js +36 -0
- package/dist/events/messageCreateHandler.js +11 -7
- package/dist/handlers/approvalButtonAction.js +99 -0
- package/dist/handlers/autoAcceptButtonAction.js +43 -0
- package/dist/handlers/buttonHandler.js +55 -0
- package/dist/handlers/commandHandler.js +44 -0
- package/dist/handlers/errorPopupButtonAction.js +137 -0
- package/dist/handlers/messageHandler.js +70 -0
- package/dist/handlers/modeSelectAction.js +63 -0
- package/dist/handlers/modelButtonAction.js +102 -0
- package/dist/handlers/planningButtonAction.js +118 -0
- package/dist/handlers/selectHandler.js +41 -0
- package/dist/handlers/templateButtonAction.js +54 -0
- package/dist/platform/adapter.js +8 -0
- package/dist/platform/discord/discordAdapter.js +99 -0
- package/dist/platform/discord/index.js +15 -0
- package/dist/platform/discord/wrappers.js +331 -0
- package/dist/platform/index.js +18 -0
- package/dist/platform/richContentBuilder.js +76 -0
- package/dist/platform/telegram/index.js +16 -0
- package/dist/platform/telegram/telegramAdapter.js +195 -0
- package/dist/platform/telegram/telegramFormatter.js +134 -0
- package/dist/platform/telegram/wrappers.js +333 -0
- package/dist/platform/types.js +28 -0
- package/dist/services/approvalDetector.js +15 -2
- package/dist/services/cdpBridgeManager.js +91 -146
- package/dist/services/cdpService.js +88 -2
- package/dist/services/chatSessionService.js +50 -10
- package/dist/services/defaultModelApplicator.js +54 -0
- package/dist/services/modeService.js +16 -1
- package/dist/services/modelService.js +57 -16
- package/dist/services/notificationSender.js +149 -0
- package/dist/services/responseMonitor.js +1 -2
- package/dist/services/screenshotService.js +2 -2
- package/dist/ui/autoAcceptUi.js +37 -0
- package/dist/ui/modeUi.js +38 -1
- package/dist/ui/modelsUi.js +96 -0
- package/dist/ui/outputUi.js +32 -0
- package/dist/ui/projectListUi.js +55 -0
- package/dist/ui/screenshotUi.js +26 -0
- package/dist/ui/sessionPickerUi.js +35 -1
- package/dist/ui/templateUi.js +41 -0
- package/dist/utils/configLoader.js +63 -12
- package/dist/utils/lockfile.js +5 -5
- package/dist/utils/logger.js +7 -0
- package/dist/utils/telegramImageHandler.js +127 -0
- package/package.json +6 -3
- package/dist/commands/joinDetachCommandHandler.js +0 -285
- package/dist/services/retryStore.js +0 -46
- package/dist/ui/buttonUtils.js +0 -33
- package/dist/utils/antigravityPaths.js +0 -94
- package/dist/utils/logFileTransport.js +0 -147
package/README.md
CHANGED
|
@@ -3,19 +3,20 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<img src="https://img.shields.io/badge/version-0.
|
|
6
|
+
<img src="https://img.shields.io/badge/version-0.3.0-blue?style=flat-square" alt="Version" />
|
|
7
7
|
<img src="https://img.shields.io/badge/Antigravity-1.19.5-ff6b35?style=flat-square" alt="Antigravity" />
|
|
8
8
|
<img src="https://img.shields.io/badge/node-18.x+-brightgreen?style=flat-square&logo=node.js" alt="Node.js" />
|
|
9
9
|
<img src="https://img.shields.io/badge/discord.js-14.x-5865F2?style=flat-square&logo=discord&logoColor=white" alt="discord.js" />
|
|
10
|
+
<img src="https://img.shields.io/badge/telegram-optional-26A5E4?style=flat-square&logo=telegram&logoColor=white" alt="Telegram" />
|
|
10
11
|
<img src="https://img.shields.io/badge/protocol-CDP%20%2F%20WebSocket-orange?style=flat-square" alt="CDP/WebSocket" />
|
|
11
12
|
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License" />
|
|
12
13
|
</p>
|
|
13
14
|
|
|
14
15
|
# LazyGravity
|
|
15
16
|
|
|
16
|
-
**LazyGravity** is a local, secure
|
|
17
|
+
**LazyGravity** is a local, secure bot that lets you remotely operate [Antigravity](https://antigravity.dev) on your home PC — from your smartphone, anywhere. Supports **Discord** and **Telegram** (optional).
|
|
17
18
|
|
|
18
|
-
Send natural language instructions like "fix that bug" or "start designing the new feature" from your phone. Antigravity executes them locally on your home PC using its full resources, and reports results back to
|
|
19
|
+
Send natural language instructions like "fix that bug" or "start designing the new feature" from your phone. Antigravity executes them locally on your home PC using its full resources, and reports results back to your chat platform.
|
|
19
20
|
|
|
20
21
|
https://github.com/user-attachments/assets/08eac63e-5ede-469b-ac6c-1c40ec77b0c0
|
|
21
22
|
|
|
@@ -33,7 +34,7 @@ The interactive wizard walks you through Discord bot creation, token setup, and
|
|
|
33
34
|
|
|
34
35
|
```bash
|
|
35
36
|
lazy-gravity open # Launch Antigravity with CDP enabled
|
|
36
|
-
lazy-gravity start # Start the Discord
|
|
37
|
+
lazy-gravity start # Start the bot (Discord by default, or both platforms)
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
Or run directly without installing:
|
|
@@ -47,23 +48,30 @@ npx lazy-gravity
|
|
|
47
48
|
## Features
|
|
48
49
|
|
|
49
50
|
1. **Fully Local & Secure**
|
|
50
|
-
- **No external server or port exposure** — runs as a local process on your PC, communicating directly with Discord.
|
|
51
|
-
- **Whitelist access control**: only authorized
|
|
51
|
+
- **No external server or port exposure** — runs as a local process on your PC, communicating directly with Discord/Telegram.
|
|
52
|
+
- **Whitelist access control**: only authorized user IDs can interact with the bot (per-platform allowlists).
|
|
52
53
|
- **Secure credential management**: Bot tokens and API keys are stored locally (never in source code).
|
|
53
54
|
- **Path traversal prevention & resource protection**: sandboxed directory access and concurrent task limits prevent abuse.
|
|
54
55
|
|
|
55
|
-
2. **
|
|
56
|
-
-
|
|
57
|
-
-
|
|
56
|
+
2. **Multi-Platform Support**
|
|
57
|
+
- **Discord** (default): Full feature set with slash commands, rich embeds, reactions, and channel management.
|
|
58
|
+
- **Telegram** (optional): Send prompts, receive responses, and use inline keyboard buttons. Requires [grammy](https://grammy.dev/) (`npm install grammy`).
|
|
59
|
+
- Run both platforms simultaneously from a single process, or use either one standalone.
|
|
58
60
|
|
|
59
|
-
3. **
|
|
60
|
-
-
|
|
61
|
+
3. **Project Management (Channel-Directory Binding)**
|
|
62
|
+
- **Discord**: Use `/project` to bind a channel to a local project directory via an interactive select menu.
|
|
63
|
+
- **Telegram**: Use `/project` to bind a chat to a workspace directory.
|
|
64
|
+
- Messages sent in a bound channel/chat are automatically forwarded to Antigravity with the correct project context.
|
|
61
65
|
|
|
62
|
-
4. **
|
|
66
|
+
4. **Context-Aware Replies**
|
|
67
|
+
- **Discord**: Results delivered as rich Embeds. Use Reply to continue the conversation with full context preserved.
|
|
68
|
+
- **Telegram**: Results delivered as formatted HTML messages with inline keyboard buttons.
|
|
69
|
+
|
|
70
|
+
5. **Real-Time Progress Monitoring**
|
|
63
71
|
- Long-running Antigravity tasks report progress as a series of messages (delivery confirmed / planning / analysis / execution / implementation / final summary).
|
|
64
72
|
|
|
65
|
-
|
|
66
|
-
- Send images (screenshots, mockups) or text files
|
|
73
|
+
6. **File Attachments & Context Parsing**
|
|
74
|
+
- Send images (screenshots, mockups) or text files — they are automatically forwarded to Antigravity as context.
|
|
67
75
|
|
|
68
76
|
## Usage & Commands
|
|
69
77
|
|
|
@@ -94,6 +102,26 @@ Just type in any bound channel:
|
|
|
94
102
|
- `🧹 /cleanup [days]` — Scan and clean up inactive session channels (default: 7 days)
|
|
95
103
|
- `❓ /help` — Display list of available commands
|
|
96
104
|
|
|
105
|
+
### Telegram Commands
|
|
106
|
+
|
|
107
|
+
Telegram commands use underscores instead of subcommand syntax (Telegram does not allow hyphens or spaces in command names).
|
|
108
|
+
|
|
109
|
+
- `/project` — Manage workspace bindings (list, select, create)
|
|
110
|
+
- `/project_create <name>` — Create a new workspace directory
|
|
111
|
+
- `/new` — Start a new chat session
|
|
112
|
+
- `/template` — List prompt templates with execute buttons
|
|
113
|
+
- `/template_add <name> <prompt>` — Add a new prompt template
|
|
114
|
+
- `/template_delete <name>` — Delete a prompt template
|
|
115
|
+
- `/mode` — Switch execution mode
|
|
116
|
+
- `/model` — Switch LLM model
|
|
117
|
+
- `/screenshot` — Capture Antigravity screenshot
|
|
118
|
+
- `/autoaccept [on|off]` — Toggle auto-accept mode
|
|
119
|
+
- `/logs [count]` — Show recent log entries
|
|
120
|
+
- `/stop` — Interrupt active LLM generation
|
|
121
|
+
- `/status` — Show bot status and connections
|
|
122
|
+
- `/ping` — Check bot latency
|
|
123
|
+
- `/help` — Show available commands
|
|
124
|
+
|
|
97
125
|
### CLI Commands
|
|
98
126
|
|
|
99
127
|
```bash
|
|
@@ -161,6 +189,20 @@ Then start the bot:
|
|
|
161
189
|
npm run start
|
|
162
190
|
```
|
|
163
191
|
|
|
192
|
+
#### Adding Telegram Support (Optional)
|
|
193
|
+
|
|
194
|
+
1. Install grammy: `npm install grammy`
|
|
195
|
+
2. Create a bot via [@BotFather](https://t.me/BotFather) on Telegram and copy the token.
|
|
196
|
+
3. Add the following to your `.env`:
|
|
197
|
+
|
|
198
|
+
```env
|
|
199
|
+
PLATFORMS=discord,telegram # or just "telegram" for Telegram-only
|
|
200
|
+
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
|
|
201
|
+
TELEGRAM_ALLOWED_USER_IDS=123456789 # Your Telegram numeric user ID
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
For Telegram-only deployments, Discord credentials (`DISCORD_BOT_TOKEN`, `CLIENT_ID`, `ALLOWED_USER_IDS`) are not required.
|
|
205
|
+
|
|
164
206
|
Alternatively, you can build and use the CLI:
|
|
165
207
|
|
|
166
208
|
```bash
|
|
@@ -232,7 +274,27 @@ Run `lazy-gravity doctor` to diagnose configuration and connectivity issues.
|
|
|
232
274
|
2. Connects via WebSocket to CDP (`Runtime.evaluate` for DOM operations)
|
|
233
275
|
3. Injects messages into the chat input, monitors Antigravity responses, and captures screenshots
|
|
234
276
|
|
|
235
|
-
**On disconnect**: automatically retries up to 3 times (`maxReconnectAttempts`). If all retries fail, an error notification is sent to
|
|
277
|
+
**On disconnect**: automatically retries up to 3 times (`maxReconnectAttempts`). If all retries fail, an error notification is sent to the active chat platform.
|
|
278
|
+
|
|
279
|
+
## Platform Architecture
|
|
280
|
+
|
|
281
|
+
LazyGravity uses a **platform abstraction layer** so the core bot logic is platform-independent:
|
|
282
|
+
|
|
283
|
+
```
|
|
284
|
+
src/platform/
|
|
285
|
+
├── types.ts # Shared interfaces (PlatformMessage, PlatformChannel, etc.)
|
|
286
|
+
├── adapter.ts # PlatformAdapter interface
|
|
287
|
+
├── richContentBuilder.ts # Immutable builder for rich content (embeds/HTML)
|
|
288
|
+
├── discord/ # Discord adapter (discord.js wrappers)
|
|
289
|
+
│ ├── discordAdapter.ts
|
|
290
|
+
│ └── wrappers.ts
|
|
291
|
+
└── telegram/ # Telegram adapter (grammy-compatible wrappers)
|
|
292
|
+
├── telegramAdapter.ts
|
|
293
|
+
├── telegramFormatter.ts # Markdown → Telegram HTML conversion
|
|
294
|
+
└── wrappers.ts
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Both adapters implement the same `PlatformAdapter` interface and emit events through `PlatformAdapterEvents`. The `EventRouter` dispatches events to platform-agnostic handlers, and the `WorkspaceQueue` serializes concurrent requests per workspace across platforms.
|
|
236
298
|
|
|
237
299
|
## License
|
|
238
300
|
|
package/dist/bin/cli.js
CHANGED
|
File without changes
|
|
@@ -71,8 +71,23 @@ function checkEnvFile() {
|
|
|
71
71
|
const envPath = path.resolve(process.cwd(), '.env');
|
|
72
72
|
return { exists: fs.existsSync(envPath), path: envPath };
|
|
73
73
|
}
|
|
74
|
+
const VALID_PLATFORMS = ['discord', 'telegram'];
|
|
75
|
+
function getActivePlatforms() {
|
|
76
|
+
const raw = process.env.PLATFORMS || 'discord';
|
|
77
|
+
return raw
|
|
78
|
+
.split(',')
|
|
79
|
+
.map((p) => p.trim().toLowerCase())
|
|
80
|
+
.filter((p) => VALID_PLATFORMS.includes(p));
|
|
81
|
+
}
|
|
74
82
|
function checkRequiredEnvVars() {
|
|
75
|
-
const
|
|
83
|
+
const platforms = getActivePlatforms();
|
|
84
|
+
const required = [];
|
|
85
|
+
if (platforms.includes('discord')) {
|
|
86
|
+
required.push('DISCORD_BOT_TOKEN', 'CLIENT_ID', 'ALLOWED_USER_IDS');
|
|
87
|
+
}
|
|
88
|
+
if (platforms.includes('telegram')) {
|
|
89
|
+
required.push('TELEGRAM_BOT_TOKEN', 'TELEGRAM_ALLOWED_USER_IDS');
|
|
90
|
+
}
|
|
76
91
|
return required.map((name) => ({
|
|
77
92
|
name,
|
|
78
93
|
set: Boolean(process.env[name]),
|
|
@@ -114,7 +129,9 @@ async function doctorAction() {
|
|
|
114
129
|
warn(`.env file not found: ${env.path} (not needed — config.json used)`);
|
|
115
130
|
}
|
|
116
131
|
}
|
|
117
|
-
// 4. Required environment variables (
|
|
132
|
+
// 4. Required environment variables (platform-aware)
|
|
133
|
+
const platforms = getActivePlatforms();
|
|
134
|
+
ok(`Active platforms: ${platforms.join(', ')}`);
|
|
118
135
|
const vars = checkRequiredEnvVars();
|
|
119
136
|
for (const v of vars) {
|
|
120
137
|
if (v.set) {
|
|
@@ -81,7 +81,7 @@ function openMacOS(port) {
|
|
|
81
81
|
}
|
|
82
82
|
function openWindows(port) {
|
|
83
83
|
return new Promise((resolve, reject) => {
|
|
84
|
-
(0, child_process_1.execFile)(
|
|
84
|
+
(0, child_process_1.execFile)(APP_NAME, [`--remote-debugging-port=${port}`], { shell: true }, (err) => {
|
|
85
85
|
if (err) {
|
|
86
86
|
reject(new Error(`Failed to open ${APP_NAME}: ${err.message}`));
|
|
87
87
|
return;
|
|
@@ -39,6 +39,17 @@ const https = __importStar(require("https"));
|
|
|
39
39
|
const fs = __importStar(require("fs"));
|
|
40
40
|
const os = __importStar(require("os"));
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
|
+
// @inquirer/select is ESM-only — use native import() that tsc won't rewrite to require()
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
|
|
44
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
45
|
+
let _select;
|
|
46
|
+
async function getSelect() {
|
|
47
|
+
if (_select === undefined) {
|
|
48
|
+
const mod = await dynamicImport('@inquirer/select');
|
|
49
|
+
_select = mod.default;
|
|
50
|
+
}
|
|
51
|
+
return _select;
|
|
52
|
+
}
|
|
42
53
|
const configLoader_1 = require("../../utils/configLoader");
|
|
43
54
|
const cdpPorts_1 = require("../../utils/cdpPorts");
|
|
44
55
|
// ---------------------------------------------------------------------------
|
|
@@ -208,8 +219,8 @@ function askSecret(rl, prompt) {
|
|
|
208
219
|
// ---------------------------------------------------------------------------
|
|
209
220
|
// Output helpers
|
|
210
221
|
// ---------------------------------------------------------------------------
|
|
211
|
-
function
|
|
212
|
-
console.log(
|
|
222
|
+
function sectionHeader(title) {
|
|
223
|
+
console.log(`\n ${C.cyan}—${C.reset} ${C.bold}${title}${C.reset}\n`);
|
|
213
224
|
}
|
|
214
225
|
function hint(text) {
|
|
215
226
|
console.log(` ${C.dim}${text}${C.reset}`);
|
|
@@ -225,9 +236,73 @@ function buildInviteUrl(clientId) {
|
|
|
225
236
|
return `https://discord.com/api/oauth2/authorize?client_id=${clientId}&permissions=${permissions}&scope=bot%20applications.commands`;
|
|
226
237
|
}
|
|
227
238
|
// ---------------------------------------------------------------------------
|
|
228
|
-
//
|
|
239
|
+
// Status detection (pure functions)
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
function isDiscordConfigured(p) {
|
|
242
|
+
return !!(p.discordToken && p.clientId && p.allowedUserIds && p.allowedUserIds.length > 0);
|
|
243
|
+
}
|
|
244
|
+
function isTelegramConfigured(p) {
|
|
245
|
+
return !!(p.telegramToken && p.telegramAllowedUserIds && p.telegramAllowedUserIds.length > 0);
|
|
246
|
+
}
|
|
247
|
+
function workspaceLabel(p) {
|
|
248
|
+
return p.workspaceBaseDir ?? (path.join(os.homedir(), 'Code') + ' (default)');
|
|
249
|
+
}
|
|
250
|
+
function isValidTelegramTokenFormat(token) {
|
|
251
|
+
return /^\d+:[A-Za-z0-9_-]+$/.test(token);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Recompute platforms from the current persisted state and save.
|
|
255
|
+
* Called after each individual setup flow so Ctrl+C mid-session
|
|
256
|
+
* still leaves a valid platforms array.
|
|
257
|
+
*/
|
|
258
|
+
function savePlatformsFromState() {
|
|
259
|
+
const current = configLoader_1.ConfigLoader.readPersisted();
|
|
260
|
+
const platforms = [];
|
|
261
|
+
if (isDiscordConfigured(current))
|
|
262
|
+
platforms.push('discord');
|
|
263
|
+
if (isTelegramConfigured(current))
|
|
264
|
+
platforms.push('telegram');
|
|
265
|
+
configLoader_1.ConfigLoader.save({ platforms });
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Add a platform to the persisted platforms list (idempotent).
|
|
269
|
+
*/
|
|
270
|
+
function addPlatform(platform) {
|
|
271
|
+
const current = configLoader_1.ConfigLoader.readPersisted();
|
|
272
|
+
const platforms = current.platforms ?? [];
|
|
273
|
+
if (!platforms.includes(platform)) {
|
|
274
|
+
configLoader_1.ConfigLoader.save({ platforms: [...platforms, platform] });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Remove a platform from the persisted platforms list (idempotent).
|
|
279
|
+
* Credentials are preserved — only the enabled flag changes.
|
|
280
|
+
*/
|
|
281
|
+
function removePlatform(platform) {
|
|
282
|
+
const current = configLoader_1.ConfigLoader.readPersisted();
|
|
283
|
+
const platforms = (current.platforms ?? []).filter((p) => p !== platform);
|
|
284
|
+
configLoader_1.ConfigLoader.save({ platforms });
|
|
285
|
+
}
|
|
286
|
+
function platformStatus(hasCredentials, platforms, platform) {
|
|
287
|
+
if (!hasCredentials)
|
|
288
|
+
return 'not_configured';
|
|
289
|
+
if (platforms?.includes(platform))
|
|
290
|
+
return 'enabled';
|
|
291
|
+
return 'disabled';
|
|
292
|
+
}
|
|
293
|
+
function statusBadge(status) {
|
|
294
|
+
switch (status) {
|
|
295
|
+
case 'enabled':
|
|
296
|
+
return `${C.green}[enabled]${C.reset}`;
|
|
297
|
+
case 'disabled':
|
|
298
|
+
return `${C.yellow}[disabled]${C.reset}`;
|
|
299
|
+
case 'not_configured':
|
|
300
|
+
return `${C.dim}[not configured]${C.reset}`;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// ---------------------------------------------------------------------------
|
|
304
|
+
// Input prompt helpers
|
|
229
305
|
// ---------------------------------------------------------------------------
|
|
230
|
-
const TOTAL_STEPS = 4;
|
|
231
306
|
async function promptToken(rl) {
|
|
232
307
|
while (true) {
|
|
233
308
|
const token = await askSecret(rl, ` ${C.yellow}>${C.reset} `);
|
|
@@ -236,20 +311,17 @@ async function promptToken(rl) {
|
|
|
236
311
|
continue;
|
|
237
312
|
}
|
|
238
313
|
const trimmed = token.trim();
|
|
239
|
-
// Extract Client ID from token
|
|
240
314
|
const clientId = extractBotIdFromToken(trimmed);
|
|
241
315
|
if (!clientId) {
|
|
242
316
|
errMsg('Invalid token format. A Discord bot token has 3 dot-separated segments.');
|
|
243
317
|
continue;
|
|
244
318
|
}
|
|
245
|
-
// Verify token against Discord API
|
|
246
319
|
process.stdout.write(` ${C.dim}Verifying token...${C.reset}`);
|
|
247
320
|
const botInfo = await verifyToken(trimmed);
|
|
248
321
|
if (botInfo) {
|
|
249
322
|
process.stdout.write(`\r ${C.green}Verified!${C.reset} Bot: ${C.bold}${botInfo.username}${C.reset} (${botInfo.id})\n`);
|
|
250
323
|
return { token: trimmed, clientId: botInfo.id, botName: botInfo.username };
|
|
251
324
|
}
|
|
252
|
-
// API failed but token format is valid — use extracted ID
|
|
253
325
|
process.stdout.write(`\r ${C.yellow}Could not verify online${C.reset} — using extracted ID: ${clientId}\n`);
|
|
254
326
|
return { token: trimmed, clientId, botName: null };
|
|
255
327
|
}
|
|
@@ -291,76 +363,220 @@ async function promptWorkspaceDir(rl) {
|
|
|
291
363
|
errMsg('Please enter an existing directory.');
|
|
292
364
|
}
|
|
293
365
|
}
|
|
294
|
-
async function
|
|
295
|
-
const
|
|
366
|
+
async function platformSubMenu(rl, platformName, status) {
|
|
367
|
+
const select = await getSelect();
|
|
368
|
+
const choices = status === 'disabled'
|
|
369
|
+
? [
|
|
370
|
+
{ name: 'Enable', value: 'enable' },
|
|
371
|
+
{ name: 'Reconfigure', value: 'reconfigure' },
|
|
372
|
+
{ name: 'Back', value: 'back' },
|
|
373
|
+
]
|
|
374
|
+
: [
|
|
375
|
+
{ name: 'Reconfigure', value: 'reconfigure' },
|
|
376
|
+
{ name: 'Disable', value: 'disable' },
|
|
377
|
+
{ name: 'Back', value: 'back' },
|
|
378
|
+
];
|
|
379
|
+
rl.pause();
|
|
296
380
|
try {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
hint('2. Click "New Application" (top-right), enter a name (e.g. LazyGravity), and create it');
|
|
302
|
-
hint('3. Go to the "Bot" tab on the left sidebar');
|
|
303
|
-
hint('4. Click "Reset Token" to generate and copy the token');
|
|
304
|
-
hint(`5. Scroll down to ${C.bold}"Privileged Gateway Intents"${C.dim} and enable ALL of:`);
|
|
305
|
-
hint(` ${C.cyan}PRESENCE INTENT${C.dim}`);
|
|
306
|
-
hint(` ${C.cyan}SERVER MEMBERS INTENT${C.dim}`);
|
|
307
|
-
hint(` ${C.cyan}MESSAGE CONTENT INTENT${C.dim} ${C.yellow}(required — bot cannot read messages without this)${C.dim}`);
|
|
308
|
-
hint(`6. Click ${C.bold}"Save Changes"${C.dim} at the bottom (Warning banner)`);
|
|
309
|
-
hintBlank();
|
|
310
|
-
const { token: discordToken, clientId } = await promptToken(rl);
|
|
311
|
-
console.log('');
|
|
312
|
-
stepHeader(2, TOTAL_STEPS, 'Guild (Server) ID');
|
|
313
|
-
hint('This registers slash commands instantly to your server.');
|
|
314
|
-
hint('1. Open Discord Settings > Advanced > enable "Developer Mode"');
|
|
315
|
-
hint('2. Right-click your server icon > "Copy Server ID"');
|
|
316
|
-
hint(`${C.yellow}Press Enter to skip${C.dim} (commands will register globally, may take ~1 hour)`);
|
|
317
|
-
hintBlank();
|
|
318
|
-
const guildId = await promptGuildId(rl);
|
|
319
|
-
console.log('');
|
|
320
|
-
stepHeader(3, TOTAL_STEPS, 'Allowed Discord User IDs');
|
|
321
|
-
hint('Only these users can send commands to the bot.');
|
|
322
|
-
hint('1. In Discord, right-click your own profile icon');
|
|
323
|
-
hint('2. Click "Copy User ID" (requires Developer Mode from step 2)');
|
|
324
|
-
hint('Multiple IDs: separate with commas (e.g. 123456,789012)');
|
|
325
|
-
hintBlank();
|
|
326
|
-
const allowedUserIds = await promptAllowedUserIds(rl);
|
|
327
|
-
console.log('');
|
|
328
|
-
stepHeader(4, TOTAL_STEPS, 'Workspace Base Directory');
|
|
329
|
-
hint('The parent directory where your coding projects live.');
|
|
330
|
-
hint('LazyGravity will scan subdirectories as workspaces.');
|
|
331
|
-
hintBlank();
|
|
332
|
-
const workspaceBaseDir = await promptWorkspaceDir(rl);
|
|
333
|
-
console.log('');
|
|
334
|
-
return { discordToken, clientId, guildId, allowedUserIds, workspaceBaseDir };
|
|
381
|
+
return await select({
|
|
382
|
+
message: `${platformName}:`,
|
|
383
|
+
choices,
|
|
384
|
+
});
|
|
335
385
|
}
|
|
336
386
|
finally {
|
|
337
|
-
rl.
|
|
387
|
+
rl.resume();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
// Individual setup flows (each saves immediately)
|
|
392
|
+
// ---------------------------------------------------------------------------
|
|
393
|
+
async function runDiscordSetup(rl) {
|
|
394
|
+
sectionHeader('Discord Bot Token');
|
|
395
|
+
hint('1. Go to https://discord.com/developers/applications and log in');
|
|
396
|
+
hint('2. Click "New Application" (top-right), enter a name (e.g. LazyGravity), and create it');
|
|
397
|
+
hint('3. Go to the "Bot" tab on the left sidebar');
|
|
398
|
+
hint('4. Click "Reset Token" to generate and copy the token');
|
|
399
|
+
hint(`5. Scroll down to ${C.bold}"Privileged Gateway Intents"${C.dim} and enable ALL of:`);
|
|
400
|
+
hint(` ${C.cyan}PRESENCE INTENT${C.dim}`);
|
|
401
|
+
hint(` ${C.cyan}SERVER MEMBERS INTENT${C.dim}`);
|
|
402
|
+
hint(` ${C.cyan}MESSAGE CONTENT INTENT${C.dim} ${C.yellow}(required — bot cannot read messages without this)${C.dim}`);
|
|
403
|
+
hint(`6. Click ${C.bold}"Save Changes"${C.dim} at the bottom (Warning banner)`);
|
|
404
|
+
hintBlank();
|
|
405
|
+
const { token: discordToken, clientId } = await promptToken(rl);
|
|
406
|
+
console.log('');
|
|
407
|
+
sectionHeader('Guild (Server) ID');
|
|
408
|
+
hint('This registers slash commands instantly to your server.');
|
|
409
|
+
hint('1. Open Discord Settings > Advanced > enable "Developer Mode"');
|
|
410
|
+
hint('2. Right-click your server icon > "Copy Server ID"');
|
|
411
|
+
hint(`${C.yellow}Press Enter to skip${C.dim} (commands will register globally, may take ~1 hour)`);
|
|
412
|
+
hintBlank();
|
|
413
|
+
const guildId = await promptGuildId(rl);
|
|
414
|
+
console.log('');
|
|
415
|
+
sectionHeader('Allowed Discord User IDs');
|
|
416
|
+
hint('Only these users can send commands to the bot.');
|
|
417
|
+
hint('1. In Discord, right-click your own profile icon');
|
|
418
|
+
hint('2. Click "Copy User ID" (requires Developer Mode from step above)');
|
|
419
|
+
hint('Multiple IDs: separate with commas (e.g. 123456,789012)');
|
|
420
|
+
hintBlank();
|
|
421
|
+
const allowedUserIds = await promptAllowedUserIds(rl);
|
|
422
|
+
console.log('');
|
|
423
|
+
configLoader_1.ConfigLoader.save({ discordToken, clientId, guildId, allowedUserIds });
|
|
424
|
+
savePlatformsFromState();
|
|
425
|
+
const inviteUrl = buildInviteUrl(clientId);
|
|
426
|
+
console.log(` ${C.green}Discord saved!${C.reset}`);
|
|
427
|
+
console.log(` ${C.dim}Invite URL:${C.reset} ${inviteUrl}\n`);
|
|
428
|
+
}
|
|
429
|
+
async function runTelegramSetup(rl) {
|
|
430
|
+
sectionHeader('Telegram Bot Token');
|
|
431
|
+
hint('1. Open Telegram and message @BotFather');
|
|
432
|
+
hint('2. Send /newbot and follow the prompts to create a bot');
|
|
433
|
+
hint('3. Copy the token BotFather gives you');
|
|
434
|
+
hintBlank();
|
|
435
|
+
let telegramToken = '';
|
|
436
|
+
while (true) {
|
|
437
|
+
const raw = await askSecret(rl, ` ${C.yellow}>${C.reset} `);
|
|
438
|
+
if (!isNonEmpty(raw)) {
|
|
439
|
+
errMsg('Token cannot be empty. Please try again.');
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
const trimmed = raw.trim();
|
|
443
|
+
if (!isValidTelegramTokenFormat(trimmed)) {
|
|
444
|
+
errMsg('Invalid token format. Telegram tokens look like: 123456:ABCdef...');
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
telegramToken = trimmed;
|
|
448
|
+
break;
|
|
338
449
|
}
|
|
450
|
+
console.log('');
|
|
451
|
+
sectionHeader('Allowed Telegram User IDs');
|
|
452
|
+
hint('Only these users can send messages to the bot.');
|
|
453
|
+
hint('To find your ID: message @userinfobot on Telegram');
|
|
454
|
+
hint('Multiple IDs: separate with commas (e.g. 123456,789012)');
|
|
455
|
+
hintBlank();
|
|
456
|
+
const telegramAllowedUserIds = await promptAllowedUserIds(rl);
|
|
457
|
+
console.log('');
|
|
458
|
+
configLoader_1.ConfigLoader.save({ telegramToken, telegramAllowedUserIds });
|
|
459
|
+
savePlatformsFromState();
|
|
460
|
+
console.log(` ${C.green}Telegram saved!${C.reset}\n`);
|
|
461
|
+
}
|
|
462
|
+
async function runWorkspaceSetup(rl) {
|
|
463
|
+
sectionHeader('Workspace Base Directory');
|
|
464
|
+
hint('The parent directory where your coding projects live.');
|
|
465
|
+
hint('LazyGravity will scan subdirectories as workspaces.');
|
|
466
|
+
hintBlank();
|
|
467
|
+
const workspaceBaseDir = await promptWorkspaceDir(rl);
|
|
468
|
+
console.log('');
|
|
469
|
+
configLoader_1.ConfigLoader.save({ workspaceBaseDir });
|
|
470
|
+
console.log(` ${C.green}Workspace saved!${C.reset}\n`);
|
|
339
471
|
}
|
|
340
472
|
// ---------------------------------------------------------------------------
|
|
341
473
|
// Public action
|
|
342
474
|
// ---------------------------------------------------------------------------
|
|
343
475
|
async function setupAction() {
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
476
|
+
const rl = createInterface();
|
|
477
|
+
try {
|
|
478
|
+
console.log(SETUP_LOGO);
|
|
479
|
+
while (true) {
|
|
480
|
+
const config = configLoader_1.ConfigLoader.readPersisted();
|
|
481
|
+
const discordSt = platformStatus(isDiscordConfigured(config), config.platforms, 'discord');
|
|
482
|
+
const telegramSt = platformStatus(isTelegramConfigured(config), config.platforms, 'telegram');
|
|
483
|
+
const wsLabel = `${C.dim}${workspaceLabel(config)}${C.reset}`;
|
|
484
|
+
const select = await getSelect();
|
|
485
|
+
rl.pause();
|
|
486
|
+
const choice = await select({
|
|
487
|
+
message: 'Configure:',
|
|
488
|
+
choices: [
|
|
489
|
+
{ name: `Discord ${statusBadge(discordSt)}`, value: 'discord' },
|
|
490
|
+
{ name: `Telegram ${statusBadge(telegramSt)}`, value: 'telegram' },
|
|
491
|
+
{ name: `Workspace Directory ${wsLabel}`, value: 'workspace' },
|
|
492
|
+
{ name: `Done — save & exit`, value: 'done' },
|
|
493
|
+
],
|
|
494
|
+
});
|
|
495
|
+
rl.resume();
|
|
496
|
+
switch (choice) {
|
|
497
|
+
case 'discord':
|
|
498
|
+
if (discordSt === 'not_configured') {
|
|
499
|
+
await runDiscordSetup(rl);
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
const action = await platformSubMenu(rl, 'Discord', discordSt);
|
|
503
|
+
switch (action) {
|
|
504
|
+
case 'enable':
|
|
505
|
+
addPlatform('discord');
|
|
506
|
+
console.log(` ${C.green}Discord enabled.${C.reset}\n`);
|
|
507
|
+
break;
|
|
508
|
+
case 'reconfigure':
|
|
509
|
+
await runDiscordSetup(rl);
|
|
510
|
+
break;
|
|
511
|
+
case 'disable':
|
|
512
|
+
removePlatform('discord');
|
|
513
|
+
console.log(` ${C.yellow}Discord disabled.${C.reset} Credentials kept.\n`);
|
|
514
|
+
break;
|
|
515
|
+
case 'back':
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
break;
|
|
520
|
+
case 'telegram':
|
|
521
|
+
if (telegramSt === 'not_configured') {
|
|
522
|
+
await runTelegramSetup(rl);
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
const action = await platformSubMenu(rl, 'Telegram', telegramSt);
|
|
526
|
+
switch (action) {
|
|
527
|
+
case 'enable':
|
|
528
|
+
addPlatform('telegram');
|
|
529
|
+
console.log(` ${C.green}Telegram enabled.${C.reset}\n`);
|
|
530
|
+
break;
|
|
531
|
+
case 'reconfigure':
|
|
532
|
+
await runTelegramSetup(rl);
|
|
533
|
+
break;
|
|
534
|
+
case 'disable':
|
|
535
|
+
removePlatform('telegram');
|
|
536
|
+
console.log(` ${C.yellow}Telegram disabled.${C.reset} Credentials kept.\n`);
|
|
537
|
+
break;
|
|
538
|
+
case 'back':
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
break;
|
|
543
|
+
case 'workspace':
|
|
544
|
+
await runWorkspaceSetup(rl);
|
|
545
|
+
break;
|
|
546
|
+
case 'done': {
|
|
547
|
+
const finalConfig = configLoader_1.ConfigLoader.readPersisted();
|
|
548
|
+
const platforms = finalConfig.platforms ?? [];
|
|
549
|
+
if (platforms.length === 0) {
|
|
550
|
+
errMsg('No platforms enabled yet. Please enable at least one platform.');
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
const configPath = configLoader_1.ConfigLoader.getConfigFilePath();
|
|
554
|
+
console.log(`\n ${C.green}Setup complete!${C.reset} Platforms: ${platforms.join(', ')}\n`);
|
|
555
|
+
console.log(` ${C.dim}Saved to${C.reset} ${configPath}\n`);
|
|
556
|
+
if (platforms.includes('discord') && finalConfig.clientId) {
|
|
557
|
+
const inviteUrl = buildInviteUrl(finalConfig.clientId);
|
|
558
|
+
console.log(` ${C.cyan}Discord:${C.reset}`);
|
|
559
|
+
console.log(` ${C.bold}1.${C.reset} ${C.yellow}Verify Privileged Gateway Intents are enabled${C.reset} in the Bot tab:`);
|
|
560
|
+
console.log(` ${C.dim}Required: PRESENCE INTENT, SERVER MEMBERS INTENT, MESSAGE CONTENT INTENT${C.reset}`);
|
|
561
|
+
console.log(` https://discord.com/developers/applications/${finalConfig.clientId}/bot\n`);
|
|
562
|
+
console.log(` ${C.bold}2.${C.reset} Add the bot to your server:`);
|
|
563
|
+
console.log(` ${inviteUrl}\n`);
|
|
564
|
+
}
|
|
565
|
+
if (platforms.includes('telegram')) {
|
|
566
|
+
console.log(` ${C.cyan}Telegram:${C.reset}`);
|
|
567
|
+
console.log(` ${C.dim}Your Telegram bot is ready. Message it on Telegram after starting.${C.reset}\n`);
|
|
568
|
+
}
|
|
569
|
+
console.log(` ${C.cyan}Start:${C.reset}`);
|
|
570
|
+
console.log(` ${C.bold}1.${C.reset} Open Antigravity with CDP enabled:`);
|
|
571
|
+
console.log(` ${C.green}lazy-gravity open${C.reset}`);
|
|
572
|
+
console.log(` ${C.dim}(auto-selects an available port from: ${cdpPorts_1.CDP_PORTS.join(', ')})${C.reset}\n`);
|
|
573
|
+
console.log(` ${C.bold}2.${C.reset} Run: ${C.green}lazy-gravity start${C.reset}\n`);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
finally {
|
|
580
|
+
rl.close();
|
|
581
|
+
}
|
|
366
582
|
}
|