lazy-gravity 0.7.0 → 0.7.2
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/dist/services/cdpService.js +14 -5
- package/dist/utils/lockfile.js +33 -41
- package/package.json +4 -3
|
@@ -1912,6 +1912,8 @@ class CdpService extends events_1.EventEmitter {
|
|
|
1912
1912
|
'[role="button"]',
|
|
1913
1913
|
'div.cursor-pointer',
|
|
1914
1914
|
'div[class*="cursor-pointer"]',
|
|
1915
|
+
'span[class*="select-none"]',
|
|
1916
|
+
'span[class*="text-xs"]',
|
|
1915
1917
|
];
|
|
1916
1918
|
const getScopes = () => {
|
|
1917
1919
|
const scopes = [document];
|
|
@@ -1956,6 +1958,8 @@ class CdpService extends events_1.EventEmitter {
|
|
|
1956
1958
|
'[role="button"]',
|
|
1957
1959
|
'div.cursor-pointer',
|
|
1958
1960
|
'div[class*="cursor-pointer"]',
|
|
1961
|
+
'span[class*="select-none"]',
|
|
1962
|
+
'span[class*="overflow-hidden"]',
|
|
1959
1963
|
];
|
|
1960
1964
|
|
|
1961
1965
|
let models = collectModels();
|
|
@@ -2070,7 +2074,8 @@ class CdpService extends events_1.EventEmitter {
|
|
|
2070
2074
|
};
|
|
2071
2075
|
const candidates = Array.from(document.querySelectorAll(
|
|
2072
2076
|
'[role="option"], [role="menuitem"], [role="combobox"], [aria-selected], [aria-checked], ' +
|
|
2073
|
-
'[aria-current], button, [role="button"], div.cursor-pointer, div[class*="cursor-pointer"]'
|
|
2077
|
+
'[aria-current], button, [role="button"], div.cursor-pointer, div[class*="cursor-pointer"], ' +
|
|
2078
|
+
'span[class*="select-none"], span[class*="text-xs"]'
|
|
2074
2079
|
))
|
|
2075
2080
|
.filter(isVisible)
|
|
2076
2081
|
.map((el) => ({ el, label: getLabel(el), score: getScore(el) }))
|
|
@@ -2118,10 +2123,10 @@ class CdpService extends events_1.EventEmitter {
|
|
|
2118
2123
|
if (!this.isConnectedFlag || !this.ws) {
|
|
2119
2124
|
await this.reconnectOnDemand();
|
|
2120
2125
|
}
|
|
2121
|
-
// DOM manipulation script:
|
|
2122
|
-
//
|
|
2123
|
-
//
|
|
2124
|
-
//
|
|
2126
|
+
// DOM manipulation script: adaptive Antigravity UI model picker
|
|
2127
|
+
// Legacy (<v1.107): div.cursor-pointer with class 'px-2 py-1 flex items-center justify-between'
|
|
2128
|
+
// v1.107+: span elements with classes 'select-none', 'text-xs', 'overflow-hidden'
|
|
2129
|
+
// Selection detected via ARIA attributes, data-state, or bg-gray-500/20 class
|
|
2125
2130
|
const safeModel = JSON.stringify(modelName);
|
|
2126
2131
|
const expression = `(async () => {
|
|
2127
2132
|
const targetModel = ${safeModel};
|
|
@@ -2160,6 +2165,8 @@ class CdpService extends events_1.EventEmitter {
|
|
|
2160
2165
|
'[role="button"]',
|
|
2161
2166
|
'div.cursor-pointer',
|
|
2162
2167
|
'div[class*="cursor-pointer"]',
|
|
2168
|
+
'span[class*="select-none"]',
|
|
2169
|
+
'span[class*="text-xs"]',
|
|
2163
2170
|
];
|
|
2164
2171
|
const triggerSelectors = [
|
|
2165
2172
|
'[role="combobox"]',
|
|
@@ -2170,6 +2177,8 @@ class CdpService extends events_1.EventEmitter {
|
|
|
2170
2177
|
'[role="button"]',
|
|
2171
2178
|
'div.cursor-pointer',
|
|
2172
2179
|
'div[class*="cursor-pointer"]',
|
|
2180
|
+
'span[class*="select-none"]',
|
|
2181
|
+
'span[class*="overflow-hidden"]',
|
|
2173
2182
|
];
|
|
2174
2183
|
const scopeSelectors = [
|
|
2175
2184
|
'[role="dialog"]',
|
package/dist/utils/lockfile.js
CHANGED
|
@@ -6,8 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.acquireLock = acquireLock;
|
|
7
7
|
const logger_1 = require("./logger");
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
9
10
|
const path_1 = __importDefault(require("path"));
|
|
10
|
-
const
|
|
11
|
+
const LOCK_DIR = process.env.XDG_RUNTIME_DIR || path_1.default.join(os_1.default.tmpdir(), `lazygravity-${process.getuid ? process.getuid() : 'user'}`);
|
|
12
|
+
const LOCK_FILE = path_1.default.join(LOCK_DIR, '.bot.lock');
|
|
11
13
|
/**
|
|
12
14
|
* Check if a process with the given PID is running
|
|
13
15
|
*/
|
|
@@ -20,64 +22,54 @@ function isProcessRunning(pid) {
|
|
|
20
22
|
return false;
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Stop an existing process and wait for it to exit
|
|
25
|
-
*/
|
|
26
|
-
function killExistingProcess(pid) {
|
|
27
|
-
logger_1.logger.warn(`🔄 Stopping existing Bot process (PID: ${pid})...`);
|
|
28
|
-
try {
|
|
29
|
-
process.kill(pid, 'SIGTERM');
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
// Ignore if already terminated
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
// Wait up to 5 seconds for process to exit
|
|
36
|
-
const deadline = Date.now() + 5000;
|
|
37
|
-
while (Date.now() < deadline) {
|
|
38
|
-
if (!isProcessRunning(pid)) {
|
|
39
|
-
logger_1.logger.info(`✅ Existing process (PID: ${pid}) stopped`);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
// Wait 50ms (busy wait)
|
|
43
|
-
const waitUntil = Date.now() + 50;
|
|
44
|
-
while (Date.now() < waitUntil) { /* spin */ }
|
|
45
|
-
}
|
|
46
|
-
// Timeout: force kill with SIGKILL
|
|
47
|
-
logger_1.logger.warn(`⚠️ Process did not exit with SIGTERM, force killing (SIGKILL)`);
|
|
48
|
-
try {
|
|
49
|
-
process.kill(pid, 'SIGKILL');
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
// ignore
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
25
|
/**
|
|
56
26
|
* Acquire a lockfile to prevent duplicate bot instances.
|
|
57
|
-
* If another process is already running, stop it before starting.
|
|
58
27
|
*
|
|
59
28
|
* @returns A function to release the lock
|
|
60
29
|
*/
|
|
61
30
|
function acquireLock() {
|
|
31
|
+
fs_1.default.mkdirSync(LOCK_DIR, { recursive: true, mode: 0o700 });
|
|
32
|
+
const dirStat = fs_1.default.lstatSync(LOCK_DIR);
|
|
33
|
+
if (!dirStat.isDirectory()) {
|
|
34
|
+
throw new Error(`Lock path is not a directory: ${LOCK_DIR}`);
|
|
35
|
+
}
|
|
36
|
+
if (typeof process.getuid === 'function' && dirStat.uid !== process.getuid()) {
|
|
37
|
+
throw new Error(`Lock directory is not owned by current user: ${LOCK_DIR}`);
|
|
38
|
+
}
|
|
39
|
+
if (process.platform !== 'win32' && (dirStat.mode & 0o077) !== 0) {
|
|
40
|
+
throw new Error(`Lock directory has overly permissive permissions: ${LOCK_DIR}`);
|
|
41
|
+
}
|
|
62
42
|
// Check existing lock file
|
|
63
43
|
if (fs_1.default.existsSync(LOCK_FILE)) {
|
|
64
44
|
const content = fs_1.default.readFileSync(LOCK_FILE, 'utf-8').trim();
|
|
65
45
|
const existingPid = parseInt(content, 10);
|
|
66
46
|
if (!isNaN(existingPid) && existingPid !== process.pid && isProcessRunning(existingPid)) {
|
|
67
|
-
|
|
68
|
-
killExistingProcess(existingPid);
|
|
47
|
+
throw new Error(`Another Bot process is already running (PID: ${existingPid})`);
|
|
69
48
|
}
|
|
70
49
|
else if (!isNaN(existingPid) && !isProcessRunning(existingPid)) {
|
|
71
50
|
logger_1.logger.warn(`⚠️ Stale lock file detected (PID: ${existingPid} has exited). Cleaning up.`);
|
|
51
|
+
try {
|
|
52
|
+
fs_1.default.unlinkSync(LOCK_FILE);
|
|
53
|
+
}
|
|
54
|
+
catch { /* ignore */ }
|
|
72
55
|
}
|
|
73
|
-
|
|
56
|
+
}
|
|
57
|
+
// Create new lock file atomically
|
|
58
|
+
try {
|
|
59
|
+
const fd = fs_1.default.openSync(LOCK_FILE, 'wx', 0o600);
|
|
74
60
|
try {
|
|
75
|
-
fs_1.default.
|
|
61
|
+
fs_1.default.writeFileSync(fd, String(process.pid), { encoding: 'utf-8' });
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
fs_1.default.closeSync(fd);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
if (err?.code === 'EEXIST') {
|
|
69
|
+
throw new Error('Another Bot process is already running');
|
|
76
70
|
}
|
|
77
|
-
|
|
71
|
+
throw err;
|
|
78
72
|
}
|
|
79
|
-
// Create new lock file
|
|
80
|
-
fs_1.default.writeFileSync(LOCK_FILE, String(process.pid), 'utf-8');
|
|
81
73
|
logger_1.logger.info(`🔒 Lock acquired (PID: ${process.pid})`);
|
|
82
74
|
// Cleanup function
|
|
83
75
|
const releaseLock = () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lazy-gravity",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Control Antigravity from anywhere — a local, secure bot (Discord + Telegram) that lets you remotely operate Antigravity on your home PC from your smartphone.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -65,13 +65,14 @@
|
|
|
65
65
|
"@types/ws": "^8.18.1",
|
|
66
66
|
"jest": "^30.2.0",
|
|
67
67
|
"jest-environment-jsdom": "^30.2.0",
|
|
68
|
+
"jest-util": "^30.2.0",
|
|
68
69
|
"jsdom": "^29.0.0",
|
|
69
70
|
"minimatch": "^10.2.1",
|
|
70
71
|
"semantic-release": "^25.0.3",
|
|
71
72
|
"ts-jest": "^29.4.6",
|
|
72
|
-
"ts-morph": "^
|
|
73
|
+
"ts-morph": "^28.0.0",
|
|
73
74
|
"ts-node": "^10.9.2",
|
|
74
75
|
"typescript": "^5.9.3",
|
|
75
|
-
"undici": "^
|
|
76
|
+
"undici": "^8.0.3"
|
|
76
77
|
}
|
|
77
78
|
}
|