aegis-framework 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Diego
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,239 @@
1
+ # ⚡ Aegis Framework
2
+
3
+ **A lightweight alternative to Electron for Linux desktop apps.**
4
+
5
+ Create desktop applications with HTML, CSS, and JavaScript that package into ~200KB AppImages instead of 150MB!
6
+
7
+ ## 📊 Size Comparison
8
+
9
+ | Framework | Typical App Size | Aegis App |
10
+ |-----------|-----------------|-----------|
11
+ | **Electron** | 150-200 MB | - |
12
+ | **Aegis** | - | **~200 KB** |
13
+
14
+ **That's 700x smaller!**
15
+
16
+ ## 🚀 Installation
17
+
18
+ ```bash
19
+ # Install globally via npm
20
+ npm install -g aegis-framework
21
+
22
+ # Or use npx without installing
23
+ npx aegis-framework init my-app
24
+ ```
25
+
26
+ ### System Requirements (Linux only)
27
+
28
+ ```bash
29
+ # Ubuntu/Debian
30
+ sudo apt install python3 python3-gi gir1.2-webkit2-4.1
31
+
32
+ # Fedora
33
+ sudo dnf install python3 python3-gobject webkit2gtk4.1
34
+
35
+ # Arch
36
+ sudo pacman -S python python-gobject webkit2gtk-4.1
37
+ ```
38
+
39
+ ## 🏁 Quick Start
40
+
41
+ ```bash
42
+ # Create a new project
43
+ aegis init my-app
44
+
45
+ # Enter the directory
46
+ cd my-app
47
+
48
+ # Start development server
49
+ aegis dev
50
+
51
+ # Build AppImage
52
+ aegis build
53
+ ```
54
+
55
+ ## 📁 Project Structure
56
+
57
+ ```
58
+ my-app/
59
+ ├── aegis.config.json # Configuration
60
+ ├── src/
61
+ │ ├── index.html # Main HTML
62
+ │ ├── styles.css # Styles
63
+ │ ├── app.js # JavaScript
64
+ │ └── preload.js # Security config
65
+ └── assets/
66
+ └── icon.png # App icon
67
+ ```
68
+
69
+ ## 🔌 API Reference
70
+
71
+ ### File Operations
72
+
73
+ ```javascript
74
+ // Read directory
75
+ const dir = await Aegis.read({ path: '/home/user' });
76
+ console.log(dir.entries);
77
+
78
+ // Read file
79
+ const file = await Aegis.read({ path: '/home/user', file: 'data.txt' });
80
+ console.log(file.content);
81
+
82
+ // Write file
83
+ await Aegis.write({
84
+ path: '/home/user',
85
+ file: 'output.txt',
86
+ content: 'Hello World!'
87
+ });
88
+
89
+ // Check existence
90
+ const info = await Aegis.exists({ path: '/home/user/file.txt' });
91
+
92
+ // Create directory
93
+ await Aegis.mkdir({ path: '/home/user/new-folder' });
94
+
95
+ // Delete
96
+ await Aegis.remove({ path: '/home/user/old-file.txt' });
97
+
98
+ // Copy
99
+ await Aegis.copy({ src: '/path/from', dest: '/path/to' });
100
+
101
+ // Move/Rename
102
+ await Aegis.move({ src: '/path/from', dest: '/path/to' });
103
+ ```
104
+
105
+ ### Dialogs
106
+
107
+ ```javascript
108
+ // Message dialog
109
+ await Aegis.dialog.message({
110
+ type: 'info', // info, warning, error, question
111
+ title: 'Hello',
112
+ message: 'World!'
113
+ });
114
+
115
+ // Confirmation dialog
116
+ const result = await Aegis.dialog.message({
117
+ type: 'question',
118
+ title: 'Confirm',
119
+ message: 'Are you sure?',
120
+ buttons: 'yesno'
121
+ });
122
+ if (result.response) { /* User clicked Yes */ }
123
+
124
+ // Open file dialog
125
+ const file = await Aegis.dialog.open({
126
+ title: 'Select File',
127
+ filters: [{ name: 'Images', extensions: ['png', 'jpg'] }]
128
+ });
129
+
130
+ // Save file dialog
131
+ const savePath = await Aegis.dialog.save({
132
+ title: 'Save As',
133
+ defaultName: 'document.txt'
134
+ });
135
+ ```
136
+
137
+ ### App Control
138
+
139
+ ```javascript
140
+ // Window controls
141
+ await Aegis.app.minimize();
142
+ await Aegis.app.maximize();
143
+ await Aegis.app.quit();
144
+
145
+ // Get system paths (localized!)
146
+ const home = await Aegis.app.getPath({ name: 'home' });
147
+ const docs = await Aegis.app.getPath({ name: 'documents' });
148
+ // Also: desktop, downloads, music, pictures, videos
149
+ ```
150
+
151
+ ### Window Control (Frameless)
152
+
153
+ ```javascript
154
+ // Make element draggable for window movement
155
+ Aegis.window.moveBar('#titlebar', { exclude: '.btn' });
156
+
157
+ // Setup resize handles
158
+ Aegis.window.resizeHandles({
159
+ '.resize-n': 'n',
160
+ '.resize-s': 's',
161
+ '.resize-se': 'se'
162
+ // etc: e, w, ne, nw, sw
163
+ });
164
+
165
+ // Get/set size
166
+ const size = await Aegis.window.getSize();
167
+ await Aegis.window.setSize({ width: 1024, height: 768 });
168
+
169
+ // Get/set position
170
+ const pos = await Aegis.window.getPosition();
171
+ await Aegis.window.setPosition({ x: 100, y: 100 });
172
+ ```
173
+
174
+ ### Run Commands
175
+
176
+ ```javascript
177
+ // Shell command
178
+ const result = await Aegis.run({ sh: 'ls -la' });
179
+ console.log(result.output);
180
+
181
+ // Python code
182
+ const py = await Aegis.run({ py: '2 + 2' });
183
+ console.log(py.output); // "4"
184
+ ```
185
+
186
+ ## 🔒 Security (preload.js)
187
+
188
+ Control which APIs are exposed to your frontend:
189
+
190
+ ```javascript
191
+ // src/preload.js
192
+ Aegis.expose([
193
+ 'read', // Allow file reading
194
+ 'write', // Allow file writing
195
+ 'dialog', // Allow dialogs
196
+ 'app', // Allow app control
197
+ 'window', // Allow window control
198
+ // Omit 'run' to prevent command execution!
199
+ ]);
200
+ ```
201
+
202
+ ## ⚙️ Configuration (aegis.config.json)
203
+
204
+ ```json
205
+ {
206
+ "name": "my-app",
207
+ "title": "My Application",
208
+ "version": "1.0.0",
209
+ "main": "src/index.html",
210
+ "preload": "src/preload.js",
211
+ "width": 1200,
212
+ "height": 800,
213
+ "frame": true,
214
+ "resizable": true,
215
+ "icon": "assets/icon.png"
216
+ }
217
+ ```
218
+
219
+ Set `"frame": false` for frameless window (custom titlebar).
220
+
221
+ ## 🤔 Why Aegis?
222
+
223
+ | Feature | Electron | Aegis |
224
+ |---------|----------|-------|
225
+ | Size | ~150 MB | **~200 KB** |
226
+ | Backend | Node.js | **Python** |
227
+ | Renderer | Chromium (bundled) | **WebKit2GTK (system)** |
228
+ | Platform | Cross-platform | **Linux** |
229
+ | RAM Usage | High | **Low** |
230
+
231
+ Aegis uses the WebKit that's already installed on Linux systems, so apps are tiny!
232
+
233
+ ## 📜 License
234
+
235
+ MIT © Diego
236
+
237
+ ---
238
+
239
+ Made with ❤️ for the Linux community
@@ -0,0 +1,19 @@
1
+ """
2
+ Aegis - Lightweight AppImage Framework
3
+ A modern alternative to Electron using WebKit2GTK and Python
4
+ """
5
+
6
+ __version__ = "0.1.0"
7
+ __author__ = "Diego"
8
+
9
+ # Lazy imports to allow CLI to work without GTK installed
10
+ def __getattr__(name):
11
+ if name == 'AegisApp':
12
+ from aegis.core.aegis import AegisApp
13
+ return AegisApp
14
+ elif name == 'AegisWindow':
15
+ from aegis.core.window import AegisWindow
16
+ return AegisWindow
17
+ raise AttributeError(f"module 'aegis' has no attribute '{name}'")
18
+
19
+ __all__ = ['AegisApp', 'AegisWindow']
@@ -0,0 +1,5 @@
1
+ """Aegis Builder - AppImage packaging"""
2
+
3
+ from aegis.builder.builder import AegisBuilder
4
+
5
+ __all__ = ['AegisBuilder']
@@ -0,0 +1,301 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Aegis Builder - AppImage Packaging System
4
+
5
+ Creates lightweight AppImages from Aegis projects.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import json
11
+ import shutil
12
+ import stat
13
+ import subprocess
14
+ import urllib.request
15
+ from pathlib import Path
16
+ from typing import Optional
17
+
18
+
19
+ class AegisBuilder:
20
+ """
21
+ Builds AppImages from Aegis projects
22
+ """
23
+
24
+ APPIMAGETOOL_URL = "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
25
+
26
+ def __init__(self, project_dir: str = '.'):
27
+ self.project_dir = Path(project_dir).resolve()
28
+ self.config = self._load_config()
29
+
30
+ def _load_config(self) -> dict:
31
+ """Load project configuration"""
32
+ config_path = self.project_dir / 'aegis.config.json'
33
+ if config_path.exists():
34
+ with open(config_path) as f:
35
+ return json.load(f)
36
+ return {}
37
+
38
+ def build(self, output_dir: str = 'dist', name: Optional[str] = None) -> str:
39
+ """
40
+ Build the AppImage
41
+
42
+ Args:
43
+ output_dir: Output directory for the AppImage
44
+ name: Custom name for the AppImage
45
+
46
+ Returns:
47
+ Path to the created AppImage
48
+ """
49
+ app_name = name or self.config.get('name', 'AegisApp')
50
+ output_path = Path(output_dir).resolve()
51
+ appdir = output_path / f'{app_name}.AppDir'
52
+
53
+ print(f"📦 Building: {app_name}")
54
+
55
+ # Clean and create AppDir
56
+ if appdir.exists():
57
+ shutil.rmtree(appdir)
58
+ appdir.mkdir(parents=True)
59
+
60
+ # Create AppDir structure
61
+ self._create_appdir_structure(appdir, app_name)
62
+
63
+ # Copy project files
64
+ self._copy_project_files(appdir)
65
+
66
+ # Copy Aegis runtime
67
+ self._copy_aegis_runtime(appdir)
68
+
69
+ # Create AppRun script
70
+ self._create_apprun(appdir, app_name)
71
+
72
+ # Create .desktop file
73
+ self._create_desktop_file(appdir, app_name)
74
+
75
+ # Create icon
76
+ self._create_icon(appdir, app_name)
77
+
78
+ # Download appimagetool if needed
79
+ appimagetool = self._ensure_appimagetool(output_path)
80
+
81
+ # Build AppImage
82
+ appimage_path = output_path / f'{app_name}-x86_64.AppImage'
83
+ self._build_appimage(appdir, appimage_path, appimagetool)
84
+
85
+ return str(appimage_path)
86
+
87
+ def _create_appdir_structure(self, appdir: Path, app_name: str):
88
+ """Create AppDir directory structure"""
89
+ dirs = [
90
+ 'usr/bin',
91
+ 'usr/lib/aegis',
92
+ 'usr/share/applications',
93
+ 'usr/share/icons/hicolor/256x256/apps',
94
+ 'usr/share/aegis/runtime',
95
+ 'app'
96
+ ]
97
+ for d in dirs:
98
+ (appdir / d).mkdir(parents=True, exist_ok=True)
99
+
100
+ print(" 📁 Created AppDir structure")
101
+
102
+ def _copy_project_files(self, appdir: Path):
103
+ """Copy project files to AppDir"""
104
+ app_dir = appdir / 'app'
105
+
106
+ # Copy all project files except dist, __pycache__, etc.
107
+ exclude = {'.git', '__pycache__', 'dist', '.venv', 'node_modules', '*.AppDir'}
108
+
109
+ for item in self.project_dir.iterdir():
110
+ if item.name in exclude or item.suffix == '.AppDir':
111
+ continue
112
+
113
+ dest = app_dir / item.name
114
+ if item.is_dir():
115
+ shutil.copytree(item, dest, ignore=shutil.ignore_patterns(*exclude))
116
+ else:
117
+ shutil.copy2(item, dest)
118
+
119
+ print(" 📄 Copied project files")
120
+
121
+ def _copy_aegis_runtime(self, appdir: Path):
122
+ """Copy Aegis runtime files"""
123
+ aegis_dir = Path(__file__).parent.parent
124
+ runtime_dir = appdir / 'usr/share/aegis'
125
+
126
+ # Create directory
127
+ runtime_dir.mkdir(parents=True, exist_ok=True)
128
+
129
+ # Copy core modules
130
+ shutil.copytree(
131
+ aegis_dir / 'core',
132
+ runtime_dir / 'core',
133
+ ignore=shutil.ignore_patterns('__pycache__', '*.pyc'),
134
+ dirs_exist_ok=True
135
+ )
136
+
137
+ # Copy runtime (JS API)
138
+ shutil.copytree(
139
+ aegis_dir / 'runtime',
140
+ runtime_dir / 'runtime',
141
+ ignore=shutil.ignore_patterns('__pycache__', '*.pyc'),
142
+ dirs_exist_ok=True
143
+ )
144
+
145
+ # Create __init__.py
146
+ init_content = '''"""Aegis Runtime"""
147
+ from aegis.core.aegis import AegisApp
148
+ '''
149
+ with open(runtime_dir / '__init__.py', 'w') as f:
150
+ f.write(init_content)
151
+
152
+ print(" ⚡ Copied Aegis runtime")
153
+
154
+ def _create_apprun(self, appdir: Path, app_name: str):
155
+ """Create AppRun script"""
156
+ apprun_content = '''#!/bin/bash
157
+ # Aegis AppRun Script
158
+
159
+ # Get the directory where AppImage is mounted
160
+ APPDIR="$(dirname "$(readlink -f "$0")")"
161
+
162
+ # Set up Python path
163
+ export PYTHONPATH="$APPDIR/usr/share:$PYTHONPATH"
164
+
165
+ # Change to app directory
166
+ cd "$APPDIR/app"
167
+
168
+ # Run Aegis app
169
+ exec python3 -c "
170
+ import sys
171
+ sys.path.insert(0, '$APPDIR/usr/share')
172
+ from aegis.core.aegis import AegisApp
173
+ app = AegisApp()
174
+ app.run()
175
+ "
176
+ '''
177
+ apprun_path = appdir / 'AppRun'
178
+ with open(apprun_path, 'w') as f:
179
+ f.write(apprun_content)
180
+
181
+ # Make executable
182
+ apprun_path.chmod(apprun_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
183
+
184
+ print(" 🚀 Created AppRun script")
185
+
186
+ def _create_desktop_file(self, appdir: Path, app_name: str):
187
+ """Create .desktop file"""
188
+ desktop_content = f'''[Desktop Entry]
189
+ Type=Application
190
+ Name={self.config.get('title', app_name)}
191
+ Exec=AppRun
192
+ Icon={app_name.lower()}
193
+ Categories=Utility;
194
+ Comment={self.config.get('description', 'An Aegis application')}
195
+ Terminal=false
196
+ '''
197
+ # Write to AppDir root and usr/share/applications
198
+ for path in [appdir / f'{app_name}.desktop',
199
+ appdir / 'usr/share/applications' / f'{app_name}.desktop']:
200
+ with open(path, 'w') as f:
201
+ f.write(desktop_content)
202
+
203
+ print(" 📋 Created .desktop file")
204
+
205
+ def _create_icon(self, appdir: Path, app_name: str):
206
+ """Create or copy app icon"""
207
+ icon_source = self.project_dir / self.config.get('icon', 'assets/icon.png')
208
+
209
+ if icon_source.exists():
210
+ # Copy existing icon
211
+ shutil.copy2(icon_source, appdir / f'{app_name.lower()}.png')
212
+ shutil.copy2(
213
+ icon_source,
214
+ appdir / 'usr/share/icons/hicolor/256x256/apps' / f'{app_name.lower()}.png'
215
+ )
216
+ print(" 🎨 Copied app icon")
217
+ else:
218
+ # Create a simple placeholder icon (1x1 transparent PNG)
219
+ # In production, you'd want to generate a proper icon
220
+ placeholder = appdir / f'{app_name.lower()}.png'
221
+ self._create_placeholder_icon(placeholder)
222
+ shutil.copy2(
223
+ placeholder,
224
+ appdir / 'usr/share/icons/hicolor/256x256/apps' / f'{app_name.lower()}.png'
225
+ )
226
+ print(" 🎨 Created placeholder icon")
227
+
228
+ def _create_placeholder_icon(self, path: Path):
229
+ """Create a simple placeholder PNG icon"""
230
+ # Minimal valid PNG (1x1 transparent pixel)
231
+ # In a real scenario, we'd generate a proper icon with cairo or PIL
232
+ png_data = bytes([
233
+ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, # PNG signature
234
+ 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, # IHDR chunk
235
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, # 1x1
236
+ 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4,
237
+ 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41,
238
+ 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00,
239
+ 0x05, 0x00, 0x01, 0x0D, 0x0A, 0x2D, 0xB4, 0x00,
240
+ 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
241
+ 0x42, 0x60, 0x82
242
+ ])
243
+ with open(path, 'wb') as f:
244
+ f.write(png_data)
245
+
246
+ def _ensure_appimagetool(self, output_path: Path) -> Path:
247
+ """Download appimagetool if not present"""
248
+ tool_path = output_path / 'appimagetool'
249
+
250
+ if not tool_path.exists():
251
+ print(" 📥 Downloading appimagetool...")
252
+ urllib.request.urlretrieve(self.APPIMAGETOOL_URL, tool_path)
253
+ tool_path.chmod(tool_path.stat().st_mode | stat.S_IXUSR)
254
+
255
+ return tool_path
256
+
257
+ def _build_appimage(self, appdir: Path, output: Path, appimagetool: Path):
258
+ """Build the final AppImage"""
259
+ print(" 🔨 Building AppImage...")
260
+
261
+ env = os.environ.copy()
262
+ env['ARCH'] = 'x86_64'
263
+
264
+ result = subprocess.run(
265
+ [str(appimagetool), str(appdir), str(output)],
266
+ env=env,
267
+ capture_output=True,
268
+ text=True
269
+ )
270
+
271
+ if result.returncode != 0:
272
+ print(f" ❌ appimagetool error: {result.stderr}")
273
+ raise RuntimeError(f"appimagetool failed: {result.stderr}")
274
+
275
+ # Clean up AppDir
276
+ shutil.rmtree(appdir)
277
+
278
+ print(f" ✅ AppImage created: {output.name}")
279
+
280
+
281
+ def build_project(project_dir: str = '.', output_dir: str = 'dist', name: str = None) -> str:
282
+ """
283
+ Convenience function to build a project
284
+
285
+ Args:
286
+ project_dir: Path to the project
287
+ output_dir: Output directory
288
+ name: Custom AppImage name
289
+
290
+ Returns:
291
+ Path to the created AppImage
292
+ """
293
+ builder = AegisBuilder(project_dir)
294
+ return builder.build(output_dir, name)
295
+
296
+
297
+ if __name__ == '__main__':
298
+ if len(sys.argv) > 1:
299
+ build_project(sys.argv[1])
300
+ else:
301
+ build_project()
@@ -0,0 +1,5 @@
1
+ """Aegis CLI - Command Line Interface"""
2
+
3
+ from aegis.cli.cli import main, AegisCLI
4
+
5
+ __all__ = ['main', 'AegisCLI']