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,519 @@
1
+ /**
2
+ * Aegis JavaScript API
3
+ *
4
+ * This file is injected into WebKit2GTK and provides the bridge
5
+ * between your frontend JavaScript and the Python backend.
6
+ *
7
+ * @version 0.1.0
8
+ */
9
+
10
+ (function () {
11
+ 'use strict';
12
+
13
+ // ==================== Internal Bridge ====================
14
+
15
+ let callbackId = 0;
16
+ const pendingCallbacks = new Map();
17
+
18
+ /**
19
+ * Resolve a callback from Python
20
+ */
21
+ window.__aegisResolve = function (response) {
22
+ const callback = pendingCallbacks.get(response.callbackId);
23
+ if (callback) {
24
+ pendingCallbacks.delete(response.callbackId);
25
+ if (response.success) {
26
+ callback.resolve(response.data);
27
+ } else {
28
+ callback.reject(new Error(response.error));
29
+ }
30
+ }
31
+ };
32
+
33
+ /**
34
+ * Send a message to Python backend
35
+ */
36
+ function invoke(action, payload = {}) {
37
+ return new Promise((resolve, reject) => {
38
+ const id = ++callbackId;
39
+ pendingCallbacks.set(id, { resolve, reject });
40
+
41
+ const message = JSON.stringify({
42
+ action: action,
43
+ payload: payload,
44
+ callbackId: id
45
+ });
46
+
47
+ // Send to Python via WebKit message handler
48
+ if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.aegis) {
49
+ window.webkit.messageHandlers.aegis.postMessage(message);
50
+ } else {
51
+ reject(new Error('Aegis bridge not available'));
52
+ }
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Check if an API is allowed by preload
58
+ */
59
+ function isAllowed(api) {
60
+ const allowed = window.__aegisAllowedAPIs || ['*'];
61
+ if (allowed.includes('*')) return true;
62
+ if (allowed.includes(api)) return true;
63
+
64
+ // Check namespace (e.g., 'dialog' allows 'dialog.open')
65
+ const namespace = api.split('.')[0];
66
+ return allowed.includes(namespace);
67
+ }
68
+
69
+ /**
70
+ * Create a guarded API function
71
+ */
72
+ function guardedInvoke(action) {
73
+ return function (payload) {
74
+ if (!isAllowed(action)) {
75
+ return Promise.reject(new Error(`API '${action}' is not allowed by preload`));
76
+ }
77
+ return invoke(action, payload);
78
+ };
79
+ }
80
+
81
+ // ==================== Public Aegis API ====================
82
+
83
+ const Aegis = {
84
+ /**
85
+ * Read file or directory contents
86
+ *
87
+ * @example
88
+ * // Read a file
89
+ * const result = await Aegis.read({ path: '/home/user', file: 'data.txt' });
90
+ * console.log(result.content);
91
+ *
92
+ * // List directory
93
+ * const dir = await Aegis.read({ path: '/home/user/documents' });
94
+ * console.log(dir.entries);
95
+ */
96
+ read: guardedInvoke('read'),
97
+
98
+ /**
99
+ * Write content to a file
100
+ *
101
+ * @example
102
+ * await Aegis.write({
103
+ * path: '/home/user',
104
+ * file: 'output.txt',
105
+ * content: 'Hello, World!'
106
+ * });
107
+ *
108
+ * // Append to file
109
+ * await Aegis.write({
110
+ * path: '/home/user',
111
+ * file: 'log.txt',
112
+ * content: 'New line\n',
113
+ * append: true
114
+ * });
115
+ */
116
+ write: guardedInvoke('write'),
117
+
118
+ /**
119
+ * Execute Python or shell commands
120
+ *
121
+ * @example
122
+ * // Run shell command
123
+ * const result = await Aegis.run({ sh: 'ls -la' });
124
+ * console.log(result.output);
125
+ *
126
+ * // Run Python code
127
+ * const pyResult = await Aegis.run({ py: '2 + 2' });
128
+ * console.log(pyResult.output); // "4"
129
+ */
130
+ run: guardedInvoke('run'),
131
+
132
+ /**
133
+ * Check if file/directory exists
134
+ *
135
+ * @example
136
+ * const info = await Aegis.exists({ path: '/home/user/file.txt' });
137
+ * if (info.exists && info.isFile) {
138
+ * console.log('File exists!');
139
+ * }
140
+ */
141
+ exists: guardedInvoke('exists'),
142
+
143
+ /**
144
+ * Create directory
145
+ *
146
+ * @example
147
+ * await Aegis.mkdir({ path: '/home/user/new-folder', recursive: true });
148
+ */
149
+ mkdir: guardedInvoke('mkdir'),
150
+
151
+ /**
152
+ * Remove file or directory
153
+ *
154
+ * @example
155
+ * await Aegis.remove({ path: '/home/user/old-file.txt' });
156
+ *
157
+ * // Remove directory recursively
158
+ * await Aegis.remove({ path: '/home/user/old-folder', recursive: true });
159
+ */
160
+ remove: guardedInvoke('remove'),
161
+
162
+ /**
163
+ * Copy file or directory
164
+ *
165
+ * @example
166
+ * await Aegis.copy({
167
+ * src: '/home/user/file.txt',
168
+ * dest: '/home/user/backup/file.txt'
169
+ * });
170
+ */
171
+ copy: guardedInvoke('copy'),
172
+
173
+ /**
174
+ * Move/rename file or directory
175
+ *
176
+ * @example
177
+ * await Aegis.move({
178
+ * src: '/home/user/old-name.txt',
179
+ * dest: '/home/user/new-name.txt'
180
+ * });
181
+ */
182
+ move: guardedInvoke('move'),
183
+
184
+ /**
185
+ * Get/set environment variables
186
+ *
187
+ * @example
188
+ * // Get single variable
189
+ * const home = await Aegis.env({ name: 'HOME' });
190
+ *
191
+ * // Set variable
192
+ * await Aegis.env({ name: 'MY_VAR', value: 'hello' });
193
+ *
194
+ * // Get all variables
195
+ * const allEnv = await Aegis.env({});
196
+ */
197
+ env: guardedInvoke('env'),
198
+
199
+ // ==================== Dialog API ====================
200
+
201
+ dialog: {
202
+ /**
203
+ * Open file dialog
204
+ *
205
+ * @example
206
+ * const result = await Aegis.dialog.open({
207
+ * title: 'Select Files',
208
+ * multiple: true,
209
+ * filters: [
210
+ * { name: 'Images', extensions: ['png', 'jpg', 'gif'] },
211
+ * { name: 'All Files', extensions: ['*'] }
212
+ * ]
213
+ * });
214
+ * console.log(result.path);
215
+ */
216
+ open: guardedInvoke('dialog.open'),
217
+
218
+ /**
219
+ * Save file dialog
220
+ *
221
+ * @example
222
+ * const result = await Aegis.dialog.save({
223
+ * title: 'Save As',
224
+ * defaultName: 'document.txt'
225
+ * });
226
+ * if (result.path) {
227
+ * await Aegis.write({ path: result.path, content: 'Content' });
228
+ * }
229
+ */
230
+ save: guardedInvoke('dialog.save'),
231
+
232
+ /**
233
+ * Show message dialog
234
+ *
235
+ * @example
236
+ * // Info dialog
237
+ * await Aegis.dialog.message({
238
+ * type: 'info',
239
+ * title: 'Success',
240
+ * message: 'Operation completed!'
241
+ * });
242
+ *
243
+ * // Confirmation dialog
244
+ * const confirm = await Aegis.dialog.message({
245
+ * type: 'question',
246
+ * title: 'Confirm',
247
+ * message: 'Are you sure?',
248
+ * buttons: 'yesno'
249
+ * });
250
+ * if (confirm.response) {
251
+ * // User clicked Yes
252
+ * }
253
+ */
254
+ message: guardedInvoke('dialog.message')
255
+ },
256
+
257
+ // ==================== App Control API ====================
258
+
259
+ app: {
260
+ /**
261
+ * Quit the application
262
+ */
263
+ quit: guardedInvoke('app.quit'),
264
+
265
+ /**
266
+ * Minimize the window
267
+ */
268
+ minimize: guardedInvoke('app.minimize'),
269
+
270
+ /**
271
+ * Toggle maximize
272
+ */
273
+ maximize: guardedInvoke('app.maximize'),
274
+
275
+ /**
276
+ * Get system paths
277
+ *
278
+ * @example
279
+ * const home = await Aegis.app.getPath({ name: 'home' });
280
+ * const downloads = await Aegis.app.getPath({ name: 'downloads' });
281
+ *
282
+ * // Available: home, desktop, documents, downloads, music, pictures, videos, temp, app
283
+ */
284
+ getPath: guardedInvoke('app.getPath')
285
+ },
286
+
287
+ // ==================== Window Control API ====================
288
+
289
+ window: {
290
+ /**
291
+ * Make an element draggable for window movement
292
+ * Call this on mousedown of your titlebar element
293
+ *
294
+ * @example
295
+ * // In your HTML: <div class="titlebar" id="titlebar">...</div>
296
+ * // In your JS:
297
+ * document.getElementById('titlebar').addEventListener('mousedown', (e) => {
298
+ * if (e.target.closest('.titlebar-btn')) return; // Don't drag on buttons
299
+ * Aegis.window.startDrag();
300
+ * });
301
+ */
302
+ startDrag: guardedInvoke('window.startDrag'),
303
+
304
+ /**
305
+ * Start window resize from edge
306
+ *
307
+ * @param {string} edge - Edge to resize from: n, s, e, w, ne, nw, se, sw
308
+ *
309
+ * @example
310
+ * // Create resize handles in your CSS/HTML, then:
311
+ * document.querySelector('.resize-se').addEventListener('mousedown', () => {
312
+ * Aegis.window.resize({ edge: 'se' });
313
+ * });
314
+ */
315
+ resize: guardedInvoke('window.resize'),
316
+
317
+ /**
318
+ * Set window size
319
+ *
320
+ * @example
321
+ * await Aegis.window.setSize({ width: 1024, height: 768 });
322
+ */
323
+ setSize: guardedInvoke('window.setSize'),
324
+
325
+ /**
326
+ * Get current window size
327
+ *
328
+ * @example
329
+ * const { width, height } = await Aegis.window.getSize();
330
+ */
331
+ getSize: guardedInvoke('window.getSize'),
332
+
333
+ /**
334
+ * Set window position
335
+ *
336
+ * @example
337
+ * await Aegis.window.setPosition({ x: 100, y: 100 });
338
+ */
339
+ setPosition: guardedInvoke('window.setPosition'),
340
+
341
+ /**
342
+ * Get current window position
343
+ *
344
+ * @example
345
+ * const { x, y } = await Aegis.window.getPosition();
346
+ */
347
+ getPosition: guardedInvoke('window.getPosition'),
348
+
349
+ /**
350
+ * Helper: Setup moveBar on an element
351
+ * Makes the specified element a drag handle for the window
352
+ *
353
+ * @param {string|Element} selector - CSS selector or element
354
+ * @param {Object} options - Options
355
+ * @param {string} options.exclude - Selector for elements that should NOT trigger drag
356
+ *
357
+ * @example
358
+ * // Make titlebar draggable, but not buttons
359
+ * Aegis.window.moveBar('#titlebar', { exclude: '.titlebar-btn' });
360
+ */
361
+ moveBar: function (selector, options = {}) {
362
+ const element = typeof selector === 'string'
363
+ ? document.querySelector(selector)
364
+ : selector;
365
+
366
+ if (!element) {
367
+ console.warn('[Aegis] moveBar: Element not found:', selector);
368
+ return;
369
+ }
370
+
371
+ element.addEventListener('mousedown', (e) => {
372
+ // Only left mouse button
373
+ if (e.button !== 0) return;
374
+
375
+ // Check if click is on excluded element
376
+ if (options.exclude && e.target.closest(options.exclude)) {
377
+ console.log('[Aegis] moveBar: excluded element clicked');
378
+ return;
379
+ }
380
+
381
+ console.log('[Aegis] moveBar: starting drag at', e.screenX, e.screenY);
382
+
383
+ // Start window drag with screen coordinates
384
+ Aegis.window.startDrag({
385
+ x: e.screenX,
386
+ y: e.screenY,
387
+ button: e.button + 1 // GTK uses 1-based buttons
388
+ }).then(r => console.log('[Aegis] startDrag result:', r))
389
+ .catch(err => console.error('[Aegis] startDrag error:', err));
390
+ });
391
+
392
+ console.log('[Aegis] moveBar attached to:', selector);
393
+ },
394
+
395
+ /**
396
+ * Helper: Setup resize handles on elements
397
+ *
398
+ * @param {Object} handles - Map of selector to edge
399
+ *
400
+ * @example
401
+ * Aegis.window.resizeHandles({
402
+ * '.resize-n': 'n',
403
+ * '.resize-s': 's',
404
+ * '.resize-e': 'e',
405
+ * '.resize-w': 'w',
406
+ * '.resize-ne': 'ne',
407
+ * '.resize-nw': 'nw',
408
+ * '.resize-se': 'se',
409
+ * '.resize-sw': 'sw'
410
+ * });
411
+ */
412
+ resizeHandles: function (handles) {
413
+ for (const [selector, edge] of Object.entries(handles)) {
414
+ const element = document.querySelector(selector);
415
+ if (element) {
416
+ element.addEventListener('mousedown', (e) => {
417
+ if (e.button !== 0) return;
418
+ Aegis.window.resize({
419
+ edge,
420
+ x: e.screenX,
421
+ y: e.screenY,
422
+ button: e.button + 1
423
+ });
424
+ });
425
+ }
426
+ }
427
+ console.log('[Aegis] Resize handles attached');
428
+ }
429
+ },
430
+
431
+ // ==================== Configuration API ====================
432
+
433
+ /**
434
+ * Expose specific APIs (used in preload.js)
435
+ *
436
+ * @example
437
+ * Aegis.expose(['read', 'write', 'dialog']);
438
+ */
439
+ expose: function (apis) {
440
+ window.__aegisAllowedAPIs = apis;
441
+ },
442
+
443
+ /**
444
+ * Expose all APIs (use with caution!)
445
+ */
446
+ exposeAll: function () {
447
+ window.__aegisAllowedAPIs = ['*'];
448
+ },
449
+
450
+ /**
451
+ * Configure Aegis behavior
452
+ *
453
+ * @example
454
+ * Aegis.config({
455
+ * allowRemoteContent: false,
456
+ * enableDevTools: true
457
+ * });
458
+ */
459
+ config: function (options) {
460
+ window.__aegisConfig = { ...window.__aegisConfig, ...options };
461
+ },
462
+
463
+ /**
464
+ * Register a custom handler (for custom Python actions)
465
+ *
466
+ * @example
467
+ * // In preload.js:
468
+ * Aegis.handle('customAction', async (data) => {
469
+ * return await Aegis.run({ py: `custom_function(${data.value})` });
470
+ * });
471
+ *
472
+ * // In your app:
473
+ * const result = await Aegis.invoke('customAction', { value: 42 });
474
+ */
475
+ handle: function (name, handler) {
476
+ Aegis._customHandlers = Aegis._customHandlers || {};
477
+ Aegis._customHandlers[name] = handler;
478
+ },
479
+
480
+ /**
481
+ * Invoke a custom handler or built-in action
482
+ */
483
+ invoke: async function (action, payload) {
484
+ // Check for custom handler first
485
+ if (Aegis._customHandlers && Aegis._customHandlers[action]) {
486
+ return await Aegis._customHandlers[action](payload);
487
+ }
488
+ // Fall back to built-in
489
+ return invoke(action, payload);
490
+ },
491
+
492
+ // ==================== Utility Functions ====================
493
+
494
+ /**
495
+ * Version of Aegis
496
+ */
497
+ version: '0.1.0',
498
+
499
+ /**
500
+ * Check if running in Aegis environment
501
+ */
502
+ isAegis: function () {
503
+ return !!(window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.aegis);
504
+ }
505
+ };
506
+
507
+ // ==================== Expose to Window ====================
508
+
509
+ // Make Aegis available globally
510
+ window.Aegis = Aegis;
511
+
512
+ // Also expose as module if applicable
513
+ if (typeof module !== 'undefined' && module.exports) {
514
+ module.exports = Aegis;
515
+ }
516
+
517
+ console.log('%c⚡ Aegis v' + Aegis.version + ' loaded', 'color: #00ff88; font-weight: bold;');
518
+
519
+ })();
package/aegis-cli.py ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Aegis CLI Entry Point
4
+
5
+ Run with: python aegis-cli.py [command]
6
+ Or make executable: chmod +x aegis-cli.py && ./aegis-cli.py [command]
7
+ """
8
+
9
+ import sys
10
+ import os
11
+
12
+ # Add aegis to path
13
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
14
+
15
+ from aegis.cli.cli import main
16
+
17
+ if __name__ == '__main__':
18
+ main()
package/bin/aegis ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Aegis CLI - npm wrapper
4
+ *
5
+ * This wrapper calls the Python CLI directly, ensuring proper
6
+ * PYTHONPATH configuration for the installed package.
7
+ */
8
+
9
+ const { spawn } = require('child_process');
10
+ const path = require('path');
11
+ const fs = require('fs');
12
+
13
+ // Get the directory where aegis is installed
14
+ const aegisDir = path.join(__dirname, '..');
15
+ const cliPath = path.join(aegisDir, 'aegis-cli.py');
16
+
17
+ // Check if Python is available
18
+ function getPythonCommand() {
19
+ const pythons = ['python3', 'python'];
20
+ for (const cmd of pythons) {
21
+ try {
22
+ require('child_process').execSync(`${cmd} --version`, { stdio: 'ignore' });
23
+ return cmd;
24
+ } catch (e) {
25
+ continue;
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+
31
+ // Check for system dependencies
32
+ function checkDependencies() {
33
+ const python = getPythonCommand();
34
+ if (!python) {
35
+ console.error('\x1b[31m❌ Python 3 not found!\x1b[0m');
36
+ console.error(' Please install Python 3: sudo apt install python3');
37
+ process.exit(1);
38
+ }
39
+
40
+ // Check for GTK/WebKit on Linux (only for dev/run commands)
41
+ const command = process.argv[2];
42
+ if (['dev', 'run'].includes(command)) {
43
+ try {
44
+ require('child_process').execSync(
45
+ `${python} -c "import gi; gi.require_version('WebKit2', '4.1')"`,
46
+ { stdio: 'ignore' }
47
+ );
48
+ } catch (e) {
49
+ console.error('\x1b[33m⚠️ WebKit2GTK not found!\x1b[0m');
50
+ console.error(' Install with: sudo apt install python3-gi gir1.2-webkit2-4.1');
51
+ console.error('');
52
+ }
53
+ }
54
+
55
+ return python;
56
+ }
57
+
58
+ // Main execution
59
+ const python = checkDependencies();
60
+
61
+ // Set PYTHONPATH to include aegis modules
62
+ const env = { ...process.env };
63
+ env.PYTHONPATH = aegisDir + (env.PYTHONPATH ? `:${env.PYTHONPATH}` : '');
64
+
65
+ // Forward arguments to Python CLI
66
+ const args = [cliPath, ...process.argv.slice(2)];
67
+
68
+ const child = spawn(python, args, {
69
+ stdio: 'inherit',
70
+ env: env,
71
+ cwd: process.cwd()
72
+ });
73
+
74
+ child.on('error', (err) => {
75
+ console.error(`\x1b[31m❌ Failed to start Aegis: ${err.message}\x1b[0m`);
76
+ process.exit(1);
77
+ });
78
+
79
+ child.on('close', (code) => {
80
+ process.exit(code || 0);
81
+ });
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "aegis-framework",
3
+ "version": "0.1.0",
4
+ "description": "Lightweight AppImage framework using WebKit2GTK and Python - An alternative to Electron that creates ~200KB apps instead of 150MB!",
5
+ "keywords": [
6
+ "appimage",
7
+ "electron-alternative",
8
+ "webkit",
9
+ "webkit2gtk",
10
+ "gtk",
11
+ "python",
12
+ "linux",
13
+ "desktop",
14
+ "desktop-app",
15
+ "framework",
16
+ "lightweight"
17
+ ],
18
+ "author": "Diego",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/Diegopam/aegis-framework"
23
+ },
24
+ "homepage": "https://github.com/Diegopam/aegis-framework#readme",
25
+ "bugs": {
26
+ "url": "https://github.com/Diegopam/aegis-framework/issues"
27
+ },
28
+ "bin": {
29
+ "aegis": "./bin/aegis"
30
+ },
31
+ "files": [
32
+ "bin/",
33
+ "aegis/",
34
+ "aegis-cli.py",
35
+ "README.md",
36
+ "LICENSE"
37
+ ],
38
+ "scripts": {
39
+ "test": "echo 'Check dependencies...' && python3 --version",
40
+ "postinstall": "echo '⚡ Aegis installed! Run: aegis init my-app'"
41
+ },
42
+ "os": [
43
+ "linux"
44
+ ],
45
+ "cpu": [
46
+ "x64"
47
+ ],
48
+ "engines": {
49
+ "node": ">=14"
50
+ }
51
+ }