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.
@@ -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: based on actual Antigravity UI DOM structure
2122
- // Model list uses div.cursor-pointer elements with class 'px-2 py-1 flex items-center justify-between'
2123
- // Currently selected has 'bg-gray-500/20', others have 'hover:bg-gray-500/10'
2124
- // textContent may have "New" suffix
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"]',
@@ -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 LOCK_FILE = path_1.default.resolve(process.cwd(), '.bot.lock');
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
- // Stop existing process and restart
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
- // Remove stale lock file
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.unlinkSync(LOCK_FILE);
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
- catch { /* ignore */ }
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.0",
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": "^27.0.2",
73
+ "ts-morph": "^28.0.0",
73
74
  "ts-node": "^10.9.2",
74
75
  "typescript": "^5.9.3",
75
- "undici": "^7.22.0"
76
+ "undici": "^8.0.3"
76
77
  }
77
78
  }