antigravity-rtl-patcher 1.0.0 ā 2.0.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 +79 -41
- package/bin/cli.js +31 -8
- package/package.json +24 -6
- package/payload/rtl-engine.js +136 -53
- package/payload/styles.css +27 -10
- package/src/patcher.js +239 -86
- package/src/paths.js +99 -19
- package/src/utils.js +17 -8
package/README.md
CHANGED
|
@@ -1,77 +1,115 @@
|
|
|
1
1
|
# š Antigravity RTL Patcher
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
|
-
<img src="https://img.shields.io/npm/v/antigravity-rtl-patcher?color=cyan&label=Version&style=for-the-badge" alt="Version">
|
|
5
|
-
<img src="https://img.shields.io/npm/dt/antigravity-rtl-patcher?color=magenta&style=for-the-badge" alt="Downloads">
|
|
6
|
-
<img src="https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge" alt="License">
|
|
7
|
-
</div>
|
|
8
4
|
|
|
9
|
-
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
**Intelligent RTL & Persian Typography Engine for Antigravity IDE**
|
|
11
|
+
|
|
12
|
+
*One command. Full right-to-left support. Zero config.*
|
|
10
13
|
|
|
11
|
-
<div align="center">
|
|
12
|
-
<h3>An elegant, intelligent, and seamless RTL & Typography patcher for Antigravity IDE.</h3>
|
|
13
|
-
<p>Bring native support for Persian, Arabic, and Hebrew to your favorite AI IDE.</p>
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
##
|
|
19
|
-
Antigravity IDE (like many modern AI editors) is built on Electron. Out of the box, it lacks proper Right-to-Left (RTL) support. When AI generates Persian or Arabic text, the alignment breaks, fonts look terrible, and mixed English/RTL sentences become unreadable.
|
|
18
|
+
## 𧬠How It Works
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
Antigravity IDE is built on Electron. This CLI tool directly patches the IDE's core workbench files to inject a smart **MutationObserver** engine that:
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- **āļø OS Agnostic:** Works on Windows, macOS, and Linux out of the box.
|
|
22
|
+
1. š **Watches** the DOM for new chat messages and AI responses in real-time
|
|
23
|
+
2. š§ **Detects** Persian/Arabic/Hebrew text using Unicode analysis with ratio-based scoring
|
|
24
|
+
3. šØ **Applies** `direction: rtl` and `unicode-bidi: plaintext` only where needed
|
|
25
|
+
4. āļø **Injects** the beautiful **Vazirmatn** font for perfect Persian/Arabic readability
|
|
26
|
+
5. š **Isolates** English code blocks inside RTL text so they don't break
|
|
29
27
|
|
|
30
28
|
---
|
|
31
29
|
|
|
32
|
-
##
|
|
33
|
-
|
|
34
|
-
You don't even need to download the repository. Just run it directly via `npx`!
|
|
35
|
-
|
|
36
|
-
### 1. Patch the IDE
|
|
37
|
-
To inject RTL support, run the following command in your terminal:
|
|
30
|
+
## ā” Quick Start
|
|
38
31
|
|
|
39
32
|
```bash
|
|
40
|
-
#
|
|
33
|
+
# Linux / macOS
|
|
34
|
+
npx antigravity-rtl-patcher patch
|
|
35
|
+
|
|
36
|
+
# If installed in system directory (e.g., /opt/)
|
|
41
37
|
sudo npx antigravity-rtl-patcher patch
|
|
38
|
+
```
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
```bash
|
|
41
|
+
# Windows (Run terminal as Administrator if needed)
|
|
44
42
|
npx antigravity-rtl-patcher patch
|
|
45
43
|
```
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
That's it. Restart Antigravity IDE and enjoy native RTL support. āØ
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## š Commands
|
|
50
|
+
|
|
51
|
+
| Command | Description |
|
|
52
|
+
|---------|-------------|
|
|
53
|
+
| `agy-rtl patch` | Inject RTL support into Antigravity IDE |
|
|
54
|
+
| `agy-rtl restore` | Remove patch and restore original files |
|
|
55
|
+
| `agy-rtl status` | Check if IDE is currently patched |
|
|
56
|
+
| `agy-rtl patch --path /custom/path` | Patch a custom installation directory |
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## š After IDE Updates
|
|
61
|
+
|
|
62
|
+
When Antigravity IDE updates, the patch may be overwritten. Simply run the patch command again:
|
|
49
63
|
|
|
50
64
|
```bash
|
|
51
|
-
|
|
52
|
-
|
|
65
|
+
npx antigravity-rtl-patcher patch
|
|
66
|
+
```
|
|
53
67
|
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## šļø Architecture
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
antigravity-rtl-patcher/
|
|
74
|
+
āāā bin/
|
|
75
|
+
ā āāā cli.js # CLI entry point (commander-based)
|
|
76
|
+
āāā src/
|
|
77
|
+
ā āāā patcher.js # Core logic: backup, inject, repack
|
|
78
|
+
ā āāā paths.js # OS-aware installation path detection
|
|
79
|
+
ā āāā utils.js # Permission checks & helpers
|
|
80
|
+
āāā payload/
|
|
81
|
+
ā āāā rtl-engine.js # MutationObserver RTL detection engine
|
|
82
|
+
ā āāā styles.css # Vazirmatn font + RTL typography fixes
|
|
83
|
+
āāā package.json
|
|
84
|
+
āāā README.md
|
|
56
85
|
```
|
|
57
86
|
|
|
58
|
-
|
|
87
|
+
### Supported Installation Types
|
|
88
|
+
|
|
89
|
+
| Type | Description | Method |
|
|
90
|
+
|------|-------------|--------|
|
|
91
|
+
| **Unpacked** | IDE with `resources/app/` directory | Direct file modification |
|
|
92
|
+
| **ASAR** | App bundled as `app.asar` | Extract ā Patch ā Repack |
|
|
59
93
|
|
|
60
94
|
---
|
|
61
95
|
|
|
62
|
-
##
|
|
96
|
+
## š Permissions
|
|
97
|
+
|
|
98
|
+
- **User directory installs** (e.g., `~/Downloads/`): No special permissions needed
|
|
99
|
+
- **System directory installs** (e.g., `/opt/`): Requires `sudo` on Linux/macOS
|
|
63
100
|
|
|
64
|
-
|
|
65
|
-
1. **The Patcher (`src/patcher.js`)**: A Node.js CLI that locates the `app.asar` file, extracts it to a temporary directory, injects our payload into the main `index.html`, and repacks it.
|
|
66
|
-
2. **The Payload (`payload/`)**: A combination of smart CSS and Vanilla JavaScript that runs inside the Electron UI. It uses a `MutationObserver` to watch for new chat messages dynamically generated by the AI and styles them on the fly.
|
|
101
|
+
---
|
|
67
102
|
|
|
68
103
|
## š¤ Contributing
|
|
104
|
+
|
|
69
105
|
Contributions, issues, and feature requests are welcome!
|
|
70
|
-
|
|
106
|
+
|
|
107
|
+
1. Fork the repo
|
|
108
|
+
2. Create your feature branch (`git checkout -b feature/amazing`)
|
|
109
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
110
|
+
4. Push to the branch (`git push origin feature/amazing`)
|
|
111
|
+
5. Open a Pull Request
|
|
71
112
|
|
|
72
113
|
## š License
|
|
73
|
-
This project is [MIT](https://choosealicense.com/licenses/mit/) licensed.
|
|
74
114
|
|
|
75
|
-
|
|
76
|
-
<div align="center">
|
|
77
|
-
</div>
|
|
115
|
+
[MIT](https://choosealicense.com/licenses/mit/)
|
package/bin/cli.js
CHANGED
|
@@ -2,20 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
const { program } = require('commander');
|
|
4
4
|
const chalk = require('chalk');
|
|
5
|
-
const { patch, restore } = require('../src/patcher');
|
|
5
|
+
const { patch, restore, status } = require('../src/patcher');
|
|
6
|
+
|
|
7
|
+
const BANNER = `
|
|
8
|
+
${chalk.cyan('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā')}
|
|
9
|
+
${chalk.cyan('ā')} ${chalk.bold.white('š Antigravity RTL Patcher')} ${chalk.gray('v2.0.0')} ${chalk.cyan('ā')}
|
|
10
|
+
${chalk.cyan('ā')} ${chalk.gray('Intelligent RTL support for Antigravity IDE')} ${chalk.cyan('ā')}
|
|
11
|
+
${chalk.cyan('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā')}
|
|
12
|
+
`;
|
|
6
13
|
|
|
7
14
|
program
|
|
8
15
|
.name('agy-rtl')
|
|
9
16
|
.description('RTL & Typography patcher for Antigravity IDE')
|
|
10
|
-
.version('
|
|
17
|
+
.version('2.0.0');
|
|
11
18
|
|
|
12
19
|
program
|
|
13
20
|
.command('patch')
|
|
14
21
|
.description('Inject RTL support into Antigravity IDE')
|
|
15
|
-
.
|
|
22
|
+
.option('-p, --path <path>', 'Custom path to Antigravity IDE installation')
|
|
23
|
+
.action(async (options) => {
|
|
24
|
+
console.log(BANNER);
|
|
16
25
|
try {
|
|
17
|
-
|
|
18
|
-
await patch();
|
|
26
|
+
await patch(options.path);
|
|
19
27
|
} catch (err) {
|
|
20
28
|
console.error(chalk.red('\nā Patch failed:'), err.message);
|
|
21
29
|
process.exit(1);
|
|
@@ -25,14 +33,29 @@ program
|
|
|
25
33
|
program
|
|
26
34
|
.command('restore')
|
|
27
35
|
.description('Restore Antigravity IDE to its original state')
|
|
28
|
-
.
|
|
36
|
+
.option('-p, --path <path>', 'Custom path to Antigravity IDE installation')
|
|
37
|
+
.action(async (options) => {
|
|
38
|
+
console.log(BANNER);
|
|
29
39
|
try {
|
|
30
|
-
|
|
31
|
-
await restore();
|
|
40
|
+
await restore(options.path);
|
|
32
41
|
} catch (err) {
|
|
33
42
|
console.error(chalk.red('\nā Restore failed:'), err.message);
|
|
34
43
|
process.exit(1);
|
|
35
44
|
}
|
|
36
45
|
});
|
|
37
46
|
|
|
47
|
+
program
|
|
48
|
+
.command('status')
|
|
49
|
+
.description('Check if Antigravity IDE is currently patched')
|
|
50
|
+
.option('-p, --path <path>', 'Custom path to Antigravity IDE installation')
|
|
51
|
+
.action(async (options) => {
|
|
52
|
+
console.log(BANNER);
|
|
53
|
+
try {
|
|
54
|
+
await status(options.path);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error(chalk.red('\nā Status check failed:'), err.message);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
38
61
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,32 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "antigravity-rtl-patcher",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "š Intelligent RTL & Persian/Arabic typography patcher for Antigravity IDE",
|
|
5
5
|
"main": "src/patcher.js",
|
|
6
6
|
"bin": {
|
|
7
|
+
"antigravity-rtl-patcher": "./bin/cli.js",
|
|
7
8
|
"agy-rtl": "./bin/cli.js"
|
|
8
9
|
},
|
|
9
10
|
"scripts": {
|
|
10
|
-
"start": "node ./bin/cli.js"
|
|
11
|
+
"start": "node ./bin/cli.js",
|
|
12
|
+
"patch": "node ./bin/cli.js patch",
|
|
13
|
+
"restore": "node ./bin/cli.js restore"
|
|
11
14
|
},
|
|
12
15
|
"keywords": [
|
|
13
16
|
"antigravity",
|
|
14
17
|
"rtl",
|
|
15
18
|
"farsi",
|
|
19
|
+
"persian",
|
|
16
20
|
"arabic",
|
|
17
21
|
"hebrew",
|
|
18
22
|
"patcher",
|
|
19
23
|
"electron",
|
|
20
|
-
"asar"
|
|
24
|
+
"asar",
|
|
25
|
+
"ide",
|
|
26
|
+
"vscode",
|
|
27
|
+
"right-to-left",
|
|
28
|
+
"bidirectional"
|
|
21
29
|
],
|
|
22
30
|
"author": "VaFa1726",
|
|
23
31
|
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/VaFa1726/antigravity-rtl-patcher.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/VaFa1726/antigravity-rtl-patcher/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/VaFa1726/antigravity-rtl-patcher#readme",
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=16.0.0"
|
|
42
|
+
},
|
|
24
43
|
"dependencies": {
|
|
25
44
|
"@electron/asar": "^3.2.14",
|
|
26
45
|
"chalk": "^4.1.2",
|
|
27
46
|
"commander": "^11.1.0",
|
|
28
47
|
"fs-extra": "^11.2.0",
|
|
29
|
-
"ora": "^5.4.1"
|
|
30
|
-
"sudo-prompt": "^9.2.1"
|
|
48
|
+
"ora": "^5.4.1"
|
|
31
49
|
}
|
|
32
50
|
}
|
package/payload/rtl-engine.js
CHANGED
|
@@ -1,73 +1,156 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* AGY RTL Engine v2.0.0
|
|
3
|
+
* Intelligent RTL detection & styling for Antigravity IDE
|
|
4
|
+
*
|
|
5
|
+
* This script runs inside the Electron renderer process.
|
|
6
|
+
* It uses MutationObserver to watch for DOM changes and
|
|
7
|
+
* automatically applies RTL direction to Persian/Arabic/Hebrew text.
|
|
8
|
+
*
|
|
9
|
+
* CSP Compatible: No use of innerHTML, eval, or inline event handlers.
|
|
10
|
+
*/
|
|
11
|
+
(function () {
|
|
12
|
+
'use strict';
|
|
3
13
|
|
|
4
|
-
//
|
|
5
|
-
const
|
|
14
|
+
// Unicode ranges for RTL scripts
|
|
15
|
+
const RTL_REGEX = /[\u0591-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]/;
|
|
6
16
|
|
|
17
|
+
// Minimum ratio of RTL characters to consider text as RTL
|
|
18
|
+
const RTL_CHAR_REGEX = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/g;
|
|
19
|
+
const MIN_RTL_RATIO = 0.3;
|
|
20
|
+
|
|
21
|
+
// Attribute to mark processed elements
|
|
22
|
+
const PROCESSED_ATTR = 'data-agy-rtl';
|
|
23
|
+
|
|
24
|
+
// Selectors to target (chat messages, markdown output, editor tooltips)
|
|
25
|
+
const TARGET_SELECTORS = [
|
|
26
|
+
// Chat and AI response areas
|
|
27
|
+
'.chat-response-content p',
|
|
28
|
+
'.chat-response-content li',
|
|
29
|
+
'.chat-response-content h1',
|
|
30
|
+
'.chat-response-content h2',
|
|
31
|
+
'.chat-response-content h3',
|
|
32
|
+
'.chat-response-content h4',
|
|
33
|
+
'.chat-response-content blockquote',
|
|
34
|
+
// Markdown preview
|
|
35
|
+
'.markdown-body p',
|
|
36
|
+
'.markdown-body li',
|
|
37
|
+
'.markdown-body h1',
|
|
38
|
+
'.markdown-body h2',
|
|
39
|
+
'.markdown-body h3',
|
|
40
|
+
'.markdown-body blockquote',
|
|
41
|
+
// Generic rendered text
|
|
42
|
+
'.rendered-markdown p',
|
|
43
|
+
'.rendered-markdown li',
|
|
44
|
+
'.rendered-markdown h1',
|
|
45
|
+
'.rendered-markdown h2',
|
|
46
|
+
'.rendered-markdown h3',
|
|
47
|
+
'.rendered-markdown blockquote',
|
|
48
|
+
// Monaco editor hover & tooltips
|
|
49
|
+
'.monaco-hover-content p',
|
|
50
|
+
'.suggest-details p',
|
|
51
|
+
// Notifications
|
|
52
|
+
'.notification-toast-container p',
|
|
53
|
+
// Generic paragraphs and spans in webview
|
|
54
|
+
'p', 'li', 'h1', 'h2', 'h3', 'h4', 'blockquote',
|
|
55
|
+
].join(', ');
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Determine if a text string is primarily RTL.
|
|
59
|
+
*/
|
|
7
60
|
function isRTL(text) {
|
|
8
|
-
|
|
9
|
-
|
|
61
|
+
if (!text || text.trim().length === 0) return false;
|
|
62
|
+
|
|
63
|
+
// Quick check: does it contain any RTL character at all?
|
|
64
|
+
if (!RTL_REGEX.test(text)) return false;
|
|
65
|
+
|
|
66
|
+
// Count RTL characters vs total alphabetic characters
|
|
67
|
+
const rtlMatches = text.match(RTL_CHAR_REGEX);
|
|
68
|
+
if (!rtlMatches) return false;
|
|
69
|
+
|
|
70
|
+
const alphaChars = text.replace(/[\s\d\W]/g, '').length;
|
|
71
|
+
if (alphaChars === 0) return false;
|
|
72
|
+
|
|
73
|
+
return (rtlMatches.length / alphaChars) >= MIN_RTL_RATIO;
|
|
10
74
|
}
|
|
11
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Apply RTL styling to an element.
|
|
78
|
+
*/
|
|
12
79
|
function applyRTL(element) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (isRTL(
|
|
17
|
-
element.setAttribute(
|
|
80
|
+
if (element.getAttribute(PROCESSED_ATTR)) return;
|
|
81
|
+
|
|
82
|
+
const text = element.textContent || '';
|
|
83
|
+
if (isRTL(text)) {
|
|
84
|
+
element.setAttribute(PROCESSED_ATTR, 'true');
|
|
18
85
|
element.style.direction = 'rtl';
|
|
19
86
|
element.style.textAlign = 'start';
|
|
20
|
-
|
|
21
|
-
// Crucial for mixed English/Farsi content (like code snippets in Farsi text)
|
|
22
|
-
element.style.unicodeBidi = 'plaintext';
|
|
23
|
-
|
|
24
|
-
// Mark as processed
|
|
25
|
-
element.classList.add('agy-rtl-processed');
|
|
87
|
+
element.style.unicodeBidi = 'plaintext';
|
|
26
88
|
}
|
|
27
89
|
}
|
|
28
90
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Scan a subtree for elements that need RTL.
|
|
93
|
+
*/
|
|
94
|
+
function scanElement(root) {
|
|
95
|
+
if (!root || root.nodeType !== Node.ELEMENT_NODE) return;
|
|
96
|
+
|
|
97
|
+
// Check if the root itself matches
|
|
98
|
+
try {
|
|
99
|
+
if (root.matches && root.matches(TARGET_SELECTORS)) {
|
|
100
|
+
applyRTL(root);
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
// Ignore selector errors
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Scan children
|
|
107
|
+
try {
|
|
108
|
+
const elements = root.querySelectorAll(TARGET_SELECTORS);
|
|
109
|
+
elements.forEach(function (el) {
|
|
110
|
+
applyRTL(el);
|
|
111
|
+
});
|
|
112
|
+
} catch (e) {
|
|
113
|
+
// Ignore errors
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Initialize the MutationObserver.
|
|
119
|
+
*/
|
|
120
|
+
function initObserver() {
|
|
121
|
+
var observer = new MutationObserver(function (mutations) {
|
|
122
|
+
mutations.forEach(function (mutation) {
|
|
123
|
+
// Handle added nodes
|
|
124
|
+
mutation.addedNodes.forEach(function (node) {
|
|
34
125
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
35
|
-
|
|
36
|
-
if (node.textContent && node.textContent.trim().length > 0) {
|
|
37
|
-
// We should probably target specific chat bubble selectors here if we knew them.
|
|
38
|
-
// For a generic approach, we look for paragraphs or divs with raw text.
|
|
39
|
-
const elementsToScan = node.querySelectorAll('p, span, div.message-content, div.chat-bubble, li, h1, h2, h3');
|
|
40
|
-
elementsToScan.forEach(el => {
|
|
41
|
-
if (!el.classList.contains('agy-rtl-processed')) {
|
|
42
|
-
applyRTL(el);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// Also check the root node added
|
|
47
|
-
if (!node.classList.contains('agy-rtl-processed')) {
|
|
48
|
-
applyRTL(node);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
126
|
+
scanElement(node);
|
|
51
127
|
}
|
|
52
128
|
});
|
|
53
|
-
|
|
129
|
+
|
|
130
|
+
// Handle text content changes
|
|
131
|
+
if (mutation.type === 'characterData' && mutation.target.parentElement) {
|
|
132
|
+
applyRTL(mutation.target.parentElement);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
54
135
|
});
|
|
55
|
-
});
|
|
56
136
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
observer.observe(document.body, {
|
|
60
|
-
childList: true,
|
|
137
|
+
observer.observe(document.body, {
|
|
138
|
+
childList: true,
|
|
61
139
|
subtree: true,
|
|
62
|
-
characterData: true
|
|
140
|
+
characterData: true,
|
|
63
141
|
});
|
|
64
|
-
|
|
65
|
-
// Initial scan
|
|
66
|
-
document.querySelectorAll('p, span, div, li, h1, h2, h3').forEach(el => {
|
|
67
|
-
if (el.textContent && el.textContent.trim().length > 0 && !el.children.length) {
|
|
68
|
-
applyRTL(el);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
142
|
|
|
143
|
+
// Initial full-page scan
|
|
144
|
+
scanElement(document.body);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Start when DOM is ready
|
|
148
|
+
if (document.readyState === 'loading') {
|
|
149
|
+
document.addEventListener('DOMContentLoaded', initObserver);
|
|
150
|
+
} else {
|
|
151
|
+
// DOM already loaded, wait a moment for dynamic content
|
|
152
|
+
setTimeout(initObserver, 500);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log('%cš AGY RTL Engine v2.0.0 loaded', 'color: cyan; font-weight: bold;');
|
|
73
156
|
})();
|
package/payload/styles.css
CHANGED
|
@@ -1,20 +1,37 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/*
|
|
2
|
+
* AGY RTL Styles v2.0.0
|
|
3
|
+
* Typography & RTL fixes for Antigravity IDE
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* Vazirmatn ā Modern Persian/Arabic font from Google Fonts */
|
|
2
7
|
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap');
|
|
3
8
|
|
|
4
|
-
/*
|
|
9
|
+
/* RTL text styling */
|
|
5
10
|
[data-agy-rtl="true"] {
|
|
6
|
-
font-family: 'Vazirmatn', system-ui, -apple-system, sans-serif !important;
|
|
7
|
-
line-height: 1.
|
|
8
|
-
|
|
11
|
+
font-family: 'Vazirmatn', system-ui, -apple-system, 'Segoe UI', sans-serif !important;
|
|
12
|
+
line-height: 1.85 !important;
|
|
13
|
+
word-spacing: 0.02em;
|
|
9
14
|
}
|
|
10
15
|
|
|
11
|
-
/*
|
|
12
|
-
[data-agy-rtl="true"] code,
|
|
13
|
-
[data-agy-rtl="true"] pre
|
|
16
|
+
/* Preserve LTR for code blocks inside RTL text */
|
|
17
|
+
[data-agy-rtl="true"] code,
|
|
18
|
+
[data-agy-rtl="true"] pre,
|
|
19
|
+
[data-agy-rtl="true"] .monaco-tokenized-source {
|
|
14
20
|
direction: ltr !important;
|
|
15
21
|
text-align: left !important;
|
|
16
22
|
unicode-bidi: isolate !important;
|
|
17
|
-
font-family: monospace !important;
|
|
23
|
+
font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', 'Consolas', monospace !important;
|
|
18
24
|
display: inline-block;
|
|
19
|
-
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Inline code in RTL paragraphs */
|
|
28
|
+
[data-agy-rtl="true"] code {
|
|
29
|
+
margin: 0 3px;
|
|
30
|
+
padding: 1px 5px;
|
|
31
|
+
border-radius: 3px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Fix list markers in RTL */
|
|
35
|
+
[data-agy-rtl="true"] li {
|
|
36
|
+
list-style-position: inside;
|
|
20
37
|
}
|
package/src/patcher.js
CHANGED
|
@@ -4,57 +4,103 @@ const path = require('path');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const ora = require('ora');
|
|
6
6
|
const chalk = require('chalk');
|
|
7
|
-
const {
|
|
7
|
+
const { findInstallations } = require('./paths');
|
|
8
8
|
const { checkPermissions, delay } = require('./utils');
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
const PATCH_MARKER = 'agy-rtl-engine';
|
|
11
|
+
const BACKUP_SUFFIX = '.agy-rtl-backup';
|
|
12
|
+
const ENGINE_FILENAME = 'agy-rtl-engine.js';
|
|
13
|
+
const STYLES_FILENAME = 'agy-rtl-styles.css';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Patch an unpacked Antigravity IDE installation.
|
|
17
|
+
* Copies payload files and modifies workbench.html.
|
|
18
|
+
*/
|
|
19
|
+
async function patchUnpacked(installation, spinner) {
|
|
20
|
+
const { workbenchDir, workbenchHtml } = installation;
|
|
21
|
+
|
|
22
|
+
// 1. Backup
|
|
23
|
+
const backupPath = workbenchHtml + BACKUP_SUFFIX;
|
|
24
|
+
if (!fs.existsSync(backupPath)) {
|
|
25
|
+
await fs.copy(workbenchHtml, backupPath);
|
|
26
|
+
spinner.succeed('Backup created: ' + chalk.gray(path.basename(backupPath)));
|
|
27
|
+
} else {
|
|
28
|
+
spinner.info('Backup already exists, skipping.');
|
|
22
29
|
}
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
// 2. Check if already patched
|
|
32
|
+
spinner.start('Checking patch status...');
|
|
33
|
+
const htmlContent = await fs.readFile(workbenchHtml, 'utf-8');
|
|
34
|
+
if (htmlContent.includes(PATCH_MARKER)) {
|
|
35
|
+
spinner.info('IDE is already patched. Use "restore" first to re-patch.');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 3. Copy payload files to workbench directory
|
|
40
|
+
spinner.start('Copying RTL engine & styles...');
|
|
41
|
+
const payloadDir = path.join(__dirname, '..', 'payload');
|
|
42
|
+
|
|
43
|
+
await fs.copy(
|
|
44
|
+
path.join(payloadDir, 'rtl-engine.js'),
|
|
45
|
+
path.join(workbenchDir, ENGINE_FILENAME)
|
|
46
|
+
);
|
|
47
|
+
await fs.copy(
|
|
48
|
+
path.join(payloadDir, 'styles.css'),
|
|
49
|
+
path.join(workbenchDir, STYLES_FILENAME)
|
|
50
|
+
);
|
|
51
|
+
spinner.succeed('Payload files copied.');
|
|
52
|
+
|
|
53
|
+
// 4. Inject references into workbench.html
|
|
54
|
+
spinner.start('Injecting RTL support into workbench...');
|
|
55
|
+
let modified = htmlContent;
|
|
56
|
+
|
|
57
|
+
// Add stylesheet link in <head> before </head>
|
|
58
|
+
const styleTag = `\n\t<!-- AGY-RTL-PATCH -->\n\t<link rel="stylesheet" href="./${STYLES_FILENAME}">\n\t<!-- /AGY-RTL-PATCH -->`;
|
|
59
|
+
modified = modified.replace('</head>', `${styleTag}\n</head>`);
|
|
60
|
+
|
|
61
|
+
// Add script before </html>
|
|
62
|
+
const scriptTag = `\n<!-- AGY-RTL-PATCH -->\n<script src="./${ENGINE_FILENAME}" id="${PATCH_MARKER}"></script>\n<!-- /AGY-RTL-PATCH -->`;
|
|
63
|
+
modified = modified.replace('</html>', `${scriptTag}\n</html>`);
|
|
64
|
+
|
|
65
|
+
await fs.writeFile(workbenchHtml, modified, 'utf-8');
|
|
66
|
+
spinner.succeed('RTL support injected into workbench.');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Patch an ASAR-packed Antigravity installation.
|
|
71
|
+
* Extracts the asar, patches files, and repacks.
|
|
72
|
+
*/
|
|
73
|
+
async function patchAsar(installation, spinner) {
|
|
74
|
+
const { asarPath } = installation;
|
|
75
|
+
const backupPath = asarPath + BACKUP_SUFFIX;
|
|
76
|
+
const tmpDir = path.join(os.tmpdir(), 'agy-rtl-patch-' + Date.now());
|
|
77
|
+
|
|
27
78
|
try {
|
|
28
79
|
// 1. Backup
|
|
29
|
-
spinner.start('Creating backup...');
|
|
30
80
|
if (!fs.existsSync(backupPath)) {
|
|
81
|
+
spinner.start('Creating backup...');
|
|
31
82
|
await fs.copy(asarPath, backupPath);
|
|
32
|
-
spinner.succeed('Backup created
|
|
83
|
+
spinner.succeed('Backup created.');
|
|
33
84
|
} else {
|
|
34
85
|
spinner.info('Backup already exists, skipping.');
|
|
35
86
|
}
|
|
36
87
|
|
|
37
88
|
// 2. Extract
|
|
38
|
-
spinner.start('Extracting
|
|
39
|
-
if (fs.existsSync(tmpDir)) {
|
|
40
|
-
await fs.remove(tmpDir);
|
|
41
|
-
}
|
|
89
|
+
spinner.start('Extracting app.asar...');
|
|
42
90
|
await fs.ensureDir(tmpDir);
|
|
43
91
|
asar.extractAll(asarPath, tmpDir);
|
|
44
|
-
spinner.succeed('
|
|
92
|
+
spinner.succeed('Extracted successfully.');
|
|
45
93
|
|
|
46
|
-
// 3.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// Find the main HTML file - usually index.html in the root or a dist folder
|
|
50
|
-
const possibleHtmlPaths = [
|
|
94
|
+
// 3. Find workbench.html inside extracted asar
|
|
95
|
+
const possiblePaths = [
|
|
96
|
+
path.join(tmpDir, 'out', 'vs', 'code', 'electron-browser', 'workbench', 'workbench.html'),
|
|
51
97
|
path.join(tmpDir, 'index.html'),
|
|
98
|
+
path.join(tmpDir, 'dist', 'index.html'),
|
|
52
99
|
path.join(tmpDir, 'out', 'index.html'),
|
|
53
|
-
path.join(tmpDir, 'dist', 'index.html')
|
|
54
100
|
];
|
|
55
|
-
|
|
101
|
+
|
|
56
102
|
let targetHtml = null;
|
|
57
|
-
for (const p of
|
|
103
|
+
for (const p of possiblePaths) {
|
|
58
104
|
if (fs.existsSync(p)) {
|
|
59
105
|
targetHtml = p;
|
|
60
106
|
break;
|
|
@@ -62,90 +108,197 @@ async function patch() {
|
|
|
62
108
|
}
|
|
63
109
|
|
|
64
110
|
if (!targetHtml) {
|
|
65
|
-
|
|
111
|
+
spinner.warn('No patchable HTML found in this package. Skipping.');
|
|
112
|
+
await fs.remove(tmpDir);
|
|
113
|
+
return;
|
|
66
114
|
}
|
|
67
115
|
|
|
68
|
-
let htmlContent = await fs.readFile(targetHtml, 'utf-8');
|
|
69
|
-
|
|
70
|
-
// Read payload files
|
|
71
|
-
const engineCode = await fs.readFile(path.join(__dirname, '..', 'payload', 'rtl-engine.js'), 'utf-8');
|
|
72
|
-
const stylesCode = await fs.readFile(path.join(__dirname, '..', 'payload', 'styles.css'), 'utf-8');
|
|
73
|
-
|
|
74
116
|
// Check if already patched
|
|
75
|
-
|
|
76
|
-
|
|
117
|
+
const htmlContent = await fs.readFile(targetHtml, 'utf-8');
|
|
118
|
+
if (htmlContent.includes(PATCH_MARKER)) {
|
|
119
|
+
spinner.info('ASAR is already patched.');
|
|
77
120
|
await fs.remove(tmpDir);
|
|
78
121
|
return;
|
|
79
122
|
}
|
|
80
123
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
124
|
+
// 4. Copy payload files
|
|
125
|
+
spinner.start('Injecting RTL engine...');
|
|
126
|
+
const payloadDir = path.join(__dirname, '..', 'payload');
|
|
127
|
+
const targetDir = path.dirname(targetHtml);
|
|
128
|
+
|
|
129
|
+
await fs.copy(path.join(payloadDir, 'rtl-engine.js'), path.join(targetDir, ENGINE_FILENAME));
|
|
130
|
+
await fs.copy(path.join(payloadDir, 'styles.css'), path.join(targetDir, STYLES_FILENAME));
|
|
131
|
+
|
|
132
|
+
// 5. Modify HTML
|
|
133
|
+
let modified = htmlContent;
|
|
134
|
+
const styleTag = `\n\t<!-- AGY-RTL-PATCH -->\n\t<link rel="stylesheet" href="./${STYLES_FILENAME}">\n\t<!-- /AGY-RTL-PATCH -->`;
|
|
135
|
+
const scriptTag = `\n<!-- AGY-RTL-PATCH -->\n<script src="./${ENGINE_FILENAME}" id="${PATCH_MARKER}"></script>\n<!-- /AGY-RTL-PATCH -->`;
|
|
87
136
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
137
|
+
if (modified.includes('</head>')) {
|
|
138
|
+
modified = modified.replace('</head>', `${styleTag}\n</head>`);
|
|
139
|
+
}
|
|
140
|
+
if (modified.includes('</html>')) {
|
|
141
|
+
modified = modified.replace('</html>', `${scriptTag}\n</html>`);
|
|
142
|
+
} else if (modified.includes('</body>')) {
|
|
143
|
+
modified = modified.replace('</body>', `${scriptTag}\n</body>`);
|
|
91
144
|
} else {
|
|
92
|
-
|
|
93
|
-
htmlContent += injection;
|
|
145
|
+
modified += scriptTag;
|
|
94
146
|
}
|
|
95
147
|
|
|
96
|
-
await fs.writeFile(targetHtml,
|
|
97
|
-
spinner.succeed('
|
|
148
|
+
await fs.writeFile(targetHtml, modified, 'utf-8');
|
|
149
|
+
spinner.succeed('RTL engine injected.');
|
|
98
150
|
|
|
99
|
-
//
|
|
100
|
-
spinner.start('Repacking
|
|
101
|
-
await delay(
|
|
151
|
+
// 6. Repack
|
|
152
|
+
spinner.start('Repacking app.asar...');
|
|
153
|
+
await delay(300);
|
|
102
154
|
await asar.createPackage(tmpDir, asarPath);
|
|
103
|
-
spinner.succeed('
|
|
104
|
-
|
|
105
|
-
// 5. Cleanup
|
|
106
|
-
spinner.start('Cleaning up...');
|
|
107
|
-
await fs.remove(tmpDir);
|
|
108
|
-
spinner.succeed('Cleanup complete.');
|
|
155
|
+
spinner.succeed('Repacked successfully.');
|
|
109
156
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
} catch (err) {
|
|
114
|
-
spinner.fail('An error occurred during patching.');
|
|
157
|
+
} finally {
|
|
158
|
+
// Cleanup temp dir
|
|
115
159
|
if (fs.existsSync(tmpDir)) {
|
|
116
160
|
await fs.remove(tmpDir);
|
|
117
161
|
}
|
|
118
|
-
throw err;
|
|
119
162
|
}
|
|
120
163
|
}
|
|
121
164
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
spinner.fail(
|
|
132
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Main patch function.
|
|
167
|
+
*/
|
|
168
|
+
async function patch(customPath) {
|
|
169
|
+
const spinner = ora('Searching for Antigravity IDE...').start();
|
|
170
|
+
|
|
171
|
+
const installations = findInstallations(customPath);
|
|
172
|
+
|
|
173
|
+
if (installations.length === 0) {
|
|
174
|
+
spinner.fail('Antigravity IDE installation not found.');
|
|
175
|
+
console.error(chalk.yellow('\nTry specifying the path manually:'));
|
|
176
|
+
console.error(chalk.cyan(' agy-rtl patch --path /path/to/Antigravity-IDE\n'));
|
|
177
|
+
throw new Error('Installation not found.');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
spinner.succeed(`Found ${installations.length} installation(s).`);
|
|
181
|
+
|
|
182
|
+
for (const inst of installations) {
|
|
183
|
+
console.log(chalk.cyan(`\nš Patching: ${chalk.white(inst.basePath)}`));
|
|
184
|
+
console.log(chalk.gray(` Type: ${inst.type === 'unpacked' ? 'Unpacked App' : 'ASAR Package'}`));
|
|
185
|
+
|
|
186
|
+
checkPermissions(inst.basePath);
|
|
187
|
+
|
|
188
|
+
if (inst.type === 'unpacked') {
|
|
189
|
+
await patchUnpacked(inst, ora());
|
|
190
|
+
} else {
|
|
191
|
+
await patchAsar(inst, ora());
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log(chalk.green.bold('\n⨠Antigravity IDE successfully patched with RTL support!'));
|
|
196
|
+
console.log(chalk.cyan(' Please restart Antigravity IDE for changes to take effect.\n'));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Restore an unpacked installation from backup.
|
|
201
|
+
*/
|
|
202
|
+
async function restoreUnpacked(installation, spinner) {
|
|
203
|
+
const { workbenchDir, workbenchHtml } = installation;
|
|
204
|
+
const backupPath = workbenchHtml + BACKUP_SUFFIX;
|
|
205
|
+
|
|
206
|
+
if (fs.existsSync(backupPath)) {
|
|
207
|
+
spinner.start('Restoring workbench.html from backup...');
|
|
208
|
+
await fs.copy(backupPath, workbenchHtml);
|
|
209
|
+
await fs.remove(backupPath);
|
|
210
|
+
spinner.succeed('Workbench restored.');
|
|
211
|
+
} else {
|
|
212
|
+
spinner.warn('No backup found for workbench.html.');
|
|
133
213
|
}
|
|
134
214
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
215
|
+
// Remove injected payload files
|
|
216
|
+
const engineFile = path.join(workbenchDir, ENGINE_FILENAME);
|
|
217
|
+
const stylesFile = path.join(workbenchDir, STYLES_FILENAME);
|
|
218
|
+
|
|
219
|
+
if (fs.existsSync(engineFile)) await fs.remove(engineFile);
|
|
220
|
+
if (fs.existsSync(stylesFile)) await fs.remove(stylesFile);
|
|
221
|
+
spinner.succeed('Payload files removed.');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Restore an ASAR installation from backup.
|
|
226
|
+
*/
|
|
227
|
+
async function restoreAsar(installation, spinner) {
|
|
228
|
+
const { asarPath } = installation;
|
|
229
|
+
const backupPath = asarPath + BACKUP_SUFFIX;
|
|
230
|
+
|
|
138
231
|
if (fs.existsSync(backupPath)) {
|
|
232
|
+
spinner.start('Restoring app.asar from backup...');
|
|
139
233
|
await fs.copy(backupPath, asarPath);
|
|
140
234
|
await fs.remove(backupPath);
|
|
141
|
-
spinner.succeed('
|
|
142
|
-
console.log(chalk.green.bold('\n⨠Antigravity IDE restored to original state!'));
|
|
235
|
+
spinner.succeed('ASAR restored.');
|
|
143
236
|
} else {
|
|
144
|
-
spinner.
|
|
237
|
+
spinner.warn('No backup found for app.asar.');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Main restore function.
|
|
243
|
+
*/
|
|
244
|
+
async function restore(customPath) {
|
|
245
|
+
const spinner = ora('Searching for Antigravity IDE...').start();
|
|
246
|
+
const installations = findInstallations(customPath);
|
|
247
|
+
|
|
248
|
+
if (installations.length === 0) {
|
|
249
|
+
spinner.fail('Antigravity IDE installation not found.');
|
|
250
|
+
throw new Error('Installation not found.');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
spinner.succeed(`Found ${installations.length} installation(s).`);
|
|
254
|
+
|
|
255
|
+
for (const inst of installations) {
|
|
256
|
+
console.log(chalk.yellow(`\nš Restoring: ${chalk.white(inst.basePath)}`));
|
|
257
|
+
|
|
258
|
+
checkPermissions(inst.basePath);
|
|
259
|
+
|
|
260
|
+
if (inst.type === 'unpacked') {
|
|
261
|
+
await restoreUnpacked(inst, ora());
|
|
262
|
+
} else {
|
|
263
|
+
await restoreAsar(inst, ora());
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
console.log(chalk.green.bold('\n⨠Antigravity IDE restored to original state!'));
|
|
268
|
+
console.log(chalk.cyan(' Please restart Antigravity IDE for changes to take effect.\n'));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Check patch status.
|
|
273
|
+
*/
|
|
274
|
+
async function status(customPath) {
|
|
275
|
+
const spinner = ora('Searching for Antigravity IDE...').start();
|
|
276
|
+
const installations = findInstallations(customPath);
|
|
277
|
+
|
|
278
|
+
if (installations.length === 0) {
|
|
279
|
+
spinner.fail('Antigravity IDE installation not found.');
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
spinner.succeed(`Found ${installations.length} installation(s).`);
|
|
284
|
+
|
|
285
|
+
for (const inst of installations) {
|
|
286
|
+
let isPatched = false;
|
|
287
|
+
|
|
288
|
+
if (inst.type === 'unpacked') {
|
|
289
|
+
const html = await fs.readFile(inst.workbenchHtml, 'utf-8');
|
|
290
|
+
isPatched = html.includes(PATCH_MARKER);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const statusIcon = isPatched ? chalk.green('ā PATCHED') : chalk.red('ā NOT PATCHED');
|
|
294
|
+
console.log(`\n ${statusIcon} ${chalk.white(inst.basePath)}`);
|
|
295
|
+
console.log(chalk.gray(` Type: ${inst.type === 'unpacked' ? 'Unpacked App' : 'ASAR Package'}`));
|
|
145
296
|
}
|
|
297
|
+
console.log('');
|
|
146
298
|
}
|
|
147
299
|
|
|
148
300
|
module.exports = {
|
|
149
301
|
patch,
|
|
150
|
-
restore
|
|
302
|
+
restore,
|
|
303
|
+
status,
|
|
151
304
|
};
|
package/src/paths.js
CHANGED
|
@@ -2,36 +2,116 @@ const fs = require('fs-extra');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Known installation paths for Antigravity IDE across all platforms.
|
|
7
|
+
* Both the IDE (unpacked) and the standalone app (asar) are supported.
|
|
8
|
+
*/
|
|
9
|
+
function getSearchPaths() {
|
|
10
|
+
const home = os.homedir();
|
|
6
11
|
const platform = os.platform();
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
const common = [
|
|
14
|
+
// Downloads (common for manual installs)
|
|
15
|
+
path.join(home, 'Downloads', 'Antigravity-IDE'),
|
|
16
|
+
path.join(home, 'Downloads', 'Antigravity-x64'),
|
|
17
|
+
path.join(home, 'Downloads', 'Antigravity'),
|
|
18
|
+
// Desktop
|
|
19
|
+
path.join(home, 'Desktop', 'Antigravity-IDE'),
|
|
20
|
+
path.join(home, 'Desktop', 'Antigravity-x64'),
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const platformPaths = {
|
|
24
|
+
linux: [
|
|
25
|
+
...common,
|
|
26
|
+
'/opt/antigravity-ide',
|
|
27
|
+
'/opt/Antigravity-IDE',
|
|
28
|
+
'/opt/antigravity',
|
|
29
|
+
'/usr/lib/antigravity-ide',
|
|
30
|
+
'/usr/share/antigravity-ide',
|
|
31
|
+
path.join(home, '.local', 'share', 'antigravity-ide'),
|
|
32
|
+
path.join(home, '.local', 'lib', 'antigravity-ide'),
|
|
13
33
|
],
|
|
14
34
|
darwin: [
|
|
15
|
-
'/Applications/Antigravity.app/Contents
|
|
16
|
-
path.join(
|
|
35
|
+
'/Applications/Antigravity IDE.app/Contents',
|
|
36
|
+
path.join(home, 'Applications', 'Antigravity IDE.app', 'Contents'),
|
|
37
|
+
'/Applications/Antigravity.app/Contents',
|
|
38
|
+
...common,
|
|
39
|
+
],
|
|
40
|
+
win32: [
|
|
41
|
+
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'antigravity-ide'),
|
|
42
|
+
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Antigravity IDE'),
|
|
43
|
+
path.join(process.env.PROGRAMFILES || '', 'Antigravity IDE'),
|
|
44
|
+
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'antigravity'),
|
|
45
|
+
...common,
|
|
17
46
|
],
|
|
18
|
-
linux: [
|
|
19
|
-
'/opt/antigravity/resources/app.asar',
|
|
20
|
-
'/usr/local/lib/antigravity/resources/app.asar'
|
|
21
|
-
]
|
|
22
47
|
};
|
|
23
48
|
|
|
24
|
-
|
|
49
|
+
return platformPaths[platform] || common;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Detect the type of Antigravity installation.
|
|
54
|
+
* Returns: { type: 'unpacked' | 'asar', basePath, workbenchDir, workbenchHtml }
|
|
55
|
+
*/
|
|
56
|
+
function detectInstallation(basePath) {
|
|
57
|
+
// Type 1: Unpacked app directory (IDE version)
|
|
58
|
+
const unpackedApp = path.join(basePath, 'resources', 'app');
|
|
59
|
+
const workbenchUnpacked = path.join(
|
|
60
|
+
unpackedApp,
|
|
61
|
+
'out', 'vs', 'code', 'electron-browser', 'workbench', 'workbench.html'
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (fs.existsSync(workbenchUnpacked)) {
|
|
65
|
+
return {
|
|
66
|
+
type: 'unpacked',
|
|
67
|
+
basePath,
|
|
68
|
+
appDir: unpackedApp,
|
|
69
|
+
workbenchDir: path.dirname(workbenchUnpacked),
|
|
70
|
+
workbenchHtml: workbenchUnpacked,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Type 2: ASAR packed app
|
|
75
|
+
const asarPath = path.join(basePath, 'resources', 'app.asar');
|
|
76
|
+
if (fs.existsSync(asarPath)) {
|
|
77
|
+
return {
|
|
78
|
+
type: 'asar',
|
|
79
|
+
basePath,
|
|
80
|
+
asarPath,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Find all Antigravity IDE installations on the system.
|
|
89
|
+
*/
|
|
90
|
+
function findInstallations(customPath) {
|
|
91
|
+
const results = [];
|
|
92
|
+
|
|
93
|
+
if (customPath) {
|
|
94
|
+
const info = detectInstallation(customPath);
|
|
95
|
+
if (info) {
|
|
96
|
+
results.push(info);
|
|
97
|
+
}
|
|
98
|
+
return results;
|
|
99
|
+
}
|
|
25
100
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
101
|
+
const searchPaths = getSearchPaths();
|
|
102
|
+
for (const sp of searchPaths) {
|
|
103
|
+
if (fs.existsSync(sp)) {
|
|
104
|
+
const info = detectInstallation(sp);
|
|
105
|
+
if (info) {
|
|
106
|
+
results.push(info);
|
|
107
|
+
}
|
|
29
108
|
}
|
|
30
109
|
}
|
|
31
110
|
|
|
32
|
-
|
|
111
|
+
return results;
|
|
33
112
|
}
|
|
34
113
|
|
|
35
114
|
module.exports = {
|
|
36
|
-
|
|
115
|
+
findInstallations,
|
|
116
|
+
detectInstallation,
|
|
37
117
|
};
|
package/src/utils.js
CHANGED
|
@@ -1,26 +1,35 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Check if the current process has sufficient permissions
|
|
5
|
+
* to modify system-level application files.
|
|
6
|
+
*/
|
|
7
|
+
function checkPermissions(targetPath) {
|
|
4
8
|
const isWindows = process.platform === 'win32';
|
|
5
|
-
|
|
9
|
+
|
|
6
10
|
if (!isWindows) {
|
|
7
|
-
// Check if running as root
|
|
8
11
|
const isRoot = process.getuid && process.getuid() === 0;
|
|
9
|
-
if
|
|
12
|
+
// Check if the target is in a user-writable directory
|
|
13
|
+
const isUserDir = targetPath && targetPath.startsWith(require('os').homedir());
|
|
14
|
+
|
|
15
|
+
if (!isRoot && !isUserDir) {
|
|
10
16
|
console.error(chalk.red.bold('\nā ļø Permission Denied!'));
|
|
11
17
|
console.error(chalk.white('Modifying system applications requires administrator privileges.'));
|
|
12
|
-
console.error(chalk.white(`Please run
|
|
13
|
-
console.error(chalk.cyan(' sudo npx
|
|
18
|
+
console.error(chalk.white(`Please run with ${chalk.yellow('sudo')}:\n`));
|
|
19
|
+
console.error(chalk.cyan(' sudo npx antigravity-rtl-patcher patch\n'));
|
|
14
20
|
process.exit(1);
|
|
15
21
|
}
|
|
16
22
|
}
|
|
17
23
|
}
|
|
18
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Simple async delay helper.
|
|
27
|
+
*/
|
|
19
28
|
function delay(ms) {
|
|
20
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
29
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
21
30
|
}
|
|
22
31
|
|
|
23
32
|
module.exports = {
|
|
24
33
|
checkPermissions,
|
|
25
|
-
delay
|
|
34
|
+
delay,
|
|
26
35
|
};
|