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.
@@ -0,0 +1,607 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Aegis CLI - Command Line Interface for Aegis Framework
4
+
5
+ Usage:
6
+ aegis init [name] Create a new Aegis project
7
+ aegis dev Run in development mode (hot-reload)
8
+ aegis run Run the project
9
+ aegis build Build AppImage
10
+ aegis --version Show version
11
+ aegis --help Show help
12
+ """
13
+
14
+ import argparse
15
+ import os
16
+ import sys
17
+ import shutil
18
+ import json
19
+ import subprocess
20
+ import signal
21
+ from pathlib import Path
22
+
23
+
24
+ class AegisCLI:
25
+ """Aegis Command Line Interface"""
26
+
27
+ VERSION = "0.1.0"
28
+
29
+ def __init__(self):
30
+ self.parser = self._create_parser()
31
+ self.templates_dir = Path(__file__).parent.parent / 'templates'
32
+
33
+ def _create_parser(self):
34
+ """Create argument parser"""
35
+ parser = argparse.ArgumentParser(
36
+ prog='aegis',
37
+ description='Aegis - Lightweight AppImage Framework',
38
+ formatter_class=argparse.RawDescriptionHelpFormatter,
39
+ epilog="""
40
+ Examples:
41
+ aegis init my-app Create a new project called 'my-app'
42
+ aegis dev Start development server with hot-reload
43
+ aegis build Package as AppImage
44
+ """
45
+ )
46
+
47
+ parser.add_argument(
48
+ '--version', '-v',
49
+ action='version',
50
+ version=f'Aegis v{self.VERSION}'
51
+ )
52
+
53
+ subparsers = parser.add_subparsers(dest='command', help='Commands')
54
+
55
+ # init command
56
+ init_parser = subparsers.add_parser('init', help='Create a new Aegis project')
57
+ init_parser.add_argument('name', nargs='?', default='.', help='Project name or directory')
58
+ init_parser.add_argument('--template', '-t', default='default', help='Project template')
59
+
60
+ # dev command
61
+ dev_parser = subparsers.add_parser('dev', help='Run in development mode')
62
+ dev_parser.add_argument('--port', '-p', type=int, default=8080, help='Dev server port')
63
+ dev_parser.add_argument('--no-reload', action='store_true', help='Disable hot-reload')
64
+
65
+ # run command
66
+ run_parser = subparsers.add_parser('run', help='Run the project')
67
+ run_parser.add_argument('--config', '-c', help='Config file path')
68
+
69
+ # build command
70
+ build_parser = subparsers.add_parser('build', help='Build AppImage')
71
+ build_parser.add_argument('--output', '-o', help='Output directory')
72
+ build_parser.add_argument('--name', '-n', help='AppImage name')
73
+
74
+ return parser
75
+
76
+ def run(self, args=None):
77
+ """Run the CLI"""
78
+ parsed = self.parser.parse_args(args)
79
+
80
+ if parsed.command is None:
81
+ self.parser.print_help()
82
+ return 0
83
+
84
+ commands = {
85
+ 'init': self.cmd_init,
86
+ 'dev': self.cmd_dev,
87
+ 'run': self.cmd_run,
88
+ 'build': self.cmd_build
89
+ }
90
+
91
+ handler = commands.get(parsed.command)
92
+ if handler:
93
+ return handler(parsed)
94
+
95
+ return 1
96
+
97
+ def cmd_init(self, args):
98
+ """Initialize a new Aegis project"""
99
+ project_name = args.name
100
+
101
+ # Determine project directory
102
+ if project_name == '.':
103
+ project_dir = Path.cwd()
104
+ project_name = project_dir.name
105
+ else:
106
+ project_dir = Path.cwd() / project_name
107
+
108
+ print(f"⚔ Creating Aegis project: {project_name}")
109
+
110
+ # Check if directory exists and is not empty
111
+ if project_dir.exists() and any(project_dir.iterdir()):
112
+ print(f"āš ļø Directory '{project_dir}' is not empty.")
113
+ response = input("Continue anyway? [y/N] ")
114
+ if response.lower() != 'y':
115
+ return 1
116
+
117
+ # Create project directory
118
+ project_dir.mkdir(parents=True, exist_ok=True)
119
+
120
+ # Create project structure
121
+ self._create_project_files(project_dir, project_name)
122
+
123
+ print(f"""
124
+ āœ… Project created successfully!
125
+
126
+ Next steps:
127
+ cd {project_name}
128
+ aegis dev
129
+
130
+ Happy coding! šŸš€
131
+ """)
132
+
133
+ return 0
134
+
135
+ def _create_project_files(self, project_dir, project_name):
136
+ """Create all project files from templates"""
137
+
138
+ # Create subdirectories
139
+ (project_dir / 'src').mkdir(exist_ok=True)
140
+ (project_dir / 'assets').mkdir(exist_ok=True)
141
+
142
+ # Configuration file
143
+ config = {
144
+ 'name': project_name,
145
+ 'title': project_name.replace('-', ' ').title(),
146
+ 'version': '1.0.0',
147
+ 'main': 'src/index.html',
148
+ 'preload': 'src/preload.js',
149
+ 'width': 1200,
150
+ 'height': 800,
151
+ 'resizable': True,
152
+ 'frame': True,
153
+ 'devTools': True,
154
+ 'contextMenu': True,
155
+ 'icon': 'assets/icon.png',
156
+ 'description': 'An Aegis application'
157
+ }
158
+
159
+ with open(project_dir / 'aegis.config.json', 'w') as f:
160
+ json.dump(config, f, indent=2)
161
+
162
+ # Main HTML
163
+ html_content = f'''<!DOCTYPE html>
164
+ <html lang="en">
165
+ <head>
166
+ <meta charset="UTF-8">
167
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
168
+ <title>{project_name}</title>
169
+ <link rel="stylesheet" href="styles.css">
170
+ </head>
171
+ <body>
172
+ <div class="app">
173
+ <header class="header">
174
+ <h1>⚔ {project_name}</h1>
175
+ <p>Built with Aegis Framework</p>
176
+ </header>
177
+
178
+ <main class="main">
179
+ <div class="card">
180
+ <h2>Welcome to Aegis!</h2>
181
+ <p>Edit <code>src/index.html</code> to get started.</p>
182
+
183
+ <div class="demo">
184
+ <button id="btn-read" class="btn">šŸ“‚ Read File</button>
185
+ <button id="btn-run" class="btn">⚔ Run Command</button>
186
+ <button id="btn-dialog" class="btn btn-primary">šŸ’¬ Show Dialog</button>
187
+ </div>
188
+
189
+ <pre id="output" class="output">Output will appear here...</pre>
190
+ </div>
191
+ </main>
192
+
193
+ <footer class="footer">
194
+ Powered by Aegis v0.1.0 | WebKit2GTK + Python
195
+ </footer>
196
+ </div>
197
+
198
+ <script src="app.js"></script>
199
+ </body>
200
+ </html>
201
+ '''
202
+ with open(project_dir / 'src' / 'index.html', 'w') as f:
203
+ f.write(html_content)
204
+
205
+ # Styles
206
+ css_content = '''/* Aegis App Styles */
207
+ :root {
208
+ --primary: #00ff88;
209
+ --primary-dark: #00cc6a;
210
+ --bg: #0a0a0f;
211
+ --bg-card: #12121a;
212
+ --text: #ffffff;
213
+ --text-muted: #888899;
214
+ --border: #2a2a3a;
215
+ --radius: 12px;
216
+ }
217
+
218
+ * {
219
+ margin: 0;
220
+ padding: 0;
221
+ box-sizing: border-box;
222
+ }
223
+
224
+ body {
225
+ font-family: 'Segoe UI', system-ui, sans-serif;
226
+ background: var(--bg);
227
+ color: var(--text);
228
+ min-height: 100vh;
229
+ display: flex;
230
+ flex-direction: column;
231
+ }
232
+
233
+ .app {
234
+ display: flex;
235
+ flex-direction: column;
236
+ min-height: 100vh;
237
+ }
238
+
239
+ .header {
240
+ text-align: center;
241
+ padding: 3rem 2rem;
242
+ background: linear-gradient(180deg, #1a1a2a 0%, var(--bg) 100%);
243
+ }
244
+
245
+ .header h1 {
246
+ font-size: 2.5rem;
247
+ margin-bottom: 0.5rem;
248
+ background: linear-gradient(135deg, var(--primary), #00aaff);
249
+ -webkit-background-clip: text;
250
+ -webkit-text-fill-color: transparent;
251
+ }
252
+
253
+ .header p {
254
+ color: var(--text-muted);
255
+ }
256
+
257
+ .main {
258
+ flex: 1;
259
+ display: flex;
260
+ align-items: center;
261
+ justify-content: center;
262
+ padding: 2rem;
263
+ }
264
+
265
+ .card {
266
+ background: var(--bg-card);
267
+ border: 1px solid var(--border);
268
+ border-radius: var(--radius);
269
+ padding: 2rem;
270
+ max-width: 600px;
271
+ width: 100%;
272
+ }
273
+
274
+ .card h2 {
275
+ margin-bottom: 1rem;
276
+ }
277
+
278
+ .card p {
279
+ color: var(--text-muted);
280
+ margin-bottom: 1.5rem;
281
+ }
282
+
283
+ .card code {
284
+ background: #2a2a3a;
285
+ padding: 0.2rem 0.5rem;
286
+ border-radius: 4px;
287
+ font-family: 'Fira Code', monospace;
288
+ }
289
+
290
+ .demo {
291
+ display: flex;
292
+ gap: 1rem;
293
+ margin-bottom: 1.5rem;
294
+ flex-wrap: wrap;
295
+ }
296
+
297
+ .btn {
298
+ padding: 0.75rem 1.5rem;
299
+ border: 1px solid var(--border);
300
+ border-radius: 8px;
301
+ background: var(--bg);
302
+ color: var(--text);
303
+ cursor: pointer;
304
+ font-size: 1rem;
305
+ transition: all 0.2s;
306
+ }
307
+
308
+ .btn:hover {
309
+ background: #1a1a2a;
310
+ border-color: var(--primary);
311
+ }
312
+
313
+ .btn-primary {
314
+ background: var(--primary);
315
+ color: #000;
316
+ border-color: var(--primary);
317
+ }
318
+
319
+ .btn-primary:hover {
320
+ background: var(--primary-dark);
321
+ }
322
+
323
+ .output {
324
+ background: #000;
325
+ border: 1px solid var(--border);
326
+ border-radius: 8px;
327
+ padding: 1rem;
328
+ font-family: 'Fira Code', monospace;
329
+ font-size: 0.9rem;
330
+ color: var(--primary);
331
+ overflow-x: auto;
332
+ white-space: pre-wrap;
333
+ word-break: break-all;
334
+ }
335
+
336
+ .footer {
337
+ text-align: center;
338
+ padding: 1.5rem;
339
+ color: var(--text-muted);
340
+ font-size: 0.9rem;
341
+ border-top: 1px solid var(--border);
342
+ }
343
+ '''
344
+ with open(project_dir / 'src' / 'styles.css', 'w') as f:
345
+ f.write(css_content)
346
+
347
+ # JavaScript
348
+ js_content = '''/**
349
+ * Aegis App - Main JavaScript
350
+ */
351
+
352
+ // Wait for Aegis to be ready
353
+ document.addEventListener('DOMContentLoaded', async () => {
354
+ const output = document.getElementById('output');
355
+
356
+ // Check if running in Aegis
357
+ if (!Aegis.isAegis()) {
358
+ output.textContent = 'Not running in Aegis environment.\\nRun with: aegis dev';
359
+ return;
360
+ }
361
+
362
+ output.textContent = 'āœ… Aegis is ready!\\n\\nClick a button to test the API.';
363
+
364
+ // Read file button
365
+ document.getElementById('btn-read').addEventListener('click', async () => {
366
+ try {
367
+ const result = await Aegis.read({ path: '.' });
368
+ output.textContent = 'Directory contents:\\n\\n' +
369
+ result.entries.map(e => `${e.isDirectory ? 'šŸ“' : 'šŸ“„'} ${e.name}`).join('\\n');
370
+ } catch (err) {
371
+ output.textContent = 'Error: ' + err.message;
372
+ }
373
+ });
374
+
375
+ // Run command button
376
+ document.getElementById('btn-run').addEventListener('click', async () => {
377
+ try {
378
+ const result = await Aegis.run({ sh: 'uname -a' });
379
+ output.textContent = 'System info:\\n\\n' + result.output;
380
+ } catch (err) {
381
+ output.textContent = 'Error: ' + err.message;
382
+ }
383
+ });
384
+
385
+ // Dialog button
386
+ document.getElementById('btn-dialog').addEventListener('click', async () => {
387
+ try {
388
+ const result = await Aegis.dialog.message({
389
+ type: 'info',
390
+ title: 'Hello from Aegis!',
391
+ message: 'This is a native GTK dialog.\\nPretty cool, right?'
392
+ });
393
+ output.textContent = 'Dialog closed! Response: ' + JSON.stringify(result);
394
+ } catch (err) {
395
+ output.textContent = 'Error: ' + err.message;
396
+ }
397
+ });
398
+ });
399
+ '''
400
+ with open(project_dir / 'src' / 'app.js', 'w') as f:
401
+ f.write(js_content)
402
+
403
+ # Preload
404
+ preload_content = '''/**
405
+ * Aegis Preload Configuration
406
+ *
407
+ * This file controls which Aegis APIs are exposed to your frontend.
408
+ * For security, only expose the APIs you actually need.
409
+ */
410
+
411
+ // Expose specific APIs
412
+ Aegis.expose([
413
+ 'read', // File reading
414
+ 'write', // File writing
415
+ 'run', // Command execution
416
+ 'dialog', // Native dialogs
417
+ 'app', // App control
418
+ 'exists', // File existence check
419
+ 'mkdir', // Create directories
420
+ 'env' // Environment variables
421
+ ]);
422
+
423
+ // Configure Aegis
424
+ Aegis.config({
425
+ allowRemoteContent: false,
426
+ enableDevTools: true
427
+ });
428
+
429
+ console.log('āœ… Preload configured');
430
+ '''
431
+ with open(project_dir / 'src' / 'preload.js', 'w') as f:
432
+ f.write(preload_content)
433
+
434
+ # README
435
+ readme_content = f'''# {project_name}
436
+
437
+ An application built with [Aegis Framework](https://github.com/your-repo/aegis).
438
+
439
+ ## Getting Started
440
+
441
+ ```bash
442
+ # Development mode
443
+ aegis dev
444
+
445
+ # Build AppImage
446
+ aegis build
447
+ ```
448
+
449
+ ## Project Structure
450
+
451
+ ```
452
+ {project_name}/
453
+ ā”œā”€ā”€ aegis.config.json # Project configuration
454
+ ā”œā”€ā”€ src/
455
+ │ ā”œā”€ā”€ index.html # Main HTML
456
+ │ ā”œā”€ā”€ styles.css # Styles
457
+ │ ā”œā”€ā”€ app.js # JavaScript
458
+ │ └── preload.js # Security configuration
459
+ └── assets/
460
+ └── icon.png # App icon
461
+ ```
462
+
463
+ ## Aegis API
464
+
465
+ ```javascript
466
+ // Read files
467
+ const content = await Aegis.read({{ path: '.', file: 'data.txt' }});
468
+
469
+ // Write files
470
+ await Aegis.write({{ path: '.', file: 'output.txt', content: 'Hello!' }});
471
+
472
+ // Run commands
473
+ const result = await Aegis.run({{ sh: 'ls -la' }});
474
+
475
+ // Dialogs
476
+ await Aegis.dialog.message({{ type: 'info', title: 'Hello', message: 'World!' }});
477
+ ```
478
+
479
+ ## License
480
+
481
+ MIT
482
+ '''
483
+ with open(project_dir / 'README.md', 'w') as f:
484
+ f.write(readme_content)
485
+
486
+ # .gitignore
487
+ gitignore_content = '''# Aegis
488
+ dist/
489
+ *.AppImage
490
+ *.AppDir/
491
+
492
+ # Python
493
+ __pycache__/
494
+ *.pyc
495
+ .venv/
496
+
497
+ # Node (if using npm for frontend)
498
+ node_modules/
499
+
500
+ # IDE
501
+ .vscode/
502
+ .idea/
503
+ '''
504
+ with open(project_dir / '.gitignore', 'w') as f:
505
+ f.write(gitignore_content)
506
+
507
+ print(f" šŸ“ Created project structure")
508
+ print(f" šŸ“„ aegis.config.json")
509
+ print(f" šŸ“„ src/index.html")
510
+ print(f" šŸ“„ src/styles.css")
511
+ print(f" šŸ“„ src/app.js")
512
+ print(f" šŸ“„ src/preload.js")
513
+ print(f" šŸ“„ README.md")
514
+
515
+ def cmd_dev(self, args):
516
+ """Run in development mode"""
517
+ print("⚔ Starting Aegis in development mode...")
518
+
519
+ # Check for config
520
+ if not Path('aegis.config.json').exists():
521
+ print("āŒ No aegis.config.json found. Run 'aegis init' first.")
522
+ return 1
523
+
524
+ # Import and run
525
+ try:
526
+ from aegis.core.aegis import AegisApp
527
+ app = AegisApp()
528
+
529
+ # Enable dev tools
530
+ app.config['devTools'] = True
531
+
532
+ print(f"šŸ“‚ Loading: {app.config.get('main', 'index.html')}")
533
+ print("šŸ”„ Hot-reload: Enabled (save files to refresh)")
534
+ print("šŸ› ļø DevTools: Right-click → Inspect Element")
535
+ print("")
536
+
537
+ app.run()
538
+
539
+ except ImportError as e:
540
+ print(f"āŒ Missing dependency: {e}")
541
+ print("Make sure you have installed: python3-gi gir1.2-webkit2-4.1")
542
+ return 1
543
+ except Exception as e:
544
+ print(f"āŒ Error: {e}")
545
+ return 1
546
+
547
+ return 0
548
+
549
+ def cmd_run(self, args):
550
+ """Run the project (production mode)"""
551
+ print("⚔ Running Aegis application...")
552
+
553
+ config_path = args.config or 'aegis.config.json'
554
+
555
+ if not Path(config_path).exists():
556
+ print(f"āŒ Config not found: {config_path}")
557
+ return 1
558
+
559
+ try:
560
+ from aegis.core.aegis import AegisApp
561
+ app = AegisApp(config_path)
562
+ app.config['devTools'] = False
563
+ app.run()
564
+
565
+ except Exception as e:
566
+ print(f"āŒ Error: {e}")
567
+ return 1
568
+
569
+ return 0
570
+
571
+ def cmd_build(self, args):
572
+ """Build AppImage"""
573
+ print("šŸ“¦ Building AppImage...")
574
+
575
+ if not Path('aegis.config.json').exists():
576
+ print("āŒ No aegis.config.json found.")
577
+ return 1
578
+
579
+ try:
580
+ from aegis.builder.builder import AegisBuilder
581
+
582
+ builder = AegisBuilder()
583
+ output = args.output or 'dist'
584
+ name = args.name
585
+
586
+ appimage_path = builder.build(output_dir=output, name=name)
587
+
588
+ print(f"\nāœ… AppImage created: {appimage_path}")
589
+ print(f" Size: {Path(appimage_path).stat().st_size / 1024 / 1024:.1f} MB")
590
+
591
+ except Exception as e:
592
+ print(f"āŒ Build failed: {e}")
593
+ import traceback
594
+ traceback.print_exc()
595
+ return 1
596
+
597
+ return 0
598
+
599
+
600
+ def main():
601
+ """Entry point for CLI"""
602
+ cli = AegisCLI()
603
+ sys.exit(cli.run())
604
+
605
+
606
+ if __name__ == '__main__':
607
+ main()
@@ -0,0 +1,16 @@
1
+ """
2
+ Aegis Core Module
3
+ Contains window management and IPC bridge
4
+ """
5
+
6
+ # Lazy imports to allow CLI to work without GTK
7
+ def __getattr__(name):
8
+ if name == 'AegisApp':
9
+ from aegis.core.aegis import AegisApp
10
+ return AegisApp
11
+ elif name == 'AegisWindow':
12
+ from aegis.core.window import AegisWindow
13
+ return AegisWindow
14
+ raise AttributeError(f"module 'aegis.core' has no attribute '{name}'")
15
+
16
+ __all__ = ['AegisApp', 'AegisWindow']