aegis-desktop 0.3.0 → 0.4.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,61 @@
1
+ # AEGIS Desktop
2
+
3
+ Electron host — see the repo root [README.md](../README.md) for the full
4
+ build/architecture reference. This file covers day-to-day usage details that
5
+ don't belong in the top-level doc.
6
+
7
+ ## Keyboard shortcuts
8
+
9
+ ### In the main window
10
+
11
+ | Shortcut | Action |
12
+ |---|---|
13
+ | `Cmd/Ctrl+N` | New chat |
14
+ | `Cmd/Ctrl+K` | Open search (memory inspector) |
15
+ | `Cmd/Ctrl+S` | Save as… (export the open session as Markdown) |
16
+ | `Cmd/Ctrl+R` | Reload |
17
+ | `Cmd/Ctrl+Shift+I` | Toggle DevTools |
18
+ | `Enter` | Send the composer prompt |
19
+ | `Shift+Enter` | Newline in the composer |
20
+ | `Esc` | Close the memory inspector overlay |
21
+
22
+ ### Global quick launcher
23
+
24
+ `Cmd/Ctrl+Shift+Space` (default, configurable in the sidebar's **Quick
25
+ Launcher** card) toggles a small, frameless, always-on-top prompt window near
26
+ your cursor — from anywhere on the desktop, even when AEGIS Desktop isn't the
27
+ focused app. It streams a one-shot answer over the same transport the main
28
+ window uses, with the agent's tool-calling loop turned off (no file/shell
29
+ access, no approval prompts) — just a fast question and an answer.
30
+
31
+ | Shortcut | Action |
32
+ |---|---|
33
+ | `Cmd/Ctrl+Shift+Space` (default, configurable) | Toggle the quick launcher |
34
+ | `Enter` | Ask the typed prompt |
35
+ | `Shift+Enter` | Newline in the prompt |
36
+ | `Cmd/Ctrl+Enter` | Add the current answer to the main window as a new chat turn |
37
+ | `Esc` | Close the quick launcher |
38
+ | *(click away)* | Also closes it — closing never steals focus from whatever window had it before |
39
+
40
+ **Packaged builds** (the installed app) always register the global shortcut.
41
+ **Dev runs** (`npm start` / `electron .`) do not, unless you turn on "enable
42
+ global shortcut" in the sidebar's Quick Launcher card — this keeps a local
43
+ dev session from silently grabbing a systemwide hotkey. If the configured
44
+ accelerator is already claimed by another application, registration fails
45
+ gracefully: a warning is logged to the main process console and the
46
+ Quick Launcher card shows the reason instead of the app crashing or hanging.
47
+
48
+ ## Run from source
49
+
50
+ ```bash
51
+ cd desktop
52
+ npm install
53
+ npm start
54
+ ```
55
+
56
+ ## Checks
57
+
58
+ ```bash
59
+ npm run check # node --check every main-process + renderer file
60
+ node ../test/desktop-shell.mjs # headless IPC smoke test (no Electron binary needed)
61
+ ```
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * deep-link.js — aegis:// URL parsing (native desktop plumbing).
5
+ * Pure functions, no Electron import, so this unit-tests without the
6
+ * Electron binary. main.js wires the result into IPC; this module only
7
+ * turns a raw URL/argv list into `{ action, ... }` or null.
8
+ *
9
+ * Supported shapes:
10
+ * aegis://open?session=<id> -> { action: 'open', sessionId }
11
+ * aegis://new?prompt=<text> -> { action: 'new', prompt }
12
+ */
13
+
14
+ const PROTOCOL = 'aegis';
15
+ const SCHEME_PREFIX = `${PROTOCOL}://`;
16
+
17
+ /** True for any string that looks like our protocol, cheap enough to filter
18
+ * argv with before the full URL parse (which throws on most argv entries —
19
+ * flags, file paths — so callers should not run it against every arg). */
20
+ function isDeepLinkUrl(value) {
21
+ return typeof value === 'string' && value.toLowerCase().startsWith(SCHEME_PREFIX);
22
+ }
23
+
24
+ /**
25
+ * Parse a raw `aegis://...` URL into a routable action, or null for anything
26
+ * unparseable or from a scheme/action this app doesn't recognise. Never
27
+ * throws — a malformed or malicious URL (e.g. handed to the OS by another
28
+ * app) just yields null.
29
+ */
30
+ function parseDeepLinkUrl(url) {
31
+ if (!isDeepLinkUrl(url)) return null;
32
+ let parsed;
33
+ try {
34
+ parsed = new URL(url);
35
+ } catch {
36
+ return null;
37
+ }
38
+ if (parsed.protocol !== `${PROTOCOL}:`) return null;
39
+
40
+ // For a non-special scheme like "aegis:", "//host" still parses into
41
+ // `.hostname` — aegis://open?session=x -> hostname "open".
42
+ const action = (parsed.hostname || '').toLowerCase();
43
+
44
+ if (action === 'open') {
45
+ const sessionId = parsed.searchParams.get('session');
46
+ if (!sessionId) return null;
47
+ return { action: 'open', sessionId };
48
+ }
49
+
50
+ if (action === 'new') {
51
+ return { action: 'new', prompt: parsed.searchParams.get('prompt') || '' };
52
+ }
53
+
54
+ return null;
55
+ }
56
+
57
+ /**
58
+ * Find the deep-link URL among a process's argv, handling the Linux quirk
59
+ * where the OS hands the URL to the app as a bare positional argument (no
60
+ * `--url=` flag, no special marker) — it can land anywhere after the
61
+ * executable/script path, so every entry is checked rather than assuming a
62
+ * fixed index.
63
+ */
64
+ function extractDeepLinkUrl(argv) {
65
+ if (!Array.isArray(argv)) return null;
66
+ for (const arg of argv) {
67
+ if (isDeepLinkUrl(arg)) return arg;
68
+ }
69
+ return null;
70
+ }
71
+
72
+ /** Convenience: argv -> parsed action in one call, or null at either step. */
73
+ function parseDeepLinkArgv(argv) {
74
+ return parseDeepLinkUrl(extractDeepLinkUrl(argv));
75
+ }
76
+
77
+ module.exports = {
78
+ PROTOCOL,
79
+ isDeepLinkUrl,
80
+ parseDeepLinkUrl,
81
+ extractDeepLinkUrl,
82
+ parseDeepLinkArgv,
83
+ };