antigravity-rtl-patcher 1.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 ADDED
@@ -0,0 +1,77 @@
1
+ # 🌌 Antigravity RTL Patcher
2
+
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
+
9
+ <br>
10
+
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
+ </div>
15
+
16
+ ---
17
+
18
+ ## ⚔ What is this?
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.
20
+
21
+ This tool is a **CLI Patcher**. It deeply hooks into the core of Antigravity IDE (`app.asar`), injects a smart JavaScript `MutationObserver` engine, and automatically formats text direction dynamically.
22
+
23
+ ## šŸš€ Features
24
+ - **🧠 Smart Direction:** Detects RTL languages using Regex and applies alignment only where necessary.
25
+ - **šŸŽØ Beautiful Typography:** Automatically injects the modern **Vazirmatn** font for perfect legibility.
26
+ - **🧩 Mixed Text Support:** Uses `unicode-bidi: plaintext` to ensure English code blocks inside Persian text don't break.
27
+ - **šŸ›”ļø Safe & Reversible:** Automatically creates a backup. You can restore the IDE to its original state with a single command.
28
+ - **āš™ļø OS Agnostic:** Works on Windows, macOS, and Linux out of the box.
29
+
30
+ ---
31
+
32
+ ## šŸ“¦ Installation & Usage
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:
38
+
39
+ ```bash
40
+ # macOS & Linux (Requires sudo to modify application files)
41
+ sudo npx antigravity-rtl-patcher patch
42
+
43
+ # Windows (Run PowerShell as Administrator)
44
+ npx antigravity-rtl-patcher patch
45
+ ```
46
+
47
+ ### 2. Restore (Unpatch)
48
+ If you want to remove the patch or an update breaks something, you can easily restore from the backup:
49
+
50
+ ```bash
51
+ # macOS & Linux
52
+ sudo npx antigravity-rtl-patcher restore
53
+
54
+ # Windows
55
+ npx antigravity-rtl-patcher restore
56
+ ```
57
+
58
+ *(Note: Whenever Antigravity IDE updates, the patch will be overwritten. Just run the `patch` command again!)*
59
+
60
+ ---
61
+
62
+ ## šŸ› ļø Architecture
63
+
64
+ This project is divided into two main parts:
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.
67
+
68
+ ## šŸ¤ Contributing
69
+ Contributions, issues, and feature requests are welcome!
70
+ Feel free to check [issues page](https://github.com/VaFa1726/antigravity-rtl-patcher/issues).
71
+
72
+ ## šŸ“„ License
73
+ This project is [MIT](https://choosealicense.com/licenses/mit/) licensed.
74
+
75
+ ---
76
+ <div align="center">
77
+ </div>
package/bin/cli.js ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const chalk = require('chalk');
5
+ const { patch, restore } = require('../src/patcher');
6
+
7
+ program
8
+ .name('agy-rtl')
9
+ .description('RTL & Typography patcher for Antigravity IDE')
10
+ .version('1.0.0');
11
+
12
+ program
13
+ .command('patch')
14
+ .description('Inject RTL support into Antigravity IDE')
15
+ .action(async () => {
16
+ try {
17
+ console.log(chalk.cyan.bold('\nšŸš€ Starting Antigravity RTL Patcher...'));
18
+ await patch();
19
+ } catch (err) {
20
+ console.error(chalk.red('\nāŒ Patch failed:'), err.message);
21
+ process.exit(1);
22
+ }
23
+ });
24
+
25
+ program
26
+ .command('restore')
27
+ .description('Restore Antigravity IDE to its original state')
28
+ .action(async () => {
29
+ try {
30
+ console.log(chalk.yellow.bold('\nšŸ”„ Restoring Antigravity...'));
31
+ await restore();
32
+ } catch (err) {
33
+ console.error(chalk.red('\nāŒ Restore failed:'), err.message);
34
+ process.exit(1);
35
+ }
36
+ });
37
+
38
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "antigravity-rtl-patcher",
3
+ "version": "1.0.0",
4
+ "description": "An elegant RTL and typography patcher for Antigravity IDE based on Electron",
5
+ "main": "src/patcher.js",
6
+ "bin": {
7
+ "agy-rtl": "./bin/cli.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node ./bin/cli.js"
11
+ },
12
+ "keywords": [
13
+ "antigravity",
14
+ "rtl",
15
+ "farsi",
16
+ "arabic",
17
+ "hebrew",
18
+ "patcher",
19
+ "electron",
20
+ "asar"
21
+ ],
22
+ "author": "VaFa1726",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "@electron/asar": "^3.2.14",
26
+ "chalk": "^4.1.2",
27
+ "commander": "^11.1.0",
28
+ "fs-extra": "^11.2.0",
29
+ "ora": "^5.4.1",
30
+ "sudo-prompt": "^9.2.1"
31
+ }
32
+ }
@@ -0,0 +1,73 @@
1
+ (function() {
2
+ console.log('šŸš€ AGY RTL Engine Initialized');
3
+
4
+ // Regex to detect RTL characters (Arabic, Persian, Hebrew)
5
+ const rtlRegex = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/;
6
+
7
+ function isRTL(text) {
8
+ // Basic heuristic: check if any RTL char exists
9
+ return rtlRegex.test(text);
10
+ }
11
+
12
+ function applyRTL(element) {
13
+ // Only process text nodes or direct containers
14
+ const textContent = element.textContent || element.innerText || '';
15
+
16
+ if (isRTL(textContent)) {
17
+ element.setAttribute('data-agy-rtl', 'true');
18
+ element.style.direction = 'rtl';
19
+ 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');
26
+ }
27
+ }
28
+
29
+ // Observe DOM mutations for dynamically added chat messages
30
+ const observer = new MutationObserver((mutations) => {
31
+ mutations.forEach((mutation) => {
32
+ if (mutation.addedNodes.length) {
33
+ mutation.addedNodes.forEach((node) => {
34
+ if (node.nodeType === Node.ELEMENT_NODE) {
35
+ // Apply logic to the node itself if it has text
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
+ }
51
+ }
52
+ });
53
+ }
54
+ });
55
+ });
56
+
57
+ // Start observing
58
+ window.addEventListener('load', () => {
59
+ observer.observe(document.body, {
60
+ childList: true,
61
+ subtree: true,
62
+ characterData: true
63
+ });
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
+
73
+ })();
@@ -0,0 +1,20 @@
1
+ /* Import a modern Persian font from Google Fonts (Vazirmatn) */
2
+ @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap');
3
+
4
+ /* Apply font to patched elements */
5
+ [data-agy-rtl="true"] {
6
+ font-family: 'Vazirmatn', system-ui, -apple-system, sans-serif !important;
7
+ line-height: 1.8 !important;
8
+ letter-spacing: normal !important;
9
+ }
10
+
11
+ /* Fix code blocks inside RTL text */
12
+ [data-agy-rtl="true"] code,
13
+ [data-agy-rtl="true"] pre {
14
+ direction: ltr !important;
15
+ text-align: left !important;
16
+ unicode-bidi: isolate !important;
17
+ font-family: monospace !important;
18
+ display: inline-block;
19
+ margin: 0 4px;
20
+ }
package/src/patcher.js ADDED
@@ -0,0 +1,151 @@
1
+ const asar = require('@electron/asar');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const ora = require('ora');
6
+ const chalk = require('chalk');
7
+ const { getAsarPath } = require('./paths');
8
+ const { checkPermissions, delay } = require('./utils');
9
+
10
+ async function patch() {
11
+ checkPermissions();
12
+
13
+ const spinner = ora('Locating Antigravity IDE...').start();
14
+ let asarPath;
15
+
16
+ try {
17
+ asarPath = getAsarPath();
18
+ spinner.succeed(`Found Antigravity at: ${chalk.gray(asarPath)}`);
19
+ } catch (err) {
20
+ spinner.fail(err.message);
21
+ throw err;
22
+ }
23
+
24
+ const backupPath = `${asarPath}.backup`;
25
+ const tmpDir = path.join(os.tmpdir(), 'agy-patch-tmp');
26
+
27
+ try {
28
+ // 1. Backup
29
+ spinner.start('Creating backup...');
30
+ if (!fs.existsSync(backupPath)) {
31
+ await fs.copy(asarPath, backupPath);
32
+ spinner.succeed('Backup created successfully.');
33
+ } else {
34
+ spinner.info('Backup already exists, skipping.');
35
+ }
36
+
37
+ // 2. Extract
38
+ spinner.start('Extracting core files...');
39
+ if (fs.existsSync(tmpDir)) {
40
+ await fs.remove(tmpDir);
41
+ }
42
+ await fs.ensureDir(tmpDir);
43
+ asar.extractAll(asarPath, tmpDir);
44
+ spinner.succeed('Core files extracted.');
45
+
46
+ // 3. Inject Payload
47
+ spinner.start('Injecting RTL engine & typography...');
48
+
49
+ // Find the main HTML file - usually index.html in the root or a dist folder
50
+ const possibleHtmlPaths = [
51
+ path.join(tmpDir, 'index.html'),
52
+ path.join(tmpDir, 'out', 'index.html'),
53
+ path.join(tmpDir, 'dist', 'index.html')
54
+ ];
55
+
56
+ let targetHtml = null;
57
+ for (const p of possibleHtmlPaths) {
58
+ if (fs.existsSync(p)) {
59
+ targetHtml = p;
60
+ break;
61
+ }
62
+ }
63
+
64
+ if (!targetHtml) {
65
+ throw new Error('Could not find the main HTML file to inject.');
66
+ }
67
+
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
+ // Check if already patched
75
+ if (htmlContent.includes('agy-rtl-engine')) {
76
+ spinner.info('IDE is already patched.');
77
+ await fs.remove(tmpDir);
78
+ return;
79
+ }
80
+
81
+ const injection = `
82
+ <!-- AGY RTL PATCH -->
83
+ <style id="agy-rtl-styles">${stylesCode}</style>
84
+ <script id="agy-rtl-engine">${engineCode}</script>
85
+ <!-- END AGY RTL PATCH -->
86
+ `;
87
+
88
+ // Inject just before </body>
89
+ if (htmlContent.includes('</body>')) {
90
+ htmlContent = htmlContent.replace('</body>', `${injection}\n</body>`);
91
+ } else {
92
+ // Fallback
93
+ htmlContent += injection;
94
+ }
95
+
96
+ await fs.writeFile(targetHtml, htmlContent);
97
+ spinner.succeed('Payload injected into UI.');
98
+
99
+ // 4. Repack
100
+ spinner.start('Repacking core files...');
101
+ await delay(500); // Give file system a moment
102
+ await asar.createPackage(tmpDir, asarPath);
103
+ spinner.succeed('Core repacked successfully.');
104
+
105
+ // 5. Cleanup
106
+ spinner.start('Cleaning up...');
107
+ await fs.remove(tmpDir);
108
+ spinner.succeed('Cleanup complete.');
109
+
110
+ console.log(chalk.green.bold('\n✨ Antigravity IDE successfully patched with RTL support!'));
111
+ console.log(chalk.cyan('Please restart Antigravity for the changes to take effect.\n'));
112
+
113
+ } catch (err) {
114
+ spinner.fail('An error occurred during patching.');
115
+ if (fs.existsSync(tmpDir)) {
116
+ await fs.remove(tmpDir);
117
+ }
118
+ throw err;
119
+ }
120
+ }
121
+
122
+ async function restore() {
123
+ checkPermissions();
124
+ const spinner = ora('Locating Antigravity IDE...').start();
125
+
126
+ let asarPath;
127
+ try {
128
+ asarPath = getAsarPath();
129
+ spinner.succeed(`Found Antigravity at: ${chalk.gray(asarPath)}`);
130
+ } catch (err) {
131
+ spinner.fail(err.message);
132
+ throw err;
133
+ }
134
+
135
+ const backupPath = `${asarPath}.backup`;
136
+
137
+ spinner.start('Restoring from backup...');
138
+ if (fs.existsSync(backupPath)) {
139
+ await fs.copy(backupPath, asarPath);
140
+ await fs.remove(backupPath);
141
+ spinner.succeed('Restored successfully.');
142
+ console.log(chalk.green.bold('\n✨ Antigravity IDE restored to original state!'));
143
+ } else {
144
+ spinner.fail('No backup found. The IDE might not be patched or backup was removed.');
145
+ }
146
+ }
147
+
148
+ module.exports = {
149
+ patch,
150
+ restore
151
+ };
package/src/paths.js ADDED
@@ -0,0 +1,37 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ function getAsarPath() {
6
+ const platform = os.platform();
7
+
8
+ // Possible paths where the IDE might be installed
9
+ const paths = {
10
+ win32: [
11
+ path.join(process.env.LOCALAPPDATA || '', 'Programs', 'antigravity', 'resources', 'app.asar'),
12
+ path.join(process.env.PROGRAMFILES || '', 'antigravity', 'resources', 'app.asar')
13
+ ],
14
+ darwin: [
15
+ '/Applications/Antigravity.app/Contents/Resources/app.asar',
16
+ path.join(os.homedir(), 'Applications', 'Antigravity.app', 'Contents', 'Resources', 'app.asar')
17
+ ],
18
+ linux: [
19
+ '/opt/antigravity/resources/app.asar',
20
+ '/usr/local/lib/antigravity/resources/app.asar'
21
+ ]
22
+ };
23
+
24
+ const possiblePaths = paths[platform] || [];
25
+
26
+ for (const p of possiblePaths) {
27
+ if (fs.existsSync(p)) {
28
+ return p;
29
+ }
30
+ }
31
+
32
+ throw new Error('Antigravity installation not found. Please ensure it is installed in the default location.');
33
+ }
34
+
35
+ module.exports = {
36
+ getAsarPath
37
+ };
package/src/utils.js ADDED
@@ -0,0 +1,26 @@
1
+ const chalk = require('chalk');
2
+
3
+ function checkPermissions() {
4
+ const isWindows = process.platform === 'win32';
5
+
6
+ if (!isWindows) {
7
+ // Check if running as root
8
+ const isRoot = process.getuid && process.getuid() === 0;
9
+ if (!isRoot) {
10
+ console.error(chalk.red.bold('\nāš ļø Permission Denied!'));
11
+ console.error(chalk.white('Modifying system applications requires administrator privileges.'));
12
+ console.error(chalk.white(`Please run the command with ${chalk.yellow('sudo')}:\n`));
13
+ console.error(chalk.cyan(' sudo npx agy-rtl patch\n'));
14
+ process.exit(1);
15
+ }
16
+ }
17
+ }
18
+
19
+ function delay(ms) {
20
+ return new Promise(resolve => setTimeout(resolve, ms));
21
+ }
22
+
23
+ module.exports = {
24
+ checkPermissions,
25
+ delay
26
+ };