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 +21 -0
- package/README.md +239 -0
- package/aegis/__init__.py +19 -0
- package/aegis/__pycache__/__init__.cpython-312.pyc +0 -0
- package/aegis/builder/__init__.py +5 -0
- package/aegis/builder/__pycache__/__init__.cpython-312.pyc +0 -0
- package/aegis/builder/__pycache__/builder.cpython-312.pyc +0 -0
- package/aegis/builder/builder.py +301 -0
- package/aegis/cli/__init__.py +5 -0
- package/aegis/cli/__pycache__/__init__.cpython-312.pyc +0 -0
- package/aegis/cli/__pycache__/cli.cpython-312.pyc +0 -0
- package/aegis/cli/cli.py +607 -0
- package/aegis/core/__init__.py +16 -0
- package/aegis/core/__pycache__/__init__.cpython-312.pyc +0 -0
- package/aegis/core/__pycache__/aegis.cpython-312.pyc +0 -0
- package/aegis/core/__pycache__/bridge.cpython-312.pyc +0 -0
- package/aegis/core/__pycache__/window.cpython-312.pyc +0 -0
- package/aegis/core/aegis.py +97 -0
- package/aegis/core/bridge.py +270 -0
- package/aegis/core/preload.py +160 -0
- package/aegis/core/window.py +603 -0
- package/aegis/runtime/__init__.py +1 -0
- package/aegis/runtime/aegis-api.js +519 -0
- package/aegis-cli.py +18 -0
- package/bin/aegis +81 -0
- package/package.json +51 -0
|
@@ -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
|
+
}
|