lazy-gravity 0.0.2 → 0.0.3
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/LICENSE +21 -0
- package/README.md +224 -0
- package/dist/bin/cli.js +79 -0
- package/dist/bin/commands/doctor.js +156 -0
- package/dist/bin/commands/open.js +145 -0
- package/dist/bin/commands/setup.js +366 -0
- package/dist/bin/commands/start.js +15 -0
- package/dist/bot/index.js +914 -0
- package/dist/commands/chatCommandHandler.js +145 -0
- package/dist/commands/cleanupCommandHandler.js +396 -0
- package/dist/commands/messageParser.js +28 -0
- package/dist/commands/registerSlashCommands.js +149 -0
- package/dist/commands/slashCommandHandler.js +104 -0
- package/dist/commands/workspaceCommandHandler.js +230 -0
- package/dist/database/chatSessionRepository.js +88 -0
- package/dist/database/scheduleRepository.js +119 -0
- package/dist/database/templateRepository.js +103 -0
- package/dist/database/workspaceBindingRepository.js +109 -0
- package/dist/events/interactionCreateHandler.js +286 -0
- package/dist/events/messageCreateHandler.js +154 -0
- package/dist/index.js +10 -0
- package/dist/middleware/auth.js +10 -0
- package/dist/middleware/sanitize.js +20 -0
- package/dist/services/antigravityLauncher.js +89 -0
- package/dist/services/approvalDetector.js +384 -0
- package/dist/services/autoAcceptService.js +80 -0
- package/dist/services/cdpBridgeManager.js +204 -0
- package/dist/services/cdpConnectionPool.js +157 -0
- package/dist/services/cdpService.js +1311 -0
- package/dist/services/channelManager.js +118 -0
- package/dist/services/chatSessionService.js +516 -0
- package/dist/services/modeService.js +73 -0
- package/dist/services/modelService.js +63 -0
- package/dist/services/processManager.js +61 -0
- package/dist/services/progressSender.js +61 -0
- package/dist/services/promptDispatcher.js +17 -0
- package/dist/services/quotaService.js +185 -0
- package/dist/services/responseMonitor.js +645 -0
- package/dist/services/scheduleService.js +134 -0
- package/dist/services/screenshotService.js +85 -0
- package/dist/services/titleGeneratorService.js +113 -0
- package/dist/services/workspaceService.js +64 -0
- package/dist/ui/autoAcceptUi.js +34 -0
- package/dist/ui/modeUi.js +34 -0
- package/dist/ui/modelsUi.js +97 -0
- package/dist/ui/screenshotUi.js +51 -0
- package/dist/ui/templateUi.js +67 -0
- package/dist/utils/cdpPorts.js +5 -0
- package/dist/utils/config.js +20 -0
- package/dist/utils/configLoader.js +160 -0
- package/dist/utils/discordFormatter.js +167 -0
- package/dist/utils/i18n.js +77 -0
- package/dist/utils/imageHandler.js +154 -0
- package/dist/utils/lockfile.js +113 -0
- package/dist/utils/logger.js +32 -0
- package/dist/utils/logo.js +13 -0
- package/dist/utils/metadataExtractor.js +15 -0
- package/dist/utils/processLogBuffer.js +98 -0
- package/dist/utils/streamMessageFormatter.js +90 -0
- package/package.json +73 -5
|
@@ -0,0 +1,134 @@
|
|
|
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.ScheduleService = void 0;
|
|
37
|
+
const cron = __importStar(require("node-cron"));
|
|
38
|
+
/**
|
|
39
|
+
* Service class for managing scheduled jobs.
|
|
40
|
+
*
|
|
41
|
+
* - On bot startup, loads schedules from SQLite and re-registers them with node-cron
|
|
42
|
+
* - Handles adding, removing, and listing schedules
|
|
43
|
+
* - Provides bulk stop of all schedules (e.g. on shutdown)
|
|
44
|
+
*/
|
|
45
|
+
class ScheduleService {
|
|
46
|
+
repo;
|
|
47
|
+
/** Map managing active cron tasks (schedule ID -> ScheduledTask) */
|
|
48
|
+
activeTasks = new Map();
|
|
49
|
+
constructor(repo) {
|
|
50
|
+
this.repo = repo;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Called on bot startup. Loads all enabled schedules from DB and registers/resumes them with node-cron.
|
|
54
|
+
*
|
|
55
|
+
* @param jobCallback - Callback invoked when each job executes
|
|
56
|
+
* @returns Number of restored schedules
|
|
57
|
+
*/
|
|
58
|
+
restoreAll(jobCallback) {
|
|
59
|
+
const enabledSchedules = this.repo.findEnabled();
|
|
60
|
+
for (const schedule of enabledSchedules) {
|
|
61
|
+
this.registerCronTask(schedule, jobCallback);
|
|
62
|
+
}
|
|
63
|
+
return enabledSchedules.length;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Add a new schedule.
|
|
67
|
+
* Processes in order: cron expression validation -> DB save -> node-cron registration.
|
|
68
|
+
*
|
|
69
|
+
* @param cronExpression - Cron expression
|
|
70
|
+
* @param prompt - Prompt to execute
|
|
71
|
+
* @param workspacePath - Target workspace path
|
|
72
|
+
* @param jobCallback - Callback for job execution
|
|
73
|
+
* @returns Created schedule record
|
|
74
|
+
* @throws On invalid cron expression
|
|
75
|
+
*/
|
|
76
|
+
addSchedule(cronExpression, prompt, workspacePath, jobCallback) {
|
|
77
|
+
// Validate cron expression
|
|
78
|
+
if (!cron.validate(cronExpression)) {
|
|
79
|
+
throw new Error(`Invalid cron expression: ${cronExpression}`);
|
|
80
|
+
}
|
|
81
|
+
// Save to DB
|
|
82
|
+
const record = this.repo.create({
|
|
83
|
+
cronExpression,
|
|
84
|
+
prompt,
|
|
85
|
+
workspacePath,
|
|
86
|
+
enabled: true,
|
|
87
|
+
});
|
|
88
|
+
// Register with node-cron
|
|
89
|
+
this.registerCronTask(record, jobCallback);
|
|
90
|
+
return record;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Remove a schedule.
|
|
94
|
+
* Stops the running cron job and deletes it from the DB.
|
|
95
|
+
*
|
|
96
|
+
* @param scheduleId - ID of the schedule to remove
|
|
97
|
+
* @returns Whether the removal was successful
|
|
98
|
+
*/
|
|
99
|
+
removeSchedule(scheduleId) {
|
|
100
|
+
// Stop the running cron job
|
|
101
|
+
const task = this.activeTasks.get(scheduleId);
|
|
102
|
+
if (task) {
|
|
103
|
+
task.stop();
|
|
104
|
+
this.activeTasks.delete(scheduleId);
|
|
105
|
+
}
|
|
106
|
+
// Delete from DB
|
|
107
|
+
return this.repo.delete(scheduleId);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Stop all running cron jobs (called on bot shutdown)
|
|
111
|
+
*/
|
|
112
|
+
stopAll() {
|
|
113
|
+
for (const [id, task] of this.activeTasks) {
|
|
114
|
+
task.stop();
|
|
115
|
+
}
|
|
116
|
+
this.activeTasks.clear();
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get a list of all schedules
|
|
120
|
+
*/
|
|
121
|
+
listSchedules() {
|
|
122
|
+
return this.repo.findAll();
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Internal method to register a task with node-cron
|
|
126
|
+
*/
|
|
127
|
+
registerCronTask(schedule, jobCallback) {
|
|
128
|
+
const task = cron.schedule(schedule.cronExpression, () => {
|
|
129
|
+
jobCallback(schedule);
|
|
130
|
+
});
|
|
131
|
+
this.activeTasks.set(schedule.id, task);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
exports.ScheduleService = ScheduleService;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScreenshotService = void 0;
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
/**
|
|
6
|
+
* Service for capturing Antigravity UI screenshots
|
|
7
|
+
*
|
|
8
|
+
* Uses the Chrome DevTools Protocol Page.captureScreenshot command
|
|
9
|
+
* to capture the current browser screen and return it as a Buffer sendable to Discord.
|
|
10
|
+
*/
|
|
11
|
+
class ScreenshotService {
|
|
12
|
+
cdpService;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.cdpService = options.cdpService;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Capture the current screen.
|
|
18
|
+
*
|
|
19
|
+
* @param options Capture options
|
|
20
|
+
* @returns Capture result (Buffer on success, error message on failure)
|
|
21
|
+
*/
|
|
22
|
+
async capture(options = {}) {
|
|
23
|
+
try {
|
|
24
|
+
const params = {
|
|
25
|
+
format: options.format ?? 'png',
|
|
26
|
+
};
|
|
27
|
+
if (options.quality !== undefined) {
|
|
28
|
+
params.quality = options.quality;
|
|
29
|
+
}
|
|
30
|
+
if (options.clip) {
|
|
31
|
+
params.clip = options.clip;
|
|
32
|
+
}
|
|
33
|
+
if (options.captureBeyondViewport !== undefined) {
|
|
34
|
+
params.captureBeyondViewport = options.captureBeyondViewport;
|
|
35
|
+
}
|
|
36
|
+
const result = await this.cdpService.call('Page.captureScreenshot', params);
|
|
37
|
+
const base64Data = result?.data ?? '';
|
|
38
|
+
if (!base64Data) {
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
error: 'Screenshot data was empty.',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const buffer = Buffer.from(base64Data, 'base64');
|
|
45
|
+
return {
|
|
46
|
+
success: true,
|
|
47
|
+
buffer,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
52
|
+
logger_1.logger.error('[ScreenshotService] Error during capture:', error);
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
error: message,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Return a Base64-encoded image string (for use in Discord embeds).
|
|
61
|
+
*
|
|
62
|
+
* @param options Capture options
|
|
63
|
+
* @returns Base64-encoded image string (null on failure)
|
|
64
|
+
*/
|
|
65
|
+
async getBase64(options = {}) {
|
|
66
|
+
try {
|
|
67
|
+
const params = {
|
|
68
|
+
format: options.format ?? 'png',
|
|
69
|
+
};
|
|
70
|
+
if (options.quality !== undefined) {
|
|
71
|
+
params.quality = options.quality;
|
|
72
|
+
}
|
|
73
|
+
if (options.clip) {
|
|
74
|
+
params.clip = options.clip;
|
|
75
|
+
}
|
|
76
|
+
const result = await this.cdpService.call('Page.captureScreenshot', params);
|
|
77
|
+
return result?.data ?? null;
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
logger_1.logger.error('[ScreenshotService] Error while getting Base64:', error);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.ScreenshotService = ScreenshotService;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TitleGeneratorService = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Script to generate channel name titles using Gemini Flash within the Antigravity UI.
|
|
6
|
+
* Makes LLM calls via Antigravity's internal API (language_server).
|
|
7
|
+
* Infers API endpoint and token from existing DOM components.
|
|
8
|
+
*/
|
|
9
|
+
const GENERATE_TITLE_SCRIPT = `(async (userPrompt) => {
|
|
10
|
+
try {
|
|
11
|
+
// Generate title using Antigravity's internal fetch API
|
|
12
|
+
// Look for configuration from __NEXT_DATA__ or window.__remixContext
|
|
13
|
+
const configs = [
|
|
14
|
+
window.__NEXT_DATA__,
|
|
15
|
+
window.__remixContext,
|
|
16
|
+
window.__APP_CONFIG__,
|
|
17
|
+
].filter(Boolean);
|
|
18
|
+
|
|
19
|
+
// Fallback: extract leading text from prompt
|
|
20
|
+
const fallbackTitle = userPrompt
|
|
21
|
+
.replace(/^\\[.*?\\]\\n?/, '')
|
|
22
|
+
.substring(0, 40)
|
|
23
|
+
.trim();
|
|
24
|
+
|
|
25
|
+
return { ok: true, title: fallbackTitle, method: 'text-extract' };
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return { ok: false, error: e.message };
|
|
28
|
+
}
|
|
29
|
+
})`;
|
|
30
|
+
/**
|
|
31
|
+
* Service for generating chat session titles.
|
|
32
|
+
*
|
|
33
|
+
* Strategy:
|
|
34
|
+
* 1. Call Antigravity's Gemini Flash API via CdpService (future implementation)
|
|
35
|
+
* 2. Fallback: extract and sanitize leading text from the user prompt
|
|
36
|
+
*/
|
|
37
|
+
class TitleGeneratorService {
|
|
38
|
+
/**
|
|
39
|
+
* Generate a short title from the user's prompt
|
|
40
|
+
* @param prompt User's prompt
|
|
41
|
+
* @param cdpService Optional CdpService instance
|
|
42
|
+
*/
|
|
43
|
+
async generateTitle(prompt, cdpService) {
|
|
44
|
+
// Attempt to use Antigravity's LLM via CDP
|
|
45
|
+
if (cdpService) {
|
|
46
|
+
try {
|
|
47
|
+
const title = await this.generateViaCdp(prompt, cdpService);
|
|
48
|
+
if (title)
|
|
49
|
+
return title;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Fall through to fallback
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Fallback: text extraction
|
|
56
|
+
return this.extractTitleFromText(prompt);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Generate a title by calling Antigravity's LLM API via CDP
|
|
60
|
+
*/
|
|
61
|
+
async generateViaCdp(prompt, cdpService) {
|
|
62
|
+
try {
|
|
63
|
+
const contextId = cdpService.getPrimaryContextId();
|
|
64
|
+
const cleanPrompt = this.stripWorkspacePrefix(prompt);
|
|
65
|
+
const callParams = {
|
|
66
|
+
expression: `${GENERATE_TITLE_SCRIPT}(${JSON.stringify(cleanPrompt)})`,
|
|
67
|
+
returnByValue: true,
|
|
68
|
+
awaitPromise: true,
|
|
69
|
+
};
|
|
70
|
+
if (contextId !== null) {
|
|
71
|
+
callParams.contextId = contextId;
|
|
72
|
+
}
|
|
73
|
+
const result = await cdpService.call('Runtime.evaluate', callParams);
|
|
74
|
+
const value = result?.result?.value;
|
|
75
|
+
if (value?.ok && value?.title) {
|
|
76
|
+
return this.sanitizeForChannelName(value.title);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Fall through to fallback
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Extract a title from the prompt text (fallback)
|
|
86
|
+
*/
|
|
87
|
+
extractTitleFromText(prompt) {
|
|
88
|
+
const cleanPrompt = this.stripWorkspacePrefix(prompt);
|
|
89
|
+
const truncated = cleanPrompt.substring(0, 40).trim();
|
|
90
|
+
return this.sanitizeForChannelName(truncated) || 'untitled';
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Strip the workspace prefix
|
|
94
|
+
*/
|
|
95
|
+
stripWorkspacePrefix(prompt) {
|
|
96
|
+
return prompt.replace(/^\[ワークスペース:.*?\]\n?/, '');
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Sanitize text into a format suitable for Discord channel names
|
|
100
|
+
*/
|
|
101
|
+
sanitizeForChannelName(text) {
|
|
102
|
+
const sanitized = text
|
|
103
|
+
.toLowerCase()
|
|
104
|
+
.replace(/\s+/g, '-')
|
|
105
|
+
// Allowed in Discord channel names: alphanumeric, hyphen, underscore, CJK characters
|
|
106
|
+
.replace(/[^a-z0-9\-_\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf]/g, '-')
|
|
107
|
+
.replace(/-{2,}/g, '-')
|
|
108
|
+
.replace(/^-+|-+$/g, '')
|
|
109
|
+
.substring(0, 80);
|
|
110
|
+
return sanitized || 'untitled';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.TitleGeneratorService = TitleGeneratorService;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.WorkspaceService = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const sanitize_1 = require("../middleware/sanitize");
|
|
9
|
+
/**
|
|
10
|
+
* Service for workspace filesystem operations and path validation.
|
|
11
|
+
* Manages directories under WORKSPACE_BASE_DIR.
|
|
12
|
+
*/
|
|
13
|
+
class WorkspaceService {
|
|
14
|
+
baseDir;
|
|
15
|
+
constructor(baseDir) {
|
|
16
|
+
this.baseDir = baseDir;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Ensure the base directory exists, creating it if necessary
|
|
20
|
+
*/
|
|
21
|
+
ensureBaseDir() {
|
|
22
|
+
if (!fs_1.default.existsSync(this.baseDir)) {
|
|
23
|
+
fs_1.default.mkdirSync(this.baseDir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Return a list of subdirectories in the base directory
|
|
28
|
+
*/
|
|
29
|
+
scanWorkspaces() {
|
|
30
|
+
this.ensureBaseDir();
|
|
31
|
+
const entries = fs_1.default.readdirSync(this.baseDir, { withFileTypes: true });
|
|
32
|
+
return entries
|
|
33
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
|
|
34
|
+
.map((entry) => entry.name)
|
|
35
|
+
.sort();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Validate a relative path and return a safe absolute path
|
|
39
|
+
* @throws On path traversal detection
|
|
40
|
+
*/
|
|
41
|
+
validatePath(relativePath) {
|
|
42
|
+
return (0, sanitize_1.resolveSafePath)(relativePath, this.baseDir);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get the base directory path
|
|
46
|
+
*/
|
|
47
|
+
getBaseDir() {
|
|
48
|
+
return this.baseDir;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Return the absolute path of the specified workspace
|
|
52
|
+
*/
|
|
53
|
+
getWorkspacePath(workspaceName) {
|
|
54
|
+
return this.validatePath(workspaceName);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if the specified workspace exists
|
|
58
|
+
*/
|
|
59
|
+
exists(workspaceName) {
|
|
60
|
+
const fullPath = this.validatePath(workspaceName);
|
|
61
|
+
return fs_1.default.existsSync(fullPath) && fs_1.default.statSync(fullPath).isDirectory();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.WorkspaceService = WorkspaceService;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AUTOACCEPT_BTN_REFRESH = exports.AUTOACCEPT_BTN_OFF = exports.AUTOACCEPT_BTN_ON = void 0;
|
|
4
|
+
exports.sendAutoAcceptUI = sendAutoAcceptUI;
|
|
5
|
+
const discord_js_1 = require("discord.js");
|
|
6
|
+
exports.AUTOACCEPT_BTN_ON = 'autoaccept_btn_on';
|
|
7
|
+
exports.AUTOACCEPT_BTN_OFF = 'autoaccept_btn_off';
|
|
8
|
+
exports.AUTOACCEPT_BTN_REFRESH = 'autoaccept_btn_refresh';
|
|
9
|
+
async function sendAutoAcceptUI(target, autoAcceptService) {
|
|
10
|
+
const enabled = autoAcceptService.isEnabled();
|
|
11
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
12
|
+
.setTitle('Auto-accept Management')
|
|
13
|
+
.setColor(enabled ? 0x2ECC71 : 0x95A5A6)
|
|
14
|
+
.setDescription(`**Current Status:** ${enabled ? '🟢 ON' : '⚪ OFF'}\n\n` +
|
|
15
|
+
'ON: approval dialogs are automatically allowed.\n' +
|
|
16
|
+
'OFF: approval dialogs require manual action.')
|
|
17
|
+
.setFooter({ text: 'Use buttons below to change mode' })
|
|
18
|
+
.setTimestamp();
|
|
19
|
+
const row = new discord_js_1.ActionRowBuilder().addComponents(new discord_js_1.ButtonBuilder()
|
|
20
|
+
.setCustomId(exports.AUTOACCEPT_BTN_ON)
|
|
21
|
+
.setLabel('Turn ON')
|
|
22
|
+
.setStyle(enabled ? discord_js_1.ButtonStyle.Success : discord_js_1.ButtonStyle.Secondary), new discord_js_1.ButtonBuilder()
|
|
23
|
+
.setCustomId(exports.AUTOACCEPT_BTN_OFF)
|
|
24
|
+
.setLabel('Turn OFF')
|
|
25
|
+
.setStyle(!enabled ? discord_js_1.ButtonStyle.Danger : discord_js_1.ButtonStyle.Secondary), new discord_js_1.ButtonBuilder()
|
|
26
|
+
.setCustomId(exports.AUTOACCEPT_BTN_REFRESH)
|
|
27
|
+
.setLabel('Refresh')
|
|
28
|
+
.setStyle(discord_js_1.ButtonStyle.Primary));
|
|
29
|
+
await target.editReply({
|
|
30
|
+
content: '',
|
|
31
|
+
embeds: [embed],
|
|
32
|
+
components: [row],
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendModeUI = sendModeUI;
|
|
4
|
+
const discord_js_1 = require("discord.js");
|
|
5
|
+
const modeService_1 = require("../services/modeService");
|
|
6
|
+
/**
|
|
7
|
+
* Build and send the interactive UI for the /mode command (dropdown style)
|
|
8
|
+
*/
|
|
9
|
+
async function sendModeUI(target, modeService) {
|
|
10
|
+
const currentMode = modeService.getCurrentMode();
|
|
11
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
12
|
+
.setTitle('Mode Management')
|
|
13
|
+
.setColor(0x57F287)
|
|
14
|
+
.setDescription(`**Current Mode:** ${modeService_1.MODE_DISPLAY_NAMES[currentMode] || currentMode}\n` +
|
|
15
|
+
`${modeService_1.MODE_DESCRIPTIONS[currentMode] || ''}\n\n` +
|
|
16
|
+
`**Available Modes (${modeService_1.AVAILABLE_MODES.length})**\n` +
|
|
17
|
+
modeService_1.AVAILABLE_MODES.map(m => {
|
|
18
|
+
const icon = m === currentMode ? '[x]' : '[ ]';
|
|
19
|
+
return `${icon} **${modeService_1.MODE_DISPLAY_NAMES[m] || m}** — ${modeService_1.MODE_DESCRIPTIONS[m] || ''}`;
|
|
20
|
+
}).join('\n'))
|
|
21
|
+
.setFooter({ text: 'Select a mode from the dropdown below' })
|
|
22
|
+
.setTimestamp();
|
|
23
|
+
const selectMenu = new discord_js_1.StringSelectMenuBuilder()
|
|
24
|
+
.setCustomId('mode_select')
|
|
25
|
+
.setPlaceholder('Select a mode...')
|
|
26
|
+
.addOptions(modeService_1.AVAILABLE_MODES.map(m => ({
|
|
27
|
+
label: modeService_1.MODE_DISPLAY_NAMES[m] || m,
|
|
28
|
+
description: modeService_1.MODE_DESCRIPTIONS[m] || '',
|
|
29
|
+
value: m,
|
|
30
|
+
default: m === currentMode,
|
|
31
|
+
})));
|
|
32
|
+
const row = new discord_js_1.ActionRowBuilder().addComponents(selectMenu);
|
|
33
|
+
await target.editReply({ content: '', embeds: [embed], components: [row] });
|
|
34
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendModelsUI = sendModelsUI;
|
|
4
|
+
const discord_js_1 = require("discord.js");
|
|
5
|
+
/**
|
|
6
|
+
* Build and send the interactive UI for the /models command
|
|
7
|
+
*/
|
|
8
|
+
async function sendModelsUI(target, deps) {
|
|
9
|
+
const cdp = deps.getCurrentCdp();
|
|
10
|
+
if (!cdp) {
|
|
11
|
+
await target.editReply({ content: 'Not connected to CDP.' });
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const models = await cdp.getUiModels();
|
|
15
|
+
const currentModel = await cdp.getCurrentModel();
|
|
16
|
+
const quotaData = await deps.fetchQuota();
|
|
17
|
+
if (models.length === 0) {
|
|
18
|
+
await target.editReply({ content: 'Failed to retrieve model list from Antigravity.' });
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
function formatQuota(mName, current) {
|
|
22
|
+
if (!mName)
|
|
23
|
+
return `${current ? '[x]' : '[ ]'} Unknown`;
|
|
24
|
+
const normalize = (s) => s.toLowerCase().replace(/[\s\-_]/g, '');
|
|
25
|
+
const nName = normalize(mName);
|
|
26
|
+
const q = quotaData.find(q => {
|
|
27
|
+
const nLabel = normalize(q.label);
|
|
28
|
+
const nModel = normalize(q.model || '');
|
|
29
|
+
return nLabel === nName || nModel === nName
|
|
30
|
+
|| nName.includes(nLabel) || nLabel.includes(nName)
|
|
31
|
+
|| (nModel && (nName.includes(nModel) || nModel.includes(nName)));
|
|
32
|
+
});
|
|
33
|
+
if (!q || !q.quotaInfo)
|
|
34
|
+
return `${current ? '[x]' : '[ ]'} ${mName}`;
|
|
35
|
+
const rem = q.quotaInfo.remainingFraction;
|
|
36
|
+
const resetTime = q.quotaInfo.resetTime ? new Date(q.quotaInfo.resetTime) : null;
|
|
37
|
+
const diffMs = resetTime ? resetTime.getTime() - Date.now() : 0;
|
|
38
|
+
let timeStr = 'Ready';
|
|
39
|
+
if (diffMs > 0) {
|
|
40
|
+
const mins = Math.ceil(diffMs / 60000);
|
|
41
|
+
if (mins < 60)
|
|
42
|
+
timeStr = `${mins}m`;
|
|
43
|
+
else
|
|
44
|
+
timeStr = `${Math.floor(mins / 60)}h ${mins % 60}m`;
|
|
45
|
+
}
|
|
46
|
+
if (rem !== undefined && rem !== null) {
|
|
47
|
+
const percent = Math.round(rem * 100);
|
|
48
|
+
let icon = '🟢';
|
|
49
|
+
if (percent <= 20)
|
|
50
|
+
icon = '🔴';
|
|
51
|
+
else if (percent <= 50)
|
|
52
|
+
icon = '🟡';
|
|
53
|
+
return `${current ? '[x]' : '[ ]'} ${mName} ${icon} ${percent}% (⏱️ ${timeStr})`;
|
|
54
|
+
}
|
|
55
|
+
return `${current ? '[x]' : '[ ]'} ${mName} (⏱️ ${timeStr})`;
|
|
56
|
+
}
|
|
57
|
+
const currentModelFormatted = currentModel ? formatQuota(currentModel, true) : 'Unknown';
|
|
58
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
59
|
+
.setTitle('Model Management')
|
|
60
|
+
.setColor(0x5865F2)
|
|
61
|
+
.setDescription(`**Current Model:**\n${currentModelFormatted}\n\n` +
|
|
62
|
+
`**Available Models (${models.length})**\n` +
|
|
63
|
+
models.map(m => formatQuota(m, m === currentModel)).join('\n'))
|
|
64
|
+
.setFooter({ text: 'Latest quota information retrieved' })
|
|
65
|
+
.setTimestamp();
|
|
66
|
+
const rows = [];
|
|
67
|
+
let currentRow = new discord_js_1.ActionRowBuilder();
|
|
68
|
+
for (const mName of models.slice(0, 24)) {
|
|
69
|
+
if (currentRow.components.length === 5) {
|
|
70
|
+
rows.push(currentRow);
|
|
71
|
+
currentRow = new discord_js_1.ActionRowBuilder();
|
|
72
|
+
}
|
|
73
|
+
const safeName = mName.length > 80 ? mName.substring(0, 77) + '...' : mName;
|
|
74
|
+
currentRow.addComponents(new discord_js_1.ButtonBuilder()
|
|
75
|
+
.setCustomId(`model_btn_${mName}`)
|
|
76
|
+
.setLabel(safeName)
|
|
77
|
+
.setStyle(mName === currentModel ? discord_js_1.ButtonStyle.Success : discord_js_1.ButtonStyle.Secondary));
|
|
78
|
+
}
|
|
79
|
+
if (currentRow.components.length < 5) {
|
|
80
|
+
currentRow.addComponents(new discord_js_1.ButtonBuilder()
|
|
81
|
+
.setCustomId('model_refresh_btn')
|
|
82
|
+
.setLabel('Refresh')
|
|
83
|
+
.setStyle(discord_js_1.ButtonStyle.Primary));
|
|
84
|
+
rows.push(currentRow);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
rows.push(currentRow);
|
|
88
|
+
if (rows.length < 5) {
|
|
89
|
+
const refreshRow = new discord_js_1.ActionRowBuilder().addComponents(new discord_js_1.ButtonBuilder()
|
|
90
|
+
.setCustomId('model_refresh_btn')
|
|
91
|
+
.setLabel('Refresh')
|
|
92
|
+
.setStyle(discord_js_1.ButtonStyle.Primary));
|
|
93
|
+
rows.push(refreshRow);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
await target.editReply({ content: '', embeds: [embed], components: rows });
|
|
97
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleScreenshot = handleScreenshot;
|
|
4
|
+
const discord_js_1 = require("discord.js");
|
|
5
|
+
const screenshotService_1 = require("../services/screenshotService");
|
|
6
|
+
/**
|
|
7
|
+
* Capture a screenshot and send it to Discord
|
|
8
|
+
*/
|
|
9
|
+
async function handleScreenshot(target, cdp) {
|
|
10
|
+
if (!cdp) {
|
|
11
|
+
const content = 'Not connected to Antigravity.';
|
|
12
|
+
if (target instanceof discord_js_1.Message) {
|
|
13
|
+
await target.reply(content);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
await target.editReply({ content });
|
|
17
|
+
}
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const screenshot = new screenshotService_1.ScreenshotService({ cdpService: cdp });
|
|
22
|
+
const result = await screenshot.capture({ format: 'png' });
|
|
23
|
+
if (result.success && result.buffer) {
|
|
24
|
+
const attachment = new discord_js_1.AttachmentBuilder(result.buffer, { name: 'screenshot.png' });
|
|
25
|
+
if (target instanceof discord_js_1.Message) {
|
|
26
|
+
await target.reply({ files: [attachment] });
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
await target.editReply({ files: [attachment] });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const content = `Screenshot failed: ${result.error}`;
|
|
34
|
+
if (target instanceof discord_js_1.Message) {
|
|
35
|
+
await target.reply(content);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
await target.editReply({ content });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
const content = `Screenshot error: ${e.message}`;
|
|
44
|
+
if (target instanceof discord_js_1.Message) {
|
|
45
|
+
await target.reply(content);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
await target.editReply({ content });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TEMPLATE_BTN_PREFIX = void 0;
|
|
4
|
+
exports.parseTemplateButtonId = parseTemplateButtonId;
|
|
5
|
+
exports.sendTemplateUI = sendTemplateUI;
|
|
6
|
+
const discord_js_1 = require("discord.js");
|
|
7
|
+
/** Button customId prefix. Format: template_btn_<id> */
|
|
8
|
+
exports.TEMPLATE_BTN_PREFIX = 'template_btn_';
|
|
9
|
+
const MAX_PROMPT_PREVIEW_LEN = 60;
|
|
10
|
+
const MAX_BUTTONS = 25;
|
|
11
|
+
/**
|
|
12
|
+
* Extract template ID from a button customId.
|
|
13
|
+
* Returns NaN if the customId does not match the expected format.
|
|
14
|
+
*/
|
|
15
|
+
function parseTemplateButtonId(customId) {
|
|
16
|
+
if (!customId.startsWith(exports.TEMPLATE_BTN_PREFIX))
|
|
17
|
+
return NaN;
|
|
18
|
+
return parseInt(customId.slice(exports.TEMPLATE_BTN_PREFIX.length), 10);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build and send the template list UI with clickable buttons.
|
|
22
|
+
* Follows the same pattern as modelsUi.ts.
|
|
23
|
+
*/
|
|
24
|
+
async function sendTemplateUI(target, templates) {
|
|
25
|
+
if (templates.length === 0) {
|
|
26
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
27
|
+
.setTitle('Template Management')
|
|
28
|
+
.setColor(0x57F287)
|
|
29
|
+
.setDescription('No templates registered.\n\n' +
|
|
30
|
+
'Use `/template add name:<name> prompt:<prompt>` to add one.')
|
|
31
|
+
.setTimestamp();
|
|
32
|
+
await target.editReply({ content: '', embeds: [embed], components: [] });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const truncate = (text, max) => text.length > max ? `${text.substring(0, max - 3)}...` : text;
|
|
36
|
+
const displayTemplates = templates.slice(0, MAX_BUTTONS);
|
|
37
|
+
const hasMore = templates.length > MAX_BUTTONS;
|
|
38
|
+
const description = displayTemplates
|
|
39
|
+
.map((tpl, i) => `**${i + 1}. ${tpl.name}**\n> ${truncate(tpl.prompt, MAX_PROMPT_PREVIEW_LEN)}`)
|
|
40
|
+
.join('\n\n');
|
|
41
|
+
const footerText = hasMore
|
|
42
|
+
? `${templates.length - MAX_BUTTONS} templates are hidden. Use /template use <name> to execute directly.`
|
|
43
|
+
: 'Click a button to execute the template';
|
|
44
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
45
|
+
.setTitle('Template Management')
|
|
46
|
+
.setColor(0x57F287)
|
|
47
|
+
.setDescription(`**Registered Templates (${templates.length})**\n\n${description}`)
|
|
48
|
+
.setFooter({ text: footerText })
|
|
49
|
+
.setTimestamp();
|
|
50
|
+
const rows = [];
|
|
51
|
+
let currentRow = new discord_js_1.ActionRowBuilder();
|
|
52
|
+
for (const tpl of displayTemplates) {
|
|
53
|
+
if (currentRow.components.length === 5) {
|
|
54
|
+
rows.push(currentRow);
|
|
55
|
+
currentRow = new discord_js_1.ActionRowBuilder();
|
|
56
|
+
}
|
|
57
|
+
const safeLabel = tpl.name.length > 80 ? `${tpl.name.substring(0, 77)}...` : tpl.name;
|
|
58
|
+
currentRow.addComponents(new discord_js_1.ButtonBuilder()
|
|
59
|
+
.setCustomId(`${exports.TEMPLATE_BTN_PREFIX}${tpl.id}`)
|
|
60
|
+
.setLabel(safeLabel)
|
|
61
|
+
.setStyle(discord_js_1.ButtonStyle.Primary));
|
|
62
|
+
}
|
|
63
|
+
if (currentRow.components.length > 0) {
|
|
64
|
+
rows.push(currentRow);
|
|
65
|
+
}
|
|
66
|
+
await target.editReply({ content: '', embeds: [embed], components: rows });
|
|
67
|
+
}
|