aegis-desktop 0.8.7 → 0.8.9
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 +6 -0
- package/lib/local/engine.js +37 -31
- package/main.js +25 -1
- package/package.json +5 -4
- package/renderer/fonts/4iCs6KVjbNBYlgoKcQ72j00.woff2 +0 -0
- package/renderer/fonts/4iCs6KVjbNBYlgoKcg72j00.woff2 +0 -0
- package/renderer/fonts/4iCs6KVjbNBYlgoKcw72j00.woff2 +0 -0
- package/renderer/fonts/4iCs6KVjbNBYlgoKew72j00.woff2 +0 -0
- package/renderer/fonts/4iCs6KVjbNBYlgoKfA72j00.woff2 +0 -0
- package/renderer/fonts/4iCs6KVjbNBYlgoKfw72.woff2 +0 -0
- package/renderer/fonts/4iCu6KVjbNBYlgoKej70l0k.woff2 +0 -0
- package/renderer/fonts/4iCu6KVjbNBYlgoKej73l0mwFg.woff2 +0 -0
- package/renderer/fonts/4iCu6KVjbNBYlgoKej74l0mwFg.woff2 +0 -0
- package/renderer/fonts/4iCu6KVjbNBYlgoKej75l0mwFg.woff2 +0 -0
- package/renderer/fonts/4iCu6KVjbNBYlgoKej76l0mwFg.woff2 +0 -0
- package/renderer/fonts/4iCu6KVjbNBYlgoKej7wl0mwFg.woff2 +0 -0
- package/renderer/fonts/4iCv6KVjbNBYlgoCjC3js2yNL4U.woff2 +0 -0
- package/renderer/fonts/4iCv6KVjbNBYlgoCjC3jsGyN.woff2 +0 -0
- package/renderer/fonts/4iCv6KVjbNBYlgoCjC3jtGyNL4U.woff2 +0 -0
- package/renderer/fonts/4iCv6KVjbNBYlgoCjC3jvGyNL4U.woff2 +0 -0
- package/renderer/fonts/4iCv6KVjbNBYlgoCjC3jvWyNL4U.woff2 +0 -0
- package/renderer/fonts/4iCv6KVjbNBYlgoCjC3jvmyNL4U.woff2 +0 -0
- package/renderer/fonts/4iCv6KVjbNBYlgoCxCvjs2yNL4U.woff2 +0 -0
- package/renderer/fonts/4iCv6KVjbNBYlgoCxCvjsGyN.woff2 +0 -0
- package/renderer/fonts/4iCv6KVjbNBYlgoCxCvjtGyNL4U.woff2 +0 -0
- package/renderer/fonts/4iCv6KVjbNBYlgoCxCvjvGyNL4U.woff2 +0 -0
- package/renderer/fonts/4iCv6KVjbNBYlgoCxCvjvWyNL4U.woff2 +0 -0
- package/renderer/fonts/4iCv6KVjbNBYlgoCxCvjvmyNL4U.woff2 +0 -0
- package/renderer/fonts/8vIH7w4qzmVxm25L9Hz_.woff2 +0 -0
- package/renderer/fonts/8vIH7w4qzmVxm2BL9A.woff2 +0 -0
- package/renderer/fonts/8vIH7w4qzmVxm2NL9Hz_.woff2 +0 -0
- package/renderer/fonts/NOTICE.md +57 -0
- package/renderer/fonts/aFTR7PB1QTsUX8KYvrGyEY2tbZX9.woff2 +0 -0
- package/renderer/fonts/aFTR7PB1QTsUX8KYvrGyEYOtbQ.woff2 +0 -0
- package/renderer/fonts/aFTR7PB1QTsUX8KYvumzEY2tbZX9.woff2 +0 -0
- package/renderer/fonts/aFTR7PB1QTsUX8KYvumzEYOtbQ.woff2 +0 -0
- package/renderer/fonts/aFTU7PB1QTsUX8KYthSQBLyM.woff2 +0 -0
- package/renderer/fonts/aFTU7PB1QTsUX8KYthqQBA.woff2 +0 -0
- package/renderer/fonts/aFTW7PB1QTsUX8KYth-gBqSI.woff2 +0 -0
- package/renderer/fonts/aFTW7PB1QTsUX8KYth-gCKSIUKw.woff2 +0 -0
- package/renderer/fonts/fonts.css +368 -0
- package/renderer/index.html +6 -7
- package/renderer/quick.css +28 -3
- package/renderer/quick.html +4 -1
- package/renderer/style.css +14 -2
- package/vendor/aegis.js +16 -1
- package/vendor/auto-provision.js +52 -0
- package/vendor/brain-catalog.js +113 -0
- package/vendor/device-fingerprint.js +63 -0
package/README.md
CHANGED
|
@@ -326,3 +326,9 @@ host over the same engine.
|
|
|
326
326
|
[`aegis-desktop`](https://www.npmjs.com/package/aegis-desktop) is built from a
|
|
327
327
|
`git subtree split` of it — same code, published without the rest of the
|
|
328
328
|
monorepo. Edits land here and are synced out; nothing is authored elsewhere.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
Built by **Niklas Borneklint** — [aegiscloud.org](https://aegiscloud.org) · [@aegisinfo](https://github.com/aegisinfo)
|
|
333
|
+
|
|
334
|
+
Part of the ÆGIS ecosystem.
|
package/lib/local/engine.js
CHANGED
|
@@ -76,6 +76,34 @@ try {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* The shared pooled-brain selection rule (`client/brain-catalog.js`), resolved
|
|
81
|
+
* the same two ways as every other shared module: repo-relative from a source
|
|
82
|
+
* checkout, and this app's own staged tree (`desktop/vendor/brain-catalog.js`,
|
|
83
|
+
* flattened by scripts/predist.mjs because a packaged app cannot reach outside
|
|
84
|
+
* its own directory). The CLI resolves the same file its own way, through
|
|
85
|
+
* `cli/src/sharedpaths.js`. Both hosts must agree on which entry stands for the
|
|
86
|
+
* pool, so there is deliberately no fallback implementation here — a silently
|
|
87
|
+
* divergent local copy is the exact bug this module removes. When neither
|
|
88
|
+
* layout has it, require() throws at load time naming the file, rather than the
|
|
89
|
+
* host quietly offering a different model list than its sibling.
|
|
90
|
+
*/
|
|
91
|
+
function requireSharedBrain() {
|
|
92
|
+
const candidates = [
|
|
93
|
+
() => require('../../../client/brain-catalog.js'),
|
|
94
|
+
() => require('../../vendor/brain-catalog.js'),
|
|
95
|
+
];
|
|
96
|
+
let last = null;
|
|
97
|
+
for (const load of candidates) {
|
|
98
|
+
try {
|
|
99
|
+
return load();
|
|
100
|
+
} catch (err) {
|
|
101
|
+
last = err;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
throw last;
|
|
105
|
+
}
|
|
106
|
+
|
|
79
107
|
/**
|
|
80
108
|
* How long a turn waits for the working-tree lock before running anyway.
|
|
81
109
|
*
|
|
@@ -254,38 +282,16 @@ function normalizeCatalog(models) {
|
|
|
254
282
|
* The pool already auto-routes across whichever providers are live, so the
|
|
255
283
|
* desktop dropdown offers exactly one entry for the "aegis" class: the
|
|
256
284
|
* collapsed "Nexus" brain — never the raw provider list.
|
|
285
|
+
*
|
|
286
|
+
* The selection rule itself now lives in `client/brain-catalog.js`, shared with
|
|
287
|
+
* the CLI: this host and the terminal were offering the same account two
|
|
288
|
+
* different model lists (one collapsed entry vs. every distinct provider id),
|
|
289
|
+
* and two copies of the rule is precisely how that drift happened. Resolved the
|
|
290
|
+
* two ways this repo resolves every shared module — repo-relative in a
|
|
291
|
+
* checkout, and this app's staged vendor/ tree — with the selection itself
|
|
292
|
+
* unchanged.
|
|
257
293
|
*/
|
|
258
|
-
const
|
|
259
|
-
const NEXUS_LABEL = 'Nexus';
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Pick the one entry standing in for the pooled brain. The server serves
|
|
263
|
-
* `nexus-brain` as the canonical tier (`hidden: false`, no `alias_of`) and
|
|
264
|
-
* marks every other spelling — `aegis-brain` and the `-smart`/`-neo` tiers —
|
|
265
|
-
* as `hidden: true, alias_of: "nexus-brain"`. Prefer the canonical id so the
|
|
266
|
-
* request travels on the name the server owns; fall back to the alias, then to
|
|
267
|
-
* any tier whose `alias_of` names the brain, so a renamed or trimmed catalog
|
|
268
|
-
* still resolves to something selectable instead of silently emptying the
|
|
269
|
-
* dropdown (the previous fixed-id lookup returned [] if `aegis-brain` was ever
|
|
270
|
-
* retired, leaving the class with no model to choose).
|
|
271
|
-
*/
|
|
272
|
-
function selectBrainEntry(models) {
|
|
273
|
-
return (
|
|
274
|
-
models.find((m) => NEXUS_BRAIN_IDS.includes(m.id) && !m.hidden && m.alias_of === undefined)
|
|
275
|
-
|| models.find((m) => NEXUS_BRAIN_IDS.includes(m.id))
|
|
276
|
-
|| models.find((m) => m.alias_of && NEXUS_BRAIN_IDS.includes(m.alias_of))
|
|
277
|
-
|| null
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function filterAegisCatalog(models) {
|
|
282
|
-
const nexus = selectBrainEntry(models);
|
|
283
|
-
if (!nexus) return [];
|
|
284
|
-
// Drop the alias bookkeeping: this entry *is* the selection, so the renderer
|
|
285
|
-
// must never treat it as a hidden alias and filter it back out.
|
|
286
|
-
const { hidden, alias_of, ...rest } = nexus;
|
|
287
|
-
return [{ ...rest, label: NEXUS_LABEL }];
|
|
288
|
-
}
|
|
294
|
+
const { filterAegisCatalog } = requireSharedBrain();
|
|
289
295
|
|
|
290
296
|
// ── Agent-loop helpers ──────────────────────────────────────────────────────
|
|
291
297
|
|
package/main.js
CHANGED
|
@@ -47,6 +47,16 @@ try {
|
|
|
47
47
|
credentials = require('./vendor/credentials.js');
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// Same auto-provision helper the MCP plugin and the CLI use (see
|
|
51
|
+
// client/auto-provision.js) — one implementation so all three hosts ask
|
|
52
|
+
// aegis1 for the same account on the same machine, with the same timeout.
|
|
53
|
+
let autoProvision;
|
|
54
|
+
try {
|
|
55
|
+
autoProvision = require('../client/auto-provision.js');
|
|
56
|
+
} catch {
|
|
57
|
+
autoProvision = require('./vendor/auto-provision.js');
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
// The shared `~/.aegiscode/.env` loader (client/env-file.js, staged to
|
|
51
61
|
// ./vendor/env-file.js when packaged). It is what makes the key instruction the
|
|
52
62
|
// same in every host — "put it in ~/.aegiscode/.env" — because AEGIS_API_KEY is
|
|
@@ -2972,7 +2982,7 @@ function bootstrap() {
|
|
|
2972
2982
|
return win;
|
|
2973
2983
|
}
|
|
2974
2984
|
|
|
2975
|
-
app.whenReady().then(() => {
|
|
2985
|
+
app.whenReady().then(async () => {
|
|
2976
2986
|
// Hydrate the client from the persisted key BEFORE the renderer issues any
|
|
2977
2987
|
// status/model call, so class/model dropdowns populate immediately when a
|
|
2978
2988
|
// key was saved in-app (safeStorage is usable only after app ready).
|
|
@@ -2987,6 +2997,20 @@ function bootstrap() {
|
|
|
2987
2997
|
// The shared store is the newer write: bring this host's encrypted copy
|
|
2988
2998
|
// up to date so a CLI rotation does not have to be redone in Settings.
|
|
2989
2999
|
if (chosen.source === 'shared') settings.setAegisKey(chosen.key);
|
|
3000
|
+
} else {
|
|
3001
|
+
// Nothing configured anywhere (env, this app's encrypted copy, or the
|
|
3002
|
+
// shared store): a fresh install. Auto-provision a free device key
|
|
3003
|
+
// from aegis1 — same shared helper mcp/server.js and the CLI use, so
|
|
3004
|
+
// the desktop app works with no manual sign-in step. It starts at
|
|
3005
|
+
// zero token-bank balance server-side (register-device), and pooled
|
|
3006
|
+
// aegiscloud models additionally require an upgraded plan (aegis1's
|
|
3007
|
+
// is_pro() gate) — this only unblocks BYOK and account status/
|
|
3008
|
+
// settings until the account is funded and/or upgraded. Failure
|
|
3009
|
+
// (offline, aegis1 unreachable) falls through exactly as before: the
|
|
3010
|
+
// renderer's normal "not signed in" state handles it, and the next
|
|
3011
|
+
// launch tries again.
|
|
3012
|
+
const result = await autoProvision.autoProvisionApiKey(aegis, credentials);
|
|
3013
|
+
if (result.key) settings.setAegisKey(result.key);
|
|
2990
3014
|
}
|
|
2991
3015
|
}
|
|
2992
3016
|
// Apply whatever was last saved (or the default): ship the shortcut only
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aegis-desktop",
|
|
3
3
|
"productName": "AEGIS Desktop",
|
|
4
|
-
"version": "0.8.
|
|
5
|
-
"description": "Thin Electron host for AEGIS
|
|
4
|
+
"version": "0.8.9",
|
|
5
|
+
"description": "Thin Electron host for AEGIS — a local chat UI over the shared client/aegis.js transport. Ships transport + UI only; engine logic stays server-side.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "AEGIS Code",
|
|
8
8
|
"email": "nborneklint@gmail.com"
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"demo": "node scripts/record-demo-gif.mjs",
|
|
33
33
|
"dist": "npm run predist && electron-builder",
|
|
34
34
|
"dist:dir": "npm run predist && electron-builder --dir",
|
|
35
|
-
"check": "node --check main.js && node --check preload.js && node --check renderer/app.js && node --check renderer/quick.js && node --check renderer/budget.js && node --check renderer/usage.js && node --check renderer/stream-policy.js && node --check renderer/transcript-view.js && node --check renderer/diff.js && node --check renderer/diffview.js && node --check renderer/markdown.js && node --check renderer/vendor/aegis-highlight.js && node --check scripts/predist.mjs && node --check scripts/generate-icon.mjs && node --check lib/local/local.js && node --check lib/local/context.js && node --check lib/local/engine.js && node --check lib/local/session-rounds.js && node --check lib/local/tools.js && node --check lib/local/prompt.js && node --check lib/local/shell.js && node --check lib/local/agents.js && node --check lib/local/queue.js && node --check lib/local/autonomous.js && node --check lib/settings.js && node --check lib/sync/sessions.js && node --check lib/sync/memory-queue.js && node --check lib/window-state.js && node --check lib/deep-link.js && node --check lib/quick-launcher.js && node --check lib/avatar/tiers.js && node --check lib/avatar/xp.js && node --check lib/avatar/level.js && node --check lib/avatar/events.js && node --check lib/avatar/persona.js && node --check bin/aegis.js",
|
|
35
|
+
"check": "node --check main.js && node --check preload.js && node --check renderer/app.js && node --check renderer/quick.js && node --check renderer/budget.js && node --check renderer/usage.js && node --check renderer/stream-policy.js && node --check renderer/transcript-view.js && node --check renderer/diff.js && node --check renderer/diffview.js && node --check renderer/markdown.js && node --check renderer/vendor/aegis-highlight.js && node --check scripts/predist.mjs && node --check scripts/generate-icon.mjs && node --check scripts/bundle-fonts.mjs && node --check lib/local/local.js && node --check lib/local/context.js && node --check lib/local/engine.js && node --check lib/local/session-rounds.js && node --check lib/local/tools.js && node --check lib/local/prompt.js && node --check lib/local/shell.js && node --check lib/local/agents.js && node --check lib/local/queue.js && node --check lib/local/autonomous.js && node --check lib/settings.js && node --check lib/sync/sessions.js && node --check lib/sync/memory-queue.js && node --check lib/window-state.js && node --check lib/deep-link.js && node --check lib/quick-launcher.js && node --check lib/avatar/tiers.js && node --check lib/avatar/xp.js && node --check lib/avatar/level.js && node --check lib/avatar/events.js && node --check lib/avatar/persona.js && node --check bin/aegis.js",
|
|
36
36
|
"test:shell": "node ../test/desktop-shell.mjs",
|
|
37
37
|
"test:model": "node ../test/model-dispatch.mjs",
|
|
38
38
|
"test:budget": "node ../test/budget.test.mjs",
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
"test:avatar-level": "node test/avatar-level.test.mjs",
|
|
53
53
|
"test:avatar-events": "node test/avatar-events.test.mjs",
|
|
54
54
|
"test:avatar-tiers": "node test/avatar-tiers.test.mjs",
|
|
55
|
-
"test:avatar-persona": "node test/avatar-persona.test.mjs"
|
|
55
|
+
"test:avatar-persona": "node test/avatar-persona.test.mjs",
|
|
56
|
+
"test:renderer-fonts": "node test/renderer-fonts.test.mjs"
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|
|
58
59
|
"electron-updater": "^6.8.9"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Vendored webfonts
|
|
2
|
+
|
|
3
|
+
Committed verbatim, like `renderer/vendor/` — no CDN and no network at run time.
|
|
4
|
+
Loaded by `renderer/index.html` and `renderer/quick.html` under a
|
|
5
|
+
`font-src 'self'` CSP.
|
|
6
|
+
|
|
7
|
+
Generated by `desktop/scripts/bundle-fonts.mjs`; do not edit the `.woff2` files
|
|
8
|
+
or `fonts.css` by hand. To refresh the set:
|
|
9
|
+
|
|
10
|
+
node desktop/scripts/bundle-fonts.mjs # rewrite
|
|
11
|
+
node desktop/scripts/bundle-fonts.mjs --check # verify against the CDN (needs network)
|
|
12
|
+
|
|
13
|
+
All three families are licensed under the **SIL Open Font License 1.1**, which
|
|
14
|
+
permits bundling and redistribution. The copyright lines Google Fonts serves
|
|
15
|
+
with them:
|
|
16
|
+
|
|
17
|
+
- `Syne` — © 2018 The Syne Project Authors
|
|
18
|
+
(<https://github.com/bonjour-monde/fonderie>), OFL 1.1. Display face used for
|
|
19
|
+
the wordmark, headings and UI chrome (`--font-display`).
|
|
20
|
+
- `DM Mono` — © 2020 The DM Mono Project Authors
|
|
21
|
+
(<https://github.com/googlefonts/dm-mono>), OFL 1.1. Monospace face for code,
|
|
22
|
+
diffs and metrics (`--font-mono`).
|
|
23
|
+
- `Ubuntu` — © 2010-2011 Canonical Ltd.
|
|
24
|
+
(<https://github.com/googlefonts/UbuntuFont>), OFL 1.1. Chat body text
|
|
25
|
+
(`--font-chat`).
|
|
26
|
+
|
|
27
|
+
The OFL is a per-file licence; the full text ships with each upstream project at
|
|
28
|
+
the links above and is not duplicated here.
|
|
29
|
+
|
|
30
|
+
## Why these are vendored rather than fetched
|
|
31
|
+
|
|
32
|
+
The `<link>` to `fonts.googleapis.com` that used to sit in `index.html` made the
|
|
33
|
+
app's typography depend on the network and on the host. Online it drew the three
|
|
34
|
+
faces above. Offline — or behind a blocker, or on a machine whose font resolver
|
|
35
|
+
disagreed — the fallback chain `'Segoe UI', system-ui, sans-serif` resolved to a
|
|
36
|
+
*different* font on each platform: SF Pro on macOS, Segoe UI on Windows, whatever
|
|
37
|
+
fontconfig offered on Linux. Three visibly different products from one stylesheet.
|
|
38
|
+
|
|
39
|
+
Serving the same bytes from the app directory makes the rendering identical on
|
|
40
|
+
every platform and lets the CSP drop its external origins entirely. This is the
|
|
41
|
+
desktop counterpart of the terminal-side policy in `cli/src/caps.js`, which
|
|
42
|
+
likewise refuses to let the host change what is drawn.
|
|
43
|
+
|
|
44
|
+
Note the honest limits. These three families cover latin, latin-ext, cyrillic,
|
|
45
|
+
cyrillic-ext, greek and greek-ext — and **not** vietnamese: Google Fonts serves
|
|
46
|
+
no vietnamese subset for Syne, DM Mono or Ubuntu, and `latin-ext` stops at
|
|
47
|
+
U+1E9F and resumes at U+1EF2, so the core Vietnamese diacritic block
|
|
48
|
+
U+1EA0–U+1EF1 falls through to a host font. `desktop/scripts/bundle-fonts.mjs`
|
|
49
|
+
records the same gap, and `desktop/test/renderer-fonts.test.mjs` asserts it, so
|
|
50
|
+
the day Google adds the subset the test fails loudly instead of the gap
|
|
51
|
+
silently persisting.
|
|
52
|
+
|
|
53
|
+
Glyphs outside the covered set — CJK, and colour emoji in particular — are also
|
|
54
|
+
still drawn by a host font and remain platform-specific. `Apple Color Emoji`,
|
|
55
|
+
`Segoe UI Emoji` and `Noto Color Emoji` are not interchangeable and are not
|
|
56
|
+
redistributable here, so that divergence cannot be removed by bundling; it is
|
|
57
|
+
inherent to the platform.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/* Vendored webfonts — DO NOT EDIT BY HAND.
|
|
2
|
+
*
|
|
3
|
+
* Generated by desktop/scripts/bundle-fonts.mjs from the Google Fonts CSS for
|
|
4
|
+
* Syne (400/600/700/800), DM Mono (300/400/500 + italic 400) and Ubuntu
|
|
5
|
+
* (400/500/700 + italic 400) — the exact families renderer/index.html used to
|
|
6
|
+
* request from fonts.googleapis.com at run time.
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS IS VENDORED. A CDN <link> made the app's type depend on the network
|
|
9
|
+
* and on the host: online it drew Syne/DM Mono/Ubuntu, offline (or behind a
|
|
10
|
+
* blocker) it silently fell through to 'Segoe UI' on Windows, system-ui/SF on
|
|
11
|
+
* macOS and whatever fontconfig produced on Linux — three different-looking
|
|
12
|
+
* apps from one stylesheet. Serving the same bytes from the app directory makes
|
|
13
|
+
* the rendering identical everywhere and offline-capable. Licences are OFL 1.1;
|
|
14
|
+
* see NOTICE.md in this directory.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
@font-face {
|
|
18
|
+
font-family: 'DM Mono';
|
|
19
|
+
font-style: italic;
|
|
20
|
+
font-weight: 400;
|
|
21
|
+
font-display: swap;
|
|
22
|
+
src: url(./aFTW7PB1QTsUX8KYth-gCKSIUKw.woff2) format('woff2');
|
|
23
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
24
|
+
}
|
|
25
|
+
@font-face {
|
|
26
|
+
font-family: 'DM Mono';
|
|
27
|
+
font-style: italic;
|
|
28
|
+
font-weight: 400;
|
|
29
|
+
font-display: swap;
|
|
30
|
+
src: url(./aFTW7PB1QTsUX8KYth-gBqSI.woff2) format('woff2');
|
|
31
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
32
|
+
}
|
|
33
|
+
@font-face {
|
|
34
|
+
font-family: 'DM Mono';
|
|
35
|
+
font-style: normal;
|
|
36
|
+
font-weight: 300;
|
|
37
|
+
font-display: swap;
|
|
38
|
+
src: url(./aFTR7PB1QTsUX8KYvrGyEY2tbZX9.woff2) format('woff2');
|
|
39
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
40
|
+
}
|
|
41
|
+
@font-face {
|
|
42
|
+
font-family: 'DM Mono';
|
|
43
|
+
font-style: normal;
|
|
44
|
+
font-weight: 300;
|
|
45
|
+
font-display: swap;
|
|
46
|
+
src: url(./aFTR7PB1QTsUX8KYvrGyEYOtbQ.woff2) format('woff2');
|
|
47
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
48
|
+
}
|
|
49
|
+
@font-face {
|
|
50
|
+
font-family: 'DM Mono';
|
|
51
|
+
font-style: normal;
|
|
52
|
+
font-weight: 400;
|
|
53
|
+
font-display: swap;
|
|
54
|
+
src: url(./aFTU7PB1QTsUX8KYthSQBLyM.woff2) format('woff2');
|
|
55
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
56
|
+
}
|
|
57
|
+
@font-face {
|
|
58
|
+
font-family: 'DM Mono';
|
|
59
|
+
font-style: normal;
|
|
60
|
+
font-weight: 400;
|
|
61
|
+
font-display: swap;
|
|
62
|
+
src: url(./aFTU7PB1QTsUX8KYthqQBA.woff2) format('woff2');
|
|
63
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
64
|
+
}
|
|
65
|
+
@font-face {
|
|
66
|
+
font-family: 'DM Mono';
|
|
67
|
+
font-style: normal;
|
|
68
|
+
font-weight: 500;
|
|
69
|
+
font-display: swap;
|
|
70
|
+
src: url(./aFTR7PB1QTsUX8KYvumzEY2tbZX9.woff2) format('woff2');
|
|
71
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
72
|
+
}
|
|
73
|
+
@font-face {
|
|
74
|
+
font-family: 'DM Mono';
|
|
75
|
+
font-style: normal;
|
|
76
|
+
font-weight: 500;
|
|
77
|
+
font-display: swap;
|
|
78
|
+
src: url(./aFTR7PB1QTsUX8KYvumzEYOtbQ.woff2) format('woff2');
|
|
79
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
80
|
+
}
|
|
81
|
+
@font-face {
|
|
82
|
+
font-family: 'Syne';
|
|
83
|
+
font-style: normal;
|
|
84
|
+
font-weight: 400;
|
|
85
|
+
font-display: swap;
|
|
86
|
+
src: url(./8vIH7w4qzmVxm2NL9Hz_.woff2) format('woff2');
|
|
87
|
+
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
|
88
|
+
}
|
|
89
|
+
@font-face {
|
|
90
|
+
font-family: 'Syne';
|
|
91
|
+
font-style: normal;
|
|
92
|
+
font-weight: 400;
|
|
93
|
+
font-display: swap;
|
|
94
|
+
src: url(./8vIH7w4qzmVxm25L9Hz_.woff2) format('woff2');
|
|
95
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
96
|
+
}
|
|
97
|
+
@font-face {
|
|
98
|
+
font-family: 'Syne';
|
|
99
|
+
font-style: normal;
|
|
100
|
+
font-weight: 400;
|
|
101
|
+
font-display: swap;
|
|
102
|
+
src: url(./8vIH7w4qzmVxm2BL9A.woff2) format('woff2');
|
|
103
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
104
|
+
}
|
|
105
|
+
@font-face {
|
|
106
|
+
font-family: 'Syne';
|
|
107
|
+
font-style: normal;
|
|
108
|
+
font-weight: 600;
|
|
109
|
+
font-display: swap;
|
|
110
|
+
src: url(./8vIH7w4qzmVxm2NL9Hz_.woff2) format('woff2');
|
|
111
|
+
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
|
112
|
+
}
|
|
113
|
+
@font-face {
|
|
114
|
+
font-family: 'Syne';
|
|
115
|
+
font-style: normal;
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
font-display: swap;
|
|
118
|
+
src: url(./8vIH7w4qzmVxm25L9Hz_.woff2) format('woff2');
|
|
119
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
120
|
+
}
|
|
121
|
+
@font-face {
|
|
122
|
+
font-family: 'Syne';
|
|
123
|
+
font-style: normal;
|
|
124
|
+
font-weight: 600;
|
|
125
|
+
font-display: swap;
|
|
126
|
+
src: url(./8vIH7w4qzmVxm2BL9A.woff2) format('woff2');
|
|
127
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
128
|
+
}
|
|
129
|
+
@font-face {
|
|
130
|
+
font-family: 'Syne';
|
|
131
|
+
font-style: normal;
|
|
132
|
+
font-weight: 700;
|
|
133
|
+
font-display: swap;
|
|
134
|
+
src: url(./8vIH7w4qzmVxm2NL9Hz_.woff2) format('woff2');
|
|
135
|
+
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
|
136
|
+
}
|
|
137
|
+
@font-face {
|
|
138
|
+
font-family: 'Syne';
|
|
139
|
+
font-style: normal;
|
|
140
|
+
font-weight: 700;
|
|
141
|
+
font-display: swap;
|
|
142
|
+
src: url(./8vIH7w4qzmVxm25L9Hz_.woff2) format('woff2');
|
|
143
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
144
|
+
}
|
|
145
|
+
@font-face {
|
|
146
|
+
font-family: 'Syne';
|
|
147
|
+
font-style: normal;
|
|
148
|
+
font-weight: 700;
|
|
149
|
+
font-display: swap;
|
|
150
|
+
src: url(./8vIH7w4qzmVxm2BL9A.woff2) format('woff2');
|
|
151
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
152
|
+
}
|
|
153
|
+
@font-face {
|
|
154
|
+
font-family: 'Syne';
|
|
155
|
+
font-style: normal;
|
|
156
|
+
font-weight: 800;
|
|
157
|
+
font-display: swap;
|
|
158
|
+
src: url(./8vIH7w4qzmVxm2NL9Hz_.woff2) format('woff2');
|
|
159
|
+
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
|
160
|
+
}
|
|
161
|
+
@font-face {
|
|
162
|
+
font-family: 'Syne';
|
|
163
|
+
font-style: normal;
|
|
164
|
+
font-weight: 800;
|
|
165
|
+
font-display: swap;
|
|
166
|
+
src: url(./8vIH7w4qzmVxm25L9Hz_.woff2) format('woff2');
|
|
167
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
168
|
+
}
|
|
169
|
+
@font-face {
|
|
170
|
+
font-family: 'Syne';
|
|
171
|
+
font-style: normal;
|
|
172
|
+
font-weight: 800;
|
|
173
|
+
font-display: swap;
|
|
174
|
+
src: url(./8vIH7w4qzmVxm2BL9A.woff2) format('woff2');
|
|
175
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
176
|
+
}
|
|
177
|
+
@font-face {
|
|
178
|
+
font-family: 'Ubuntu';
|
|
179
|
+
font-style: italic;
|
|
180
|
+
font-weight: 400;
|
|
181
|
+
font-display: swap;
|
|
182
|
+
src: url(./4iCu6KVjbNBYlgoKej75l0mwFg.woff2) format('woff2');
|
|
183
|
+
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
|
184
|
+
}
|
|
185
|
+
@font-face {
|
|
186
|
+
font-family: 'Ubuntu';
|
|
187
|
+
font-style: italic;
|
|
188
|
+
font-weight: 400;
|
|
189
|
+
font-display: swap;
|
|
190
|
+
src: url(./4iCu6KVjbNBYlgoKej7wl0mwFg.woff2) format('woff2');
|
|
191
|
+
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
192
|
+
}
|
|
193
|
+
@font-face {
|
|
194
|
+
font-family: 'Ubuntu';
|
|
195
|
+
font-style: italic;
|
|
196
|
+
font-weight: 400;
|
|
197
|
+
font-display: swap;
|
|
198
|
+
src: url(./4iCu6KVjbNBYlgoKej74l0mwFg.woff2) format('woff2');
|
|
199
|
+
unicode-range: U+1F00-1FFF;
|
|
200
|
+
}
|
|
201
|
+
@font-face {
|
|
202
|
+
font-family: 'Ubuntu';
|
|
203
|
+
font-style: italic;
|
|
204
|
+
font-weight: 400;
|
|
205
|
+
font-display: swap;
|
|
206
|
+
src: url(./4iCu6KVjbNBYlgoKej73l0mwFg.woff2) format('woff2');
|
|
207
|
+
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
|
208
|
+
}
|
|
209
|
+
@font-face {
|
|
210
|
+
font-family: 'Ubuntu';
|
|
211
|
+
font-style: italic;
|
|
212
|
+
font-weight: 400;
|
|
213
|
+
font-display: swap;
|
|
214
|
+
src: url(./4iCu6KVjbNBYlgoKej76l0mwFg.woff2) format('woff2');
|
|
215
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
216
|
+
}
|
|
217
|
+
@font-face {
|
|
218
|
+
font-family: 'Ubuntu';
|
|
219
|
+
font-style: italic;
|
|
220
|
+
font-weight: 400;
|
|
221
|
+
font-display: swap;
|
|
222
|
+
src: url(./4iCu6KVjbNBYlgoKej70l0k.woff2) format('woff2');
|
|
223
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
224
|
+
}
|
|
225
|
+
@font-face {
|
|
226
|
+
font-family: 'Ubuntu';
|
|
227
|
+
font-style: normal;
|
|
228
|
+
font-weight: 400;
|
|
229
|
+
font-display: swap;
|
|
230
|
+
src: url(./4iCs6KVjbNBYlgoKcg72j00.woff2) format('woff2');
|
|
231
|
+
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
|
232
|
+
}
|
|
233
|
+
@font-face {
|
|
234
|
+
font-family: 'Ubuntu';
|
|
235
|
+
font-style: normal;
|
|
236
|
+
font-weight: 400;
|
|
237
|
+
font-display: swap;
|
|
238
|
+
src: url(./4iCs6KVjbNBYlgoKew72j00.woff2) format('woff2');
|
|
239
|
+
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
240
|
+
}
|
|
241
|
+
@font-face {
|
|
242
|
+
font-family: 'Ubuntu';
|
|
243
|
+
font-style: normal;
|
|
244
|
+
font-weight: 400;
|
|
245
|
+
font-display: swap;
|
|
246
|
+
src: url(./4iCs6KVjbNBYlgoKcw72j00.woff2) format('woff2');
|
|
247
|
+
unicode-range: U+1F00-1FFF;
|
|
248
|
+
}
|
|
249
|
+
@font-face {
|
|
250
|
+
font-family: 'Ubuntu';
|
|
251
|
+
font-style: normal;
|
|
252
|
+
font-weight: 400;
|
|
253
|
+
font-display: swap;
|
|
254
|
+
src: url(./4iCs6KVjbNBYlgoKfA72j00.woff2) format('woff2');
|
|
255
|
+
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
|
256
|
+
}
|
|
257
|
+
@font-face {
|
|
258
|
+
font-family: 'Ubuntu';
|
|
259
|
+
font-style: normal;
|
|
260
|
+
font-weight: 400;
|
|
261
|
+
font-display: swap;
|
|
262
|
+
src: url(./4iCs6KVjbNBYlgoKcQ72j00.woff2) format('woff2');
|
|
263
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
264
|
+
}
|
|
265
|
+
@font-face {
|
|
266
|
+
font-family: 'Ubuntu';
|
|
267
|
+
font-style: normal;
|
|
268
|
+
font-weight: 400;
|
|
269
|
+
font-display: swap;
|
|
270
|
+
src: url(./4iCs6KVjbNBYlgoKfw72.woff2) format('woff2');
|
|
271
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
272
|
+
}
|
|
273
|
+
@font-face {
|
|
274
|
+
font-family: 'Ubuntu';
|
|
275
|
+
font-style: normal;
|
|
276
|
+
font-weight: 500;
|
|
277
|
+
font-display: swap;
|
|
278
|
+
src: url(./4iCv6KVjbNBYlgoCjC3jvWyNL4U.woff2) format('woff2');
|
|
279
|
+
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
|
280
|
+
}
|
|
281
|
+
@font-face {
|
|
282
|
+
font-family: 'Ubuntu';
|
|
283
|
+
font-style: normal;
|
|
284
|
+
font-weight: 500;
|
|
285
|
+
font-display: swap;
|
|
286
|
+
src: url(./4iCv6KVjbNBYlgoCjC3jtGyNL4U.woff2) format('woff2');
|
|
287
|
+
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
288
|
+
}
|
|
289
|
+
@font-face {
|
|
290
|
+
font-family: 'Ubuntu';
|
|
291
|
+
font-style: normal;
|
|
292
|
+
font-weight: 500;
|
|
293
|
+
font-display: swap;
|
|
294
|
+
src: url(./4iCv6KVjbNBYlgoCjC3jvGyNL4U.woff2) format('woff2');
|
|
295
|
+
unicode-range: U+1F00-1FFF;
|
|
296
|
+
}
|
|
297
|
+
@font-face {
|
|
298
|
+
font-family: 'Ubuntu';
|
|
299
|
+
font-style: normal;
|
|
300
|
+
font-weight: 500;
|
|
301
|
+
font-display: swap;
|
|
302
|
+
src: url(./4iCv6KVjbNBYlgoCjC3js2yNL4U.woff2) format('woff2');
|
|
303
|
+
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
|
304
|
+
}
|
|
305
|
+
@font-face {
|
|
306
|
+
font-family: 'Ubuntu';
|
|
307
|
+
font-style: normal;
|
|
308
|
+
font-weight: 500;
|
|
309
|
+
font-display: swap;
|
|
310
|
+
src: url(./4iCv6KVjbNBYlgoCjC3jvmyNL4U.woff2) format('woff2');
|
|
311
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
312
|
+
}
|
|
313
|
+
@font-face {
|
|
314
|
+
font-family: 'Ubuntu';
|
|
315
|
+
font-style: normal;
|
|
316
|
+
font-weight: 500;
|
|
317
|
+
font-display: swap;
|
|
318
|
+
src: url(./4iCv6KVjbNBYlgoCjC3jsGyN.woff2) format('woff2');
|
|
319
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
320
|
+
}
|
|
321
|
+
@font-face {
|
|
322
|
+
font-family: 'Ubuntu';
|
|
323
|
+
font-style: normal;
|
|
324
|
+
font-weight: 700;
|
|
325
|
+
font-display: swap;
|
|
326
|
+
src: url(./4iCv6KVjbNBYlgoCxCvjvWyNL4U.woff2) format('woff2');
|
|
327
|
+
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
|
328
|
+
}
|
|
329
|
+
@font-face {
|
|
330
|
+
font-family: 'Ubuntu';
|
|
331
|
+
font-style: normal;
|
|
332
|
+
font-weight: 700;
|
|
333
|
+
font-display: swap;
|
|
334
|
+
src: url(./4iCv6KVjbNBYlgoCxCvjtGyNL4U.woff2) format('woff2');
|
|
335
|
+
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
336
|
+
}
|
|
337
|
+
@font-face {
|
|
338
|
+
font-family: 'Ubuntu';
|
|
339
|
+
font-style: normal;
|
|
340
|
+
font-weight: 700;
|
|
341
|
+
font-display: swap;
|
|
342
|
+
src: url(./4iCv6KVjbNBYlgoCxCvjvGyNL4U.woff2) format('woff2');
|
|
343
|
+
unicode-range: U+1F00-1FFF;
|
|
344
|
+
}
|
|
345
|
+
@font-face {
|
|
346
|
+
font-family: 'Ubuntu';
|
|
347
|
+
font-style: normal;
|
|
348
|
+
font-weight: 700;
|
|
349
|
+
font-display: swap;
|
|
350
|
+
src: url(./4iCv6KVjbNBYlgoCxCvjs2yNL4U.woff2) format('woff2');
|
|
351
|
+
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
|
352
|
+
}
|
|
353
|
+
@font-face {
|
|
354
|
+
font-family: 'Ubuntu';
|
|
355
|
+
font-style: normal;
|
|
356
|
+
font-weight: 700;
|
|
357
|
+
font-display: swap;
|
|
358
|
+
src: url(./4iCv6KVjbNBYlgoCxCvjvmyNL4U.woff2) format('woff2');
|
|
359
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
360
|
+
}
|
|
361
|
+
@font-face {
|
|
362
|
+
font-family: 'Ubuntu';
|
|
363
|
+
font-style: normal;
|
|
364
|
+
font-weight: 700;
|
|
365
|
+
font-display: swap;
|
|
366
|
+
src: url(./4iCv6KVjbNBYlgoCxCvjsGyN.woff2) format('woff2');
|
|
367
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
368
|
+
}
|
package/renderer/index.html
CHANGED
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
|
+
<!-- No external origins. The UI fonts are vendored in ./fonts/, so the window
|
|
6
|
+
draws the same faces on every platform with the network down; see
|
|
7
|
+
fonts/NOTICE.md. `font-src 'self'` is what allows them, and is why the
|
|
8
|
+
old fonts.googleapis.com / fonts.gstatic.com allowances are gone. -->
|
|
5
9
|
<meta
|
|
6
10
|
http-equiv="Content-Security-Policy"
|
|
7
|
-
content="default-src 'none'; script-src 'self'; style-src 'self'
|
|
11
|
+
content="default-src 'none'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self' data:"
|
|
8
12
|
/>
|
|
9
13
|
<title>AEGIS Desktop</title>
|
|
10
|
-
<link rel="
|
|
11
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
12
|
-
<link
|
|
13
|
-
href="https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,400&family=Syne:wght@400;600;700;800&family=Ubuntu:ital,wght@0,400;0,500;0,700;1,400&display=swap"
|
|
14
|
-
rel="stylesheet"
|
|
15
|
-
/>
|
|
14
|
+
<link rel="stylesheet" href="fonts/fonts.css" />
|
|
16
15
|
<link rel="stylesheet" href="style.css" />
|
|
17
16
|
</head>
|
|
18
17
|
<body>
|
package/renderer/quick.css
CHANGED
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
--text2: #7e9ab8;
|
|
7
7
|
--teal: #00e5c0;
|
|
8
8
|
--red: #ff1744;
|
|
9
|
+
|
|
10
|
+
/* Same deterministic stacks as style.css, and for the same reason: the three
|
|
11
|
+
named families are vendored in renderer/fonts/ (loaded by quick.html before
|
|
12
|
+
this file), so the launcher resolves to the same bytes on every platform.
|
|
13
|
+
|
|
14
|
+
This file is `-apple-system, 'Segoe UI', system-ui` no more. As the *only*
|
|
15
|
+
stack it made the launcher the last place in the app where the host still
|
|
16
|
+
decided what was drawn — SF Pro on macOS, Segoe UI on Windows, fontconfig's
|
|
17
|
+
pick on Linux — which is exactly the divergence the vendoring removes. */
|
|
18
|
+
--font-display: 'Syne', sans-serif;
|
|
19
|
+
--font-mono: 'DM Mono', monospace;
|
|
20
|
+
--font-chat: 'Ubuntu', sans-serif;
|
|
9
21
|
}
|
|
10
22
|
|
|
11
23
|
* {
|
|
@@ -21,7 +33,7 @@ body {
|
|
|
21
33
|
margin: 0;
|
|
22
34
|
background: transparent;
|
|
23
35
|
color: var(--text);
|
|
24
|
-
font: 14px/1.4 -
|
|
36
|
+
font: 14px/1.4 var(--font-chat);
|
|
25
37
|
}
|
|
26
38
|
|
|
27
39
|
.ql-shell {
|
|
@@ -45,13 +57,20 @@ body {
|
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
.ql-badge {
|
|
60
|
+
font-family: var(--font-display);
|
|
48
61
|
font-weight: 700;
|
|
49
62
|
letter-spacing: 0.04em;
|
|
50
63
|
color: var(--teal);
|
|
51
64
|
}
|
|
52
65
|
|
|
53
|
-
|
|
54
|
-
|
|
66
|
+
/* Form controls do NOT inherit type from their parent — browsers give them the
|
|
67
|
+
platform widget font — so each one here names its family. Left implicit, the
|
|
68
|
+
launcher's own controls were the last place the host decided what was drawn. */
|
|
69
|
+
.ql-model-select,
|
|
70
|
+
.ql-stop,
|
|
71
|
+
.ql-push,
|
|
72
|
+
.ql-prompt {
|
|
73
|
+
font-family: var(--font-chat);
|
|
55
74
|
}
|
|
56
75
|
|
|
57
76
|
.ql-model-select {
|
|
@@ -63,6 +82,11 @@ body {
|
|
|
63
82
|
padding: 2px 4px;
|
|
64
83
|
}
|
|
65
84
|
|
|
85
|
+
.ql-model {
|
|
86
|
+
color: var(--text2);
|
|
87
|
+
font-family: var(--font-mono);
|
|
88
|
+
}
|
|
89
|
+
|
|
66
90
|
.ql-spacer {
|
|
67
91
|
flex: 1 1 auto;
|
|
68
92
|
}
|
|
@@ -121,6 +145,7 @@ body {
|
|
|
121
145
|
display: flex;
|
|
122
146
|
align-items: center;
|
|
123
147
|
gap: 8px;
|
|
148
|
+
font-family: var(--font-mono);
|
|
124
149
|
font-size: 11px;
|
|
125
150
|
color: var(--text2);
|
|
126
151
|
}
|
package/renderer/quick.html
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
|
+
<!-- Same policy as index.html: no external origins, fonts vendored locally so
|
|
6
|
+
the quick bar looks identical on every platform. See fonts/NOTICE.md. -->
|
|
5
7
|
<meta
|
|
6
8
|
http-equiv="Content-Security-Policy"
|
|
7
|
-
content="default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:"
|
|
9
|
+
content="default-src 'none'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self' data:"
|
|
8
10
|
/>
|
|
9
11
|
<title>AEGIS Quick Launcher</title>
|
|
12
|
+
<link rel="stylesheet" href="fonts/fonts.css" />
|
|
10
13
|
<link rel="stylesheet" href="quick.css" />
|
|
11
14
|
</head>
|
|
12
15
|
<body>
|
package/renderer/style.css
CHANGED
|
@@ -20,9 +20,21 @@
|
|
|
20
20
|
--user: var(--purple);
|
|
21
21
|
--assistant: var(--surface2);
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/* Deterministic stacks. All three named families are vendored in
|
|
24
|
+
renderer/fonts/ (see fonts/NOTICE.md), so they resolve to the same bytes on
|
|
25
|
+
every platform and with the network down — the desktop counterpart of the
|
|
26
|
+
terminal policy in cli/src/caps.js.
|
|
27
|
+
|
|
28
|
+
'Segoe UI' and system-ui were removed from the display/chat stacks on
|
|
29
|
+
purpose. As *first fallbacks* they converted "webfont didn't load" into a
|
|
30
|
+
platform-specific font — Segoe UI on Windows, SF Pro on macOS, fontconfig's
|
|
31
|
+
pick on Linux — which is how one stylesheet produced three different-looking
|
|
32
|
+
apps. Only glyphs the vendored families do not cover (CJK, colour emoji)
|
|
33
|
+
now reach the generic keyword; that residue is a host font by definition
|
|
34
|
+
and cannot be bundled away. */
|
|
35
|
+
--font-display: 'Syne', sans-serif;
|
|
24
36
|
--font-mono: 'DM Mono', monospace;
|
|
25
|
-
--font-chat: 'Ubuntu',
|
|
37
|
+
--font-chat: 'Ubuntu', sans-serif;
|
|
26
38
|
}
|
|
27
39
|
|
|
28
40
|
* {
|
package/vendor/aegis.js
CHANGED
|
@@ -189,11 +189,12 @@ function createClient(opts = {}) {
|
|
|
189
189
|
return headers;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
async function apiPost(path, body, headers) {
|
|
192
|
+
async function apiPost(path, body, headers, opts) {
|
|
193
193
|
const res = await fetch(`${apiBase}${path}`, {
|
|
194
194
|
method: 'POST',
|
|
195
195
|
headers: headers || authHeaders(),
|
|
196
196
|
body: JSON.stringify(body || {}),
|
|
197
|
+
signal: opts && opts.signal,
|
|
197
198
|
});
|
|
198
199
|
return parseResponse(res);
|
|
199
200
|
}
|
|
@@ -371,6 +372,19 @@ function createClient(opts = {}) {
|
|
|
371
372
|
return apiPost('/api/verify-api-key', { api_key: apiKey });
|
|
372
373
|
}
|
|
373
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Ask aegis1 for a free device key when the caller has none configured yet
|
|
377
|
+
* (`POST /api/v1/auth/register-device`). Idempotent server-side on
|
|
378
|
+
* `fingerprint` — the same fingerprint always returns the same account's
|
|
379
|
+
* key rather than minting a new one. The account starts at zero token-bank
|
|
380
|
+
* balance: this hands out an account, not free inference, so the existing
|
|
381
|
+
* balance=0 gate on `/api/v1/chat/completions` still applies unchanged.
|
|
382
|
+
* Sends no API key of its own — there isn't one yet.
|
|
383
|
+
*/
|
|
384
|
+
async function registerDevice(fingerprint, opts) {
|
|
385
|
+
return apiPost('/api/v1/auth/register-device', { fingerprint }, undefined, opts);
|
|
386
|
+
}
|
|
387
|
+
|
|
374
388
|
/** Build the OpenAI-style messages array from either a full history or the
|
|
375
389
|
* single-shot { system, prompt } shorthand.
|
|
376
390
|
*
|
|
@@ -994,6 +1008,7 @@ function createClient(opts = {}) {
|
|
|
994
1008
|
},
|
|
995
1009
|
promptUrl,
|
|
996
1010
|
verifyApiKey,
|
|
1011
|
+
registerDevice,
|
|
997
1012
|
chatCompletion,
|
|
998
1013
|
byokChatCompletion,
|
|
999
1014
|
listModels,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Auto-provision a free aegis1 device key when a host finds no key configured
|
|
5
|
+
* anywhere (env, the shared 0600 store, legacy config.json). One
|
|
6
|
+
* implementation shared by every host (MCP plugin, desktop, CLI) — see
|
|
7
|
+
* client/credentials.js's own header for why that matters here: three copies
|
|
8
|
+
* of the same timeout/endpoint/save logic is how they drift.
|
|
9
|
+
*
|
|
10
|
+
* Calls aegis1's `POST /api/v1/auth/register-device` (see client/aegis.js's
|
|
11
|
+
* `registerDevice`), idempotent per machine fingerprint (client/
|
|
12
|
+
* device-fingerprint.js) so a wiped credential store still resolves back to
|
|
13
|
+
* the same account rather than minting a new, unfunded one.
|
|
14
|
+
*
|
|
15
|
+
* The provisioned account starts at ZERO token-bank balance server-side, and
|
|
16
|
+
* aegis1 additionally requires an upgraded (paid, active) plan to use pooled
|
|
17
|
+
* aegiscloud models at all (nexus_chat_completions' is_pro() gate) — this
|
|
18
|
+
* only unblocks BYOK and account status/settings until the account is funded
|
|
19
|
+
* and/or upgraded. It is account creation, not a grant of free inference.
|
|
20
|
+
*/
|
|
21
|
+
const { machineFingerprint } = require('./device-fingerprint.js');
|
|
22
|
+
|
|
23
|
+
const DEVICE_REGISTER_TIMEOUT_MS = 8000;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {object} aegisClient A client from client/aegis.js (createClient()).
|
|
27
|
+
* @param {object} credentials client/credentials.js (or a compatible double).
|
|
28
|
+
* @returns {Promise<{ok: boolean, key: string, error?: Error}>}
|
|
29
|
+
* `ok` is true only when a usable key was obtained AND persisted to the
|
|
30
|
+
* shared store; `aegisClient.setApiKey()` has already been called on
|
|
31
|
+
* success, so the caller's client is immediately usable either way it
|
|
32
|
+
* inspects `ok`.
|
|
33
|
+
*/
|
|
34
|
+
async function autoProvisionApiKey(aegisClient, credentials) {
|
|
35
|
+
if (aegisClient.apiKey) return { ok: true, key: aegisClient.apiKey };
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const timer = setTimeout(() => controller.abort(), DEVICE_REGISTER_TIMEOUT_MS);
|
|
38
|
+
try {
|
|
39
|
+
const data = await aegisClient.registerDevice(machineFingerprint(), { signal: controller.signal });
|
|
40
|
+
const key = data && typeof data.api_key === 'string' ? data.api_key.trim() : '';
|
|
41
|
+
if (!key) return { ok: false, key: '' };
|
|
42
|
+
const saved = credentials.saveApiKey(key);
|
|
43
|
+
aegisClient.setApiKey(key);
|
|
44
|
+
return { ok: !!saved.ok, key };
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return { ok: false, key: '', error: err };
|
|
47
|
+
} finally {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { autoProvisionApiKey, DEVICE_REGISTER_TIMEOUT_MS };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The pooled-brain entry, defined ONCE for every host.
|
|
5
|
+
*
|
|
6
|
+
* The platform's `/api/v1/models` payload lists the brain tier under five
|
|
7
|
+
* spellings (`nexus-brain` canonical; `aegis-brain`, `nexus-brain-smart`,
|
|
8
|
+
* `aegis-brain-smart`, `nexus-brain-neo` each sent as `hidden: true,
|
|
9
|
+
* alias_of: "nexus-brain"`). A host that showed all five painted five models
|
|
10
|
+
* where the server routes one, and a host that showed none (or a different one
|
|
11
|
+
* per release) left users comparing two clients that disagree about the same
|
|
12
|
+
* account.
|
|
13
|
+
*
|
|
14
|
+
* This module exists because that disagreement was real: the desktop collapsed
|
|
15
|
+
* the class to a single "Nexus" entry (desktop/lib/local/engine.js) while the
|
|
16
|
+
* CLI kept every distinct id, so the same account saw a different model list in
|
|
17
|
+
* each host. Two copies of the selection rule is the drift; one shared function
|
|
18
|
+
* is the fix, which is why both hosts require THIS file — the desktop from
|
|
19
|
+
* `desktop/lib/local/engine.js` (repo-relative `client/brain-catalog.js`, or
|
|
20
|
+
* `desktop/vendor/brain-catalog.js` in the packaged app) and the CLI from
|
|
21
|
+
* `cli/src/models.js` (through `src/sharedpaths.js`: repo-relative
|
|
22
|
+
* `client/brain-catalog.js`, or `vendor/client/brain-catalog.js` installed).
|
|
23
|
+
*
|
|
24
|
+
* The two hosts do NOT hand this module the same entry shape and must not have
|
|
25
|
+
* to: the desktop passes the relay's raw payload (`hidden`, `alias_of`), the
|
|
26
|
+
* CLI passes its own normalised entries (`hidden`, `aliasOf`). So the readers
|
|
27
|
+
* below accept either spelling — one rule, two shapes — instead of each host
|
|
28
|
+
* keeping a private copy of the rule to match its own shape, which is the drift
|
|
29
|
+
* this module exists to end.
|
|
30
|
+
*
|
|
31
|
+
* Pure: no I/O, no node builtins, no dependencies — safe to require from a
|
|
32
|
+
* renderer, a CLI start-up path, or an MCP host.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** Every id the server uses for the pooled brain, canonical spelling first. */
|
|
36
|
+
const NEXUS_BRAIN_IDS = Object.freeze(['nexus-brain', 'aegis-brain']);
|
|
37
|
+
|
|
38
|
+
/** The name users see for the collapsed entry. */
|
|
39
|
+
const NEXUS_LABEL = 'Nexus';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The id this entry is an alias OF, or `null`. Reads the relay's `alias_of` and
|
|
43
|
+
* the CLI's normalised `aliasOf`, so callers may pass either shape.
|
|
44
|
+
*/
|
|
45
|
+
function aliasOfEntry(m) {
|
|
46
|
+
if (!m || typeof m !== 'object') return null;
|
|
47
|
+
for (const value of [m.alias_of, m.aliasOf]) {
|
|
48
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Whether the entry is marked display-hidden, in either shape. */
|
|
54
|
+
function isHiddenEntry(m) {
|
|
55
|
+
return Boolean(m) && m.hidden === true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Pick the one entry standing in for the pooled brain. The server serves
|
|
60
|
+
* `nexus-brain` as the canonical tier (`hidden: false`, no `alias_of`) and
|
|
61
|
+
* marks every other spelling — `aegis-brain` and the `-smart`/`-neo` tiers —
|
|
62
|
+
* as `hidden: true, alias_of: "nexus-brain"`. Prefer the canonical id so the
|
|
63
|
+
* request travels on the name the server owns; fall back to the alias, then to
|
|
64
|
+
* any tier whose `alias_of` names the brain, so a renamed or trimmed catalog
|
|
65
|
+
* still resolves to something selectable instead of silently emptying the
|
|
66
|
+
* dropdown (the previous fixed-id lookup returned [] if `aegis-brain` was ever
|
|
67
|
+
* retired, leaving the class with no model to choose).
|
|
68
|
+
*
|
|
69
|
+
* @param {Array<object>} models normalized or raw catalog entries.
|
|
70
|
+
* @returns {object|null}
|
|
71
|
+
*/
|
|
72
|
+
function selectBrainEntry(models) {
|
|
73
|
+
const list = Array.isArray(models) ? models.filter(Boolean) : [];
|
|
74
|
+
return (
|
|
75
|
+
list.find((m) => NEXUS_BRAIN_IDS.includes(m.id) && !isHiddenEntry(m) && aliasOfEntry(m) === null)
|
|
76
|
+
|| list.find((m) => NEXUS_BRAIN_IDS.includes(m.id))
|
|
77
|
+
|| list.find((m) => NEXUS_BRAIN_IDS.includes(aliasOfEntry(m)))
|
|
78
|
+
|| null
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The Aegis-class catalog as a host must OFFER it: exactly one entry, the
|
|
84
|
+
* collapsed brain — never the raw provider list, and never the alias tiers.
|
|
85
|
+
*
|
|
86
|
+
* Offering this shape is not the same as accepting only this shape: the ids the
|
|
87
|
+
* server advertises still route (a pin on `deepseek` still runs), so callers
|
|
88
|
+
* keep the full catalog for validation and use this for what they show.
|
|
89
|
+
*
|
|
90
|
+
* Returns `[]` when the payload names no brain tier at all — which is the
|
|
91
|
+
* caller's signal that this is not a pooled-brain catalog (the BYOK listing is
|
|
92
|
+
* per-provider models and must be offered in full, not collapsed to nothing).
|
|
93
|
+
*
|
|
94
|
+
* @param {Array<object>} models the server's catalog (any shape).
|
|
95
|
+
* @returns {Array<object>} `[]` when the payload names no brain tier at all.
|
|
96
|
+
*/
|
|
97
|
+
function filterAegisCatalog(models) {
|
|
98
|
+
const nexus = selectBrainEntry(models);
|
|
99
|
+
if (!nexus) return [];
|
|
100
|
+
// Drop the alias bookkeeping in BOTH spellings: this entry *is* the
|
|
101
|
+
// selection, so a renderer must never treat it as a hidden alias and filter
|
|
102
|
+
// it back out — which is exactly what happens if `aliasOf` survives here and
|
|
103
|
+
// the consumer re-runs its own "drop aliases" pass over the result.
|
|
104
|
+
const { hidden, alias_of, aliasOf, ...rest } = nexus;
|
|
105
|
+
return [{ ...rest, label: NEXUS_LABEL }];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
NEXUS_BRAIN_IDS,
|
|
110
|
+
NEXUS_LABEL,
|
|
111
|
+
selectBrainEntry,
|
|
112
|
+
filterAegisCatalog,
|
|
113
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A stable per-machine identifier, used ONLY to ask aegis1 for a free device
|
|
5
|
+
* key (`POST /api/v1/auth/register-device`) when no key is configured yet. It
|
|
6
|
+
* is not a credential itself — the endpoint treats it as an idempotency key,
|
|
7
|
+
* so a wiped `~/.aegiscode` still resolves back to the same aegis1 account
|
|
8
|
+
* (and whatever balance it holds) instead of minting a fresh, unfunded one.
|
|
9
|
+
*
|
|
10
|
+
* Derivation mirrors ae-guix's `getMachineFingerprint()` (main.js) so the two
|
|
11
|
+
* hosts don't invent divergent conventions for the same idea: the OS's own
|
|
12
|
+
* machine identity where one exists, hashed with a purpose-specific prefix so
|
|
13
|
+
* it can never collide with a fingerprint minted for another purpose.
|
|
14
|
+
*/
|
|
15
|
+
const crypto = require('node:crypto');
|
|
16
|
+
const os = require('node:os');
|
|
17
|
+
|
|
18
|
+
/** Best-effort OS machine identity. Empty string, never throws, if none is reachable. */
|
|
19
|
+
function readMachineId() {
|
|
20
|
+
try {
|
|
21
|
+
if (process.platform === 'linux') {
|
|
22
|
+
const fs = require('node:fs');
|
|
23
|
+
try {
|
|
24
|
+
return fs.readFileSync('/etc/machine-id', 'utf8').trim();
|
|
25
|
+
} catch {
|
|
26
|
+
return fs.readFileSync('/var/lib/dbus/machine-id', 'utf8').trim();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (process.platform === 'darwin') {
|
|
30
|
+
const { execSync } = require('node:child_process');
|
|
31
|
+
const out = execSync('ioreg -rd1 -c IOPlatformExpertDevice', {
|
|
32
|
+
encoding: 'utf8',
|
|
33
|
+
timeout: 3000,
|
|
34
|
+
});
|
|
35
|
+
const m = out.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
|
|
36
|
+
if (m) return m[1];
|
|
37
|
+
}
|
|
38
|
+
if (process.platform === 'win32') {
|
|
39
|
+
const { execSync } = require('node:child_process');
|
|
40
|
+
const out = execSync('reg query HKLM\\SOFTWARE\\Microsoft\\Cryptography /v MachineGuid', {
|
|
41
|
+
encoding: 'utf8',
|
|
42
|
+
timeout: 3000,
|
|
43
|
+
});
|
|
44
|
+
const m = out.match(/MachineGuid\s+REG_SZ\s+(\S+)/);
|
|
45
|
+
if (m) return m[1];
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
/* no reachable machine id on this host — the caller falls back below */
|
|
49
|
+
}
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* sha256("aegiscode-plugin-device-v1:" + machine identity), hex.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} [overrideId] - test-only override for the raw machine identity.
|
|
57
|
+
*/
|
|
58
|
+
function machineFingerprint(overrideId) {
|
|
59
|
+
const id = overrideId || readMachineId() || `${os.hostname()}:${os.platform()}:${os.arch()}`;
|
|
60
|
+
return crypto.createHash('sha256').update(`aegiscode-plugin-device-v1:${id}`).digest('hex');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { machineFingerprint, readMachineId };
|