aegis-desktop 0.3.1 → 0.4.1
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 +137 -0
- package/lib/deep-link.js +83 -0
- package/lib/local/engine.js +398 -29
- package/lib/local/tools.js +257 -13
- package/lib/quick-launcher.js +81 -0
- package/lib/settings.js +76 -0
- package/lib/sync/sessions.js +27 -0
- package/lib/window-state.js +63 -0
- package/main.js +918 -16
- package/package.json +8 -4
- package/preload.js +121 -0
- package/renderer/app.js +476 -2
- package/renderer/index.html +49 -0
- package/renderer/markdown.js +165 -0
- package/renderer/quick.css +126 -0
- package/renderer/quick.html +39 -0
- package/renderer/quick.js +188 -0
- package/renderer/style.css +341 -0
- package/renderer/vendor/NOTICE.md +20 -0
- package/renderer/vendor/aegis-highlight.js +135 -0
- package/renderer/vendor/marked.umd.js +80 -0
- package/renderer/vendor/purify.min.js +3 -0
- package/vendor/aegis.js +34 -4
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# AEGIS Desktop
|
|
2
|
+
|
|
3
|
+
A standalone Electron chat app over the [AEGIS](https://aegiscloud.org) API,
|
|
4
|
+
with an **agentic tool loop**: the model can read, write, and edit files,
|
|
5
|
+
list directories, glob, grep, run shell commands in a persistent session, and
|
|
6
|
+
delegate whole sub-tasks to subagents. It does **not** require Claude Code.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install -g aegis-desktop
|
|
10
|
+
aegis
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Model classes
|
|
14
|
+
|
|
15
|
+
Pick any of four transports from the model-class picker, switchable
|
|
16
|
+
mid-conversation with context intact:
|
|
17
|
+
|
|
18
|
+
| Class | Transport | Key held in |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| **Aegis Cloud** | `aegiscloud.org` (pooled or pinned model) | main process |
|
|
21
|
+
| **Ollama** | local `ollama` daemon | no key needed |
|
|
22
|
+
| **Custom OpenAI-compatible** (LM Studio, OpenRouter, vLLM, …) | direct from the desktop app | main process — never sent to the renderer |
|
|
23
|
+
| **Anthropic-compatible** (Claude, or any Messages-format gateway) | direct from the desktop app | main process |
|
|
24
|
+
|
|
25
|
+
Get a free AEGIS key at **https://aegiscloud.org**, or use your own
|
|
26
|
+
Ollama/OpenAI-compatible/Anthropic-compatible endpoint — no AEGIS account
|
|
27
|
+
needed for those.
|
|
28
|
+
|
|
29
|
+
## Tools available to the model
|
|
30
|
+
|
|
31
|
+
| Tool | What it does |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `readFile` · `writeFile` · `editFile` | File access scoped to the working directory |
|
|
34
|
+
| `listDir` · `glob` · `grep` | Navigate and search a tree |
|
|
35
|
+
| `exec` | Run commands in a persistent shell session |
|
|
36
|
+
| `task` | Delegate a self-contained sub-task to a subagent |
|
|
37
|
+
|
|
38
|
+
Before `exec`, `writeFile`, or `editFile` runs, a diff/approval card asks you
|
|
39
|
+
to confirm — approve once, approve for the rest of the conversation, or deny.
|
|
40
|
+
Flip **Settings → "Confirm before running tools"** off if you'd rather the
|
|
41
|
+
agent run mutating tool calls without asking; it's on by default.
|
|
42
|
+
|
|
43
|
+
Conversations persist locally and sync to AEGIS cloud memory via a pending
|
|
44
|
+
queue that flushes on each "Sync now" or heartbeat retry. The **remember**
|
|
45
|
+
button on any assistant reply pins that message to cross-machine memory —
|
|
46
|
+
queued locally if you're offline.
|
|
47
|
+
|
|
48
|
+
## Keyboard shortcuts
|
|
49
|
+
|
|
50
|
+
### In the main window
|
|
51
|
+
|
|
52
|
+
| Shortcut | Action |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `Cmd/Ctrl+N` | New chat |
|
|
55
|
+
| `Cmd/Ctrl+K` | Open search (memory inspector) |
|
|
56
|
+
| `Cmd/Ctrl+S` | Save as… (export the open session as Markdown) |
|
|
57
|
+
| `Cmd/Ctrl+R` | Reload |
|
|
58
|
+
| `Cmd/Ctrl+Shift+I` | Toggle DevTools |
|
|
59
|
+
| `Enter` | Send the composer prompt |
|
|
60
|
+
| `Shift+Enter` | Newline in the composer |
|
|
61
|
+
| `Esc` | Close the memory inspector overlay |
|
|
62
|
+
|
|
63
|
+
### Global quick launcher
|
|
64
|
+
|
|
65
|
+
`Cmd/Ctrl+Shift+Space` (default, configurable in the sidebar's **Quick
|
|
66
|
+
Launcher** card) toggles a small, frameless, always-on-top prompt window near
|
|
67
|
+
your cursor — from anywhere on the desktop, even when AEGIS Desktop isn't the
|
|
68
|
+
focused app. It streams a one-shot answer over the same transport the main
|
|
69
|
+
window uses, with the agent's tool-calling loop turned off (no file/shell
|
|
70
|
+
access, no approval prompts) — just a fast question and an answer.
|
|
71
|
+
|
|
72
|
+
| Shortcut | Action |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `Cmd/Ctrl+Shift+Space` (default, configurable) | Toggle the quick launcher |
|
|
75
|
+
| `Enter` | Ask the typed prompt |
|
|
76
|
+
| `Shift+Enter` | Newline in the prompt |
|
|
77
|
+
| `Cmd/Ctrl+Enter` | Add the current answer to the main window as a new chat turn |
|
|
78
|
+
| `Esc` | Close the quick launcher |
|
|
79
|
+
| *(click away)* | Also closes it — closing never steals focus from whatever window had it before |
|
|
80
|
+
|
|
81
|
+
**Packaged builds** (the installed app) always register the global shortcut.
|
|
82
|
+
**Dev runs** (`npm start` / `electron .`) do not, unless you turn on "enable
|
|
83
|
+
global shortcut" in the sidebar's Quick Launcher card — this keeps a local
|
|
84
|
+
dev session from silently grabbing a systemwide hotkey. If the configured
|
|
85
|
+
accelerator is already claimed by another application, registration fails
|
|
86
|
+
gracefully: a warning is logged to the main process console and the
|
|
87
|
+
Quick Launcher card shows the reason instead of the app crashing or hanging.
|
|
88
|
+
|
|
89
|
+
## Deep links
|
|
90
|
+
|
|
91
|
+
The app registers an `aegis://` protocol handler:
|
|
92
|
+
|
|
93
|
+
| Link | What it does |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `aegis://open?session=<id>` | Resumes a saved session |
|
|
96
|
+
| `aegis://new?prompt=<text>` | Starts a fresh chat with that prompt pre-filled |
|
|
97
|
+
|
|
98
|
+
## Run from source
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
cd desktop
|
|
102
|
+
npm install
|
|
103
|
+
npm start
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Build a distributable
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npm run dist # packaged app (AppImage / MSI+NSIS / dmg)
|
|
110
|
+
npm run dist:dir # unpacked dir, for quick testing
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Checks
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
npm run check # node --check every main-process + renderer file
|
|
117
|
+
node ../test/desktop-shell.mjs # headless IPC smoke test (no Electron binary needed)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Structure
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
main.js Electron main process — window + IPC shell only
|
|
124
|
+
preload.js Context-isolated IPC bridge exposed to the renderer
|
|
125
|
+
renderer/ UI (vanilla JS, no framework)
|
|
126
|
+
lib/local/ Model classes, providers, agentic tool loop, prompt
|
|
127
|
+
lib/sync/ Local session/memory persistence + sync queue
|
|
128
|
+
vendor/aegis.js The AEGIS transport client (thin — no engine logic)
|
|
129
|
+
bin/aegis.js `aegis` CLI entry point for the global npm install
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This directory is part of the [aegiscode-plugin](../README.md) monorepo,
|
|
133
|
+
which also ships a Claude Code plugin and the shared `client/aegis.js`
|
|
134
|
+
transport over the same AEGIS backend — see the repo root for that fuller
|
|
135
|
+
architecture picture. A read-only mirror of just this directory (for
|
|
136
|
+
browsing or `git clone`) lives at
|
|
137
|
+
[aegiscloud/aegiscode-desktop](https://github.com/aegiscloud/aegiscode-desktop).
|
package/lib/deep-link.js
ADDED
|
@@ -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
|
+
};
|