aegis-desktop 1.0.13 → 1.0.15

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.
Files changed (96) hide show
  1. package/SOURCE-OFFER.txt +9 -0
  2. package/bin/aegis.js +1 -32
  3. package/lib/account-key.js +1 -56
  4. package/lib/auto.js +1 -430
  5. package/lib/avatar/companion.js +1 -698
  6. package/lib/avatar/cosmetics.js +1 -532
  7. package/lib/avatar/events.js +1 -344
  8. package/lib/avatar/holder-turn.js +1 -350
  9. package/lib/avatar/holders.js +1 -612
  10. package/lib/avatar/identity.js +1 -641
  11. package/lib/avatar/level.js +1 -368
  12. package/lib/avatar/mirror.js +1 -524
  13. package/lib/avatar/persona.js +1 -388
  14. package/lib/avatar/profile.js +1 -977
  15. package/lib/avatar/register.js +1 -307
  16. package/lib/avatar/store.js +1 -472
  17. package/lib/avatar/tiers.js +1 -38
  18. package/lib/avatar/transfer.js +1 -415
  19. package/lib/avatar/turn.js +1 -216
  20. package/lib/avatar/voice.js +1 -328
  21. package/lib/avatar/xp.js +1 -404
  22. package/lib/deep-link.js +1 -120
  23. package/lib/fingerprint.js +2 -347
  24. package/lib/google-connect.js +1 -169
  25. package/lib/local/agents.js +1 -105
  26. package/lib/local/autonomous.js +1 -1045
  27. package/lib/local/claude-pro.js +1 -993
  28. package/lib/local/context.js +1 -95
  29. package/lib/local/engine.js +1 -3228
  30. package/lib/local/entitlement.js +1 -654
  31. package/lib/local/git-scope.js +1 -228
  32. package/lib/local/local.js +1 -2135
  33. package/lib/local/prompt.js +1 -275
  34. package/lib/local/queue.js +1 -560
  35. package/lib/local/session-rounds.js +1 -208
  36. package/lib/local/shell.js +1 -680
  37. package/lib/local/telemetry.js +1 -470
  38. package/lib/local/tools.js +1 -1238
  39. package/lib/local/turn-guard.js +1 -300
  40. package/lib/local/worktree-lock.js +1 -235
  41. package/lib/provider-key-mirror.js +1 -252
  42. package/lib/quick-launcher.js +1 -84
  43. package/lib/settings.js +1 -890
  44. package/lib/sync/memory-queue.js +1 -60
  45. package/lib/sync/persist-gate.js +1 -148
  46. package/lib/sync/sessions.js +1 -106
  47. package/lib/window-state.js +1 -66
  48. package/main.js +3 -4390
  49. package/package.json +3 -5
  50. package/preload.js +3 -628
  51. package/renderer/app.js +1 -6560
  52. package/renderer/avatar/assemble.js +1 -335
  53. package/renderer/avatar/avatar.js +1 -380
  54. package/renderer/avatar/cards.js +1 -141
  55. package/renderer/avatar/hud.js +1 -206
  56. package/renderer/avatar/machine.js +1 -213
  57. package/renderer/avatar/pane.js +1 -439
  58. package/renderer/avatar/parts.js +1 -437
  59. package/renderer/budget.js +1 -168
  60. package/renderer/companion.js +1 -223
  61. package/renderer/diff.js +1 -359
  62. package/renderer/diffview.js +1 -336
  63. package/renderer/fingerprint.js +3 -720
  64. package/renderer/markdown.js +1 -168
  65. package/renderer/provider-toolbar.js +1 -134
  66. package/renderer/providers.js +1 -259
  67. package/renderer/quick.js +1 -191
  68. package/renderer/stream-policy.js +1 -306
  69. package/renderer/transcript-view.js +1 -323
  70. package/renderer/usage.js +1 -1239
  71. package/vendor/aegis.js +1 -2054
  72. package/vendor/attest.js +1 -442
  73. package/vendor/authorship-key.js +1 -410
  74. package/vendor/auto-provision.js +1 -134
  75. package/vendor/brain-catalog.js +1 -268
  76. package/vendor/byok-catalog.js +1 -230
  77. package/vendor/credentials.js +1 -494
  78. package/vendor/device-fingerprint.js +1 -141
  79. package/vendor/drive-connect.js +1 -85
  80. package/vendor/drive-upload.js +1 -174
  81. package/vendor/env-file.js +1 -1273
  82. package/vendor/fingerprint.js +1 -1064
  83. package/vendor/foreign-memory.js +1 -669
  84. package/vendor/google-signin.js +1 -125
  85. package/vendor/hooks.js +1 -430
  86. package/vendor/model-discovery.js +1 -330
  87. package/vendor/platform.js +1 -937
  88. package/vendor/prefs.js +1 -351
  89. package/vendor/presence-constants.js +1 -107
  90. package/vendor/presence.js +1 -438
  91. package/vendor/record-vault.js +3 -1055
  92. package/vendor/session-store.js +1 -548
  93. package/vendor/topup.js +1 -297
  94. package/vendor/touch.js +1 -458
  95. package/vendor/update.js +1 -704
  96. package/vendor/user-models.js +1 -381
@@ -1,337 +1,3 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
2
  // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
- // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
- // Conveying a modified copy, or running one as a network service, owes the
5
- // corresponding source under this licence (§5/§13) — see LICENSING.md.
6
- 'use strict';
7
-
8
- /**
9
- * assemble.js — persona JSON in, layered SVG out.
10
- *
11
- * A pure string builder. It takes a validated persona record plus one
12
- * expression id and returns the markup, the layer list it walked, and — the
13
- * part that matters — a `warnings`/`missing` pair. Three rules:
14
- *
15
- * 1. **Never blank, never throw.** An id the manifest does not know (a persona
16
- * hand-edited, or written by a newer release) renders the default part and
17
- * is REPORTED. `persona.js` made the same choice at the storage boundary
18
- * for the same reason: the user's own file must not be able to take the
19
- * companion's face away.
20
- *
21
- * 2. **No interpolation of user text.** Nothing the user typed is placed in
22
- * the markup, so there is no escaping question and no injection surface:
23
- * the only persona-supplied value that reaches the SVG is `hairColor`, and
24
- * it is re-checked against `#rrggbb` here even though `persona.js` already
25
- * checked it. Geometry comes from the manifest, which is code.
26
- *
27
- * 3. **No styles, no scripts, no references.** The output carries no `<style>`,
28
- * no `on*` attribute and no `href`/`url(...)`: motion lives in avatar.css,
29
- * keyed off the `data-motion` attribute this file sets. That keeps the
30
- * renderer's CSP (`script-src 'self'; style-src 'self'`) satisfied by
31
- * construction, and `desktop/test/avatar-render.test.mjs` asserts all four
32
- * absences rather than trusting this paragraph.
33
- *
34
- * Pure: no DOM, no Electron. It runs identically under `node --test` and in the
35
- * renderer, which is what makes the whole phase testable.
36
- */
37
-
38
- 'use strict';
39
-
40
- const pkg = typeof window !== 'undefined' && window.AegisAvatarParts
41
- ? window.AegisAvatarParts
42
- : typeof module !== 'undefined' && module.exports
43
- ? require('./parts.js')
44
- : null;
45
-
46
- if (!pkg) throw new Error('avatar/assemble.js: avatar/parts.js must be loaded first');
47
-
48
- const HEX = /^#[0-9a-f]{6}$/i;
49
-
50
- /** Lighten (`amount > 0`) or darken (`amount < 0`) a `#rrggbb` colour. */
51
- function shade(hex, amount) {
52
- const m = HEX.exec(String(hex || ''));
53
- if (!m) return hex;
54
- const n = parseInt(m[0].slice(1), 16);
55
- const target = amount < 0 ? 0 : 255;
56
- const k = Math.min(1, Math.abs(amount));
57
- const mix = (c) => Math.round(c * (1 - k) + target * k);
58
- const r = mix((n >> 16) & 255);
59
- const g = mix((n >> 8) & 255);
60
- const b = mix(n & 255);
61
- return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
62
- }
63
-
64
- /** Walk a flat list of shape specs, keeping only the ones with geometry. */
65
- function shapesOf(list) {
66
- return Array.isArray(list) ? list.filter((s) => s && typeof s === 'object') : [];
67
- }
68
-
69
- /** A path string from an expression is geometry — normalise it to a shape. */
70
- function asStrokes(value) {
71
- if (typeof value === 'string' && value.trim()) return [{ kind: 'path', d: value, stroke: true }];
72
- if (!Array.isArray(value)) return [];
73
- return value
74
- .map((v) => (typeof v === 'string' ? { kind: 'path', d: v, stroke: true } : v))
75
- .filter((s) => s && typeof s === 'object');
76
- }
77
-
78
- function call(fn, ...args) {
79
- return typeof fn === 'function' ? fn(...args) : undefined;
80
- }
81
-
82
- /**
83
- * Resolve a custom expression from `persona.expressions.custom`.
84
- *
85
- * A custom expression is DATA too: `{ mouth, brows, eyes }` where `mouth` is an
86
- * SVG path, `brows` is a path or a list of paths, and `eyes` names one of the
87
- * manifest's eye overrides. Anything unrecognised is dropped with a warning, so
88
- * a half-written custom expression renders the core face rather than nothing.
89
- */
90
- function customExpression(persona, id, warnings) {
91
- const custom = persona && persona.expressions && persona.expressions.custom;
92
- const record = custom && typeof custom === 'object' ? custom[id] : null;
93
- if (!record || typeof record !== 'object') return null;
94
- const out = { label: id, custom: true };
95
- if (typeof record.mouth === 'string' && record.mouth.trim()) out.mouth = record.mouth.trim();
96
- if (record.brows !== undefined) out.brows = asStrokes(record.brows).map((s) => s.d);
97
- if (record.eyes !== undefined) {
98
- if (typeof record.eyes === 'string' && pkg.EYE_OVERRIDES[record.eyes]) out.eyes = record.eyes;
99
- else warnings.push(`custom expression ${id}: unknown eye override ${JSON.stringify(record.eyes)} — ignored`);
100
- }
101
- if (out.mouth === undefined && out.brows === undefined && out.eyes === undefined) {
102
- warnings.push(`custom expression ${id} carries no drawable geometry — ignored`);
103
- return null;
104
- }
105
- return out;
106
- }
107
-
108
- /**
109
- * Assemble the avatar.
110
- *
111
- * @param {object} persona a persona record (validated or raw — both survive)
112
- * @param {object} [opts]
113
- * @param {string} [opts.expression] an `events.js` expression id
114
- * @param {boolean} [opts.reducedMotion] the OS preference; forces `minimal`
115
- * @param {string} [opts.motion] explicit override, mostly for tests
116
- * @returns {{ svg: string, layers: object[], warnings: string[], missing: string[],
117
- * expression: string, palette: string, frame: string, motion: string }}
118
- */
119
- function assemble(persona, opts = {}) {
120
- const warnings = [];
121
- const missing = [];
122
- const p = persona && typeof persona === 'object' ? persona : pkg.defaultPersona();
123
- const presentation = p.presentation && typeof p.presentation === 'object' ? p.presentation : {};
124
- const appearance = p.appearance && typeof p.appearance === 'object' ? p.appearance : {};
125
-
126
- // ---- palette / skin / hair colour --------------------------------------
127
- const paletteId = pkg.PALETTES[presentation.palette] ? presentation.palette : 'amber';
128
- if (presentation.palette !== undefined && paletteId !== presentation.palette) {
129
- warnings.push(`unknown palette ${JSON.stringify(presentation.palette)} — using ${paletteId}`);
130
- }
131
- const palette = pkg.PALETTES[paletteId];
132
- const skin = Number.isInteger(presentation.skin) && presentation.skin >= 0 && presentation.skin < pkg.SKIN_TONES.length
133
- ? presentation.skin
134
- : 2;
135
- const skinTone = pkg.SKIN_TONES[skin];
136
- const hairColor = HEX.test(String(presentation.hairColor || '')) ? String(presentation.hairColor) : '#3b2a20';
137
-
138
- function colorFor(role) {
139
- switch (role) {
140
- case 'skin': return skinTone;
141
- case 'skinShade': return shade(skinTone, -0.18);
142
- case 'hair': return hairColor;
143
- case 'hairShade': return shade(hairColor, -0.25);
144
- case 'cloth': return palette.cloth;
145
- case 'clothShade': return palette.clothShade;
146
- case 'accent': return palette.accent;
147
- case 'metal': return palette.metal;
148
- case 'bg': return palette.bg;
149
- case 'ink': return palette.ink;
150
- case 'sclera': return '#fdfbf7';
151
- case 'highlight': return '#ffffff';
152
- default: return palette.ink;
153
- }
154
- }
155
-
156
- // ---- parts -------------------------------------------------------------
157
- function part(table, kind, id, fallback) {
158
- const got = table[id];
159
- if (got) return got;
160
- if (id !== undefined && id !== null && id !== '') {
161
- missing.push(`${kind}:${id}`);
162
- warnings.push(`unknown ${kind} ${JSON.stringify(id)} — using ${fallback}`);
163
- }
164
- return table[fallback];
165
- }
166
-
167
- const frameId = pkg.FRAMES[presentation.frame] ? presentation.frame : 'A';
168
- const frame = pkg.FRAMES[frameId];
169
- const hair = part(pkg.HAIR, 'hair', presentation.hair, 'bob-2');
170
- const eyes = part(pkg.EYES, 'eyes', presentation.eyes, 'soft');
171
-
172
- const outfitIds = [];
173
- for (const item of Array.isArray(presentation.outfit) ? presentation.outfit : []) {
174
- if (pkg.OUTFITS[item]) outfitIds.push(item);
175
- else missing.push(`outfit:${item}`);
176
- }
177
- if (!outfitIds.length && Array.isArray(presentation.outfit) && presentation.outfit.length) {
178
- warnings.push('no known outfit layer — wearing the plain tee');
179
- }
180
- const outfits = outfitIds.slice(0, 3).map((id) => pkg.OUTFITS[id]);
181
-
182
- // ---- expression --------------------------------------------------------
183
- const requested = typeof opts.expression === 'string' && opts.expression ? opts.expression : 'neutral';
184
- let expression = pkg.EXPRESSIONS[requested];
185
- let expressionId = requested;
186
- if (!expression) {
187
- const custom = customExpression(p, requested, warnings);
188
- if (custom) {
189
- expression = custom;
190
- } else {
191
- if (requested !== 'neutral') {
192
- missing.push(`expression:${requested}`);
193
- warnings.push(`unknown expression ${JSON.stringify(requested)} — using neutral`);
194
- }
195
- expression = pkg.EXPRESSIONS.neutral;
196
- expressionId = 'neutral';
197
- }
198
- }
199
-
200
- const eyeOverride = expression.eyes && pkg.EYE_OVERRIDES[expression.eyes] ? pkg.EYE_OVERRIDES[expression.eyes] : null;
201
- const browShapes = Array.isArray(expression.brows)
202
- ? asStrokes(expression.brows)
203
- : asStrokes(expression.brows);
204
- const mouthShapes = asStrokes(expression.mouth);
205
-
206
- const sheet = {
207
- shadow: [{ kind: 'ellipse', cx: 100, cy: 254, rx: 62, ry: 10, role: 'clothShade', opacity: 0.5 }],
208
- hairBack: shapesOf(hair.back),
209
- body: [
210
- { kind: 'path', d: pkg.BODY.neck, role: 'skinShade' },
211
- { kind: 'path', d: pkg.BODY.shirt, role: 'cloth' },
212
- { kind: 'path', d: pkg.BODY.sleeveL, role: 'cloth' },
213
- { kind: 'path', d: pkg.BODY.sleeveR, role: 'cloth' },
214
- ],
215
- outfitUnder: [],
216
- head: [
217
- { kind: 'path', d: frame.head, role: 'skin' },
218
- ...frame.ear.map((e) => ({ kind: 'circle', cx: e.cx, cy: e.cy, r: e.r, role: 'skin' })),
219
- ],
220
- face: [
221
- ...(eyeOverride
222
- ? eyeOverride.map((s) => ({ ...s, stroke: true }))
223
- : [...eyes.left, ...eyes.right]),
224
- ...browShapes,
225
- ...shapesOf(pkg.FACE.nose),
226
- ...mouthShapes,
227
- ],
228
- hairFront: [...shapesOf(hair.front), ...shapesOf(hair.detail)],
229
- outfitOver: [],
230
- accessory: [],
231
- effect: [],
232
- };
233
-
234
- // Outfit layers draw in the order the persona listed them: the last one is
235
- // the outermost. Items flagged `accessory` (headphones, glasses) go on top of
236
- // the hair instead of into the clothes pile.
237
- const under = sheet.outfitUnder;
238
- for (const item of outfits) {
239
- under.push(...shapesOf(item.under).map((s) => ({ ...s, defaultRole: 'cloth' })));
240
- const overShapes = shapesOf(item.over).map((s) => ({ ...s, defaultRole: 'cloth' }));
241
- if (item.accessory) sheet.accessory.push(...overShapes);
242
- else sheet.outfitOver.push(...overShapes);
243
- }
244
-
245
- // A celebrating face gets a few sparks — motion-gated in CSS, and the only
246
- // thing on the `effect` layer.
247
- if (expressionId === 'happy' || expressionId === 'playful' || expressionId === 'delighted') {
248
- sheet.effect.push(
249
- { kind: 'path', d: 'M28 66 l4 10 l10 4 l-10 4 l-4 10 l-4 -10 l-10 -4 l10 -4 Z', role: 'accent', opacity: 0.9 },
250
- { kind: 'path', d: 'M172 88 l3 8 l8 3 l-8 3 l-3 8 l-3 -8 l-8 -3 l8 -3 Z', role: 'accent', opacity: 0.7 }
251
- );
252
- }
253
-
254
- // ---- markup ------------------------------------------------------------
255
- /** Per-layer default fill role, so a manifest entry rarely repeats itself. */
256
- const layerDefaultRole = {
257
- shadow: 'clothShade',
258
- hairBack: 'hair',
259
- body: 'cloth',
260
- outfitUnder: 'cloth',
261
- head: 'skin',
262
- face: 'ink',
263
- hairFront: 'hair',
264
- outfitOver: 'cloth',
265
- accessory: 'metal',
266
- effect: 'accent',
267
- };
268
-
269
- function renderShape(shape, layer) {
270
- const role = shape.role || shape.defaultRole || layerDefaultRole[layer] || 'ink';
271
- const fill = colorFor(role);
272
- const common = [];
273
- if (shape.stroke) {
274
- common.push('fill="none"', `stroke="${fill}"`, 'stroke-width="3.5"', 'stroke-linecap="round"', 'stroke-linejoin="round"');
275
- } else {
276
- common.push(`fill="${fill}"`);
277
- }
278
- if (typeof shape.opacity === 'number') common.push(`opacity="${shape.opacity}"`);
279
-
280
- switch (shape.kind) {
281
- case 'circle':
282
- return `<circle cx="${shape.cx}" cy="${shape.cy}" r="${shape.r}" ${common.join(' ')}/>`;
283
- case 'ellipse':
284
- return `<ellipse cx="${shape.cx}" cy="${shape.cy}" rx="${shape.rx}" ry="${shape.ry}" ${common.join(' ')}/>`;
285
- case 'rect': {
286
- const rx = shape.rx === undefined ? 0 : shape.rx;
287
- return `<rect x="${shape.x}" y="${shape.y}" width="${shape.width}" height="${shape.height}" rx="${rx}" ${common.join(' ')}/>`;
288
- }
289
- case 'path':
290
- default:
291
- return `<path d="${shape.d}" ${common.join(' ')}/>`;
292
- }
293
- }
294
-
295
- const motion = opts.motion
296
- ? String(opts.motion)
297
- : pkg.motionFor(p, Boolean(opts.reducedMotion));
298
- const size = ['small', 'medium', 'large'].includes(appearance.size) ? appearance.size : 'medium';
299
-
300
- const layers = [];
301
- const groups = [];
302
- for (const name of pkg.LAYERS) {
303
- const shapes = sheet[name] || [];
304
- layers.push({ name, shapes: shapes.length });
305
- if (!shapes.length) continue;
306
- const body = shapes.map((s) => renderShape(s, name)).join('');
307
- groups.push(`<g class="avatar-layer avatar-${name}" data-layer="${name}">${body}</g>`);
308
- }
309
-
310
- const svg =
311
- `<svg class="avatar-svg" viewBox="0 0 200 268" xmlns="http://www.w3.org/2000/svg" ` +
312
- `aria-hidden="true" focusable="false" role="presentation" ` +
313
- `data-motion="${motion}" data-size="${size}" data-frame="${frameId}" ` +
314
- `data-palette="${paletteId}" data-expression="${expressionId}">` +
315
- `<rect class="avatar-bg" x="0" y="0" width="200" height="268" rx="24" fill="${colorFor('bg')}"/>` +
316
- groups.join('') +
317
- '</svg>';
318
-
319
- return {
320
- svg,
321
- layers,
322
- warnings,
323
- missing,
324
- expression: expressionId,
325
- expressionLabel: expression.label || expressionId,
326
- palette: paletteId,
327
- frame: frameId,
328
- hair: hair === pkg.HAIR[presentation.hair] ? presentation.hair : 'bob-2',
329
- motion,
330
- size,
331
- };
332
- }
333
-
334
- const API = Object.freeze({ assemble, shade });
335
-
336
- if (typeof module !== 'undefined' && module.exports) module.exports = API;
337
- else if (typeof globalThis !== 'undefined') globalThis.AegisAvatarAssemble = API;
3
+ "use strict";const pkg="undefined"!=typeof window&&window.AegisAvatarParts?window.AegisAvatarParts:"undefined"!=typeof module&&module.exports?require("./parts.js"):null;if(!pkg)throw new Error("avatar/assemble.js: avatar/parts.js must be loaded first");const HEX=/^#[0-9a-f]{6}$/i;function shade(e,t){const r=HEX.exec(String(e||""));if(!r)return e;const s=parseInt(r[0].slice(1),16),o=t<0?0:255,a=Math.min(1,Math.abs(t)),n=e=>Math.round(e*(1-a)+o*a);return`#${(n(s>>16&255)<<16|n(s>>8&255)<<8|n(255&s)).toString(16).padStart(6,"0")}`}function shapesOf(e){return Array.isArray(e)?e.filter((e=>e&&"object"==typeof e)):[]}function asStrokes(e){return"string"==typeof e&&e.trim()?[{kind:"path",d:e,stroke:!0}]:Array.isArray(e)?e.map((e=>"string"==typeof e?{kind:"path",d:e,stroke:!0}:e)).filter((e=>e&&"object"==typeof e)):[]}function call(e,...t){return"function"==typeof e?e(...t):void 0}function customExpression(e,t,r){const s=e&&e.expressions&&e.expressions.custom,o=s&&"object"==typeof s?s[t]:null;if(!o||"object"!=typeof o)return null;const a={label:t,custom:!0};return"string"==typeof o.mouth&&o.mouth.trim()&&(a.mouth=o.mouth.trim()),void 0!==o.brows&&(a.brows=asStrokes(o.brows).map((e=>e.d))),void 0!==o.eyes&&("string"==typeof o.eyes&&pkg.EYE_OVERRIDES[o.eyes]?a.eyes=o.eyes:r.push(`custom expression ${t}: unknown eye override ${JSON.stringify(o.eyes)} — ignored`)),void 0===a.mouth&&void 0===a.brows&&void 0===a.eyes?(r.push(`custom expression ${t} carries no drawable geometry — ignored`),null):a}function assemble(e,t={}){const r=[],s=[],o=e&&"object"==typeof e?e:pkg.defaultPersona(),a=o.presentation&&"object"==typeof o.presentation?o.presentation:{},n=o.appearance&&"object"==typeof o.appearance?o.appearance:{},i=pkg.PALETTES[a.palette]?a.palette:"amber";void 0!==a.palette&&i!==a.palette&&r.push(`unknown palette ${JSON.stringify(a.palette)} — using ${i}`);const l=pkg.PALETTES[i],c=Number.isInteger(a.skin)&&a.skin>=0&&a.skin<pkg.SKIN_TONES.length?a.skin:2,p=pkg.SKIN_TONES[c],u=HEX.test(String(a.hairColor||""))?String(a.hairColor):"#3b2a20";function h(e){switch(e){case"skin":return p;case"skinShade":return shade(p,-.18);case"hair":return u;case"hairShade":return shade(u,-.25);case"cloth":return l.cloth;case"clothShade":return l.clothShade;case"accent":return l.accent;case"metal":return l.metal;case"bg":return l.bg;case"ink":default:return l.ink;case"sclera":return"#fdfbf7";case"highlight":return"#ffffff"}}function d(e,t,o,a){return e[o]||(null!=o&&""!==o&&(s.push(`${t}:${o}`),r.push(`unknown ${t} ${JSON.stringify(o)} — using ${a}`)),e[a])}const f=pkg.FRAMES[a.frame]?a.frame:"A",g=pkg.FRAMES[f],y=d(pkg.HAIR,"hair",a.hair,"bob-2"),k=d(pkg.EYES,"eyes",a.eyes,"soft"),m=[];for(const e of Array.isArray(a.outfit)?a.outfit:[])pkg.OUTFITS[e]?m.push(e):s.push(`outfit:${e}`);!m.length&&Array.isArray(a.outfit)&&a.outfit.length&&r.push("no known outfit layer — wearing the plain tee");const b=m.slice(0,3).map((e=>pkg.OUTFITS[e])),$="string"==typeof t.expression&&t.expression?t.expression:"neutral";let S=pkg.EXPRESSIONS[$],x=$;if(!S){const e=customExpression(o,$,r);e?S=e:("neutral"!==$&&(s.push(`expression:${$}`),r.push(`unknown expression ${JSON.stringify($)} — using neutral`)),S=pkg.EXPRESSIONS.neutral,x="neutral")}const w=S.eyes&&pkg.EYE_OVERRIDES[S.eyes]?pkg.EYE_OVERRIDES[S.eyes]:null,E=(Array.isArray(S.brows),asStrokes(S.brows)),v=asStrokes(S.mouth),A={shadow:[{kind:"ellipse",cx:100,cy:254,rx:62,ry:10,role:"clothShade",opacity:.5}],hairBack:shapesOf(y.back),body:[{kind:"path",d:pkg.BODY.neck,role:"skinShade"},{kind:"path",d:pkg.BODY.shirt,role:"cloth"},{kind:"path",d:pkg.BODY.sleeveL,role:"cloth"},{kind:"path",d:pkg.BODY.sleeveR,role:"cloth"}],outfitUnder:[],head:[{kind:"path",d:g.head,role:"skin"},...g.ear.map((e=>({kind:"circle",cx:e.cx,cy:e.cy,r:e.r,role:"skin"})))],face:[...w?w.map((e=>({...e,stroke:!0}))):[...k.left,...k.right],...E,...shapesOf(pkg.FACE.nose),...v],hairFront:[...shapesOf(y.front),...shapesOf(y.detail)],outfitOver:[],accessory:[],effect:[]},O=A.outfitUnder;for(const e of b){O.push(...shapesOf(e.under).map((e=>({...e,defaultRole:"cloth"}))));const t=shapesOf(e.over).map((e=>({...e,defaultRole:"cloth"})));e.accessory?A.accessory.push(...t):A.outfitOver.push(...t)}"happy"!==x&&"playful"!==x&&"delighted"!==x||A.effect.push({kind:"path",d:"M28 66 l4 10 l10 4 l-10 4 l-4 10 l-4 -10 l-10 -4 l10 -4 Z",role:"accent",opacity:.9},{kind:"path",d:"M172 88 l3 8 l8 3 l-8 3 l-3 8 l-3 -8 l-8 -3 l8 -3 Z",role:"accent",opacity:.7});const j={shadow:"clothShade",hairBack:"hair",body:"cloth",outfitUnder:"cloth",head:"skin",face:"ink",hairFront:"hair",outfitOver:"cloth",accessory:"metal",effect:"accent"};function R(e,t){const r=h(e.role||e.defaultRole||j[t]||"ink"),s=[];switch(e.stroke?s.push('fill="none"',`stroke="${r}"`,'stroke-width="3.5"','stroke-linecap="round"','stroke-linejoin="round"'):s.push(`fill="${r}"`),"number"==typeof e.opacity&&s.push(`opacity="${e.opacity}"`),e.kind){case"circle":return`<circle cx="${e.cx}" cy="${e.cy}" r="${e.r}" ${s.join(" ")}/>`;case"ellipse":return`<ellipse cx="${e.cx}" cy="${e.cy}" rx="${e.rx}" ry="${e.ry}" ${s.join(" ")}/>`;case"rect":{const t=void 0===e.rx?0:e.rx;return`<rect x="${e.x}" y="${e.y}" width="${e.width}" height="${e.height}" rx="${t}" ${s.join(" ")}/>`}default:return`<path d="${e.d}" ${s.join(" ")}/>`}}const I=t.motion?String(t.motion):pkg.motionFor(o,Boolean(t.reducedMotion)),T=["small","medium","large"].includes(n.size)?n.size:"medium",N=[],P=[];for(const e of pkg.LAYERS){const t=A[e]||[];if(N.push({name:e,shapes:t.length}),!t.length)continue;const r=t.map((t=>R(t,e))).join("");P.push(`<g class="avatar-layer avatar-${e}" data-layer="${e}">${r}</g>`)}return{svg:`<svg class="avatar-svg" viewBox="0 0 200 268" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" role="presentation" data-motion="${I}" data-size="${T}" data-frame="${f}" data-palette="${i}" data-expression="${x}"><rect class="avatar-bg" x="0" y="0" width="200" height="268" rx="24" fill="${h("bg")}"/>`+P.join("")+"</svg>",layers:N,warnings:r,missing:s,expression:x,expressionLabel:S.label||x,palette:i,frame:f,hair:y===pkg.HAIR[a.hair]?a.hair:"bob-2",motion:I,size:T}}const API=Object.freeze({assemble:assemble,shade:shade});"undefined"!=typeof module&&module.exports?module.exports=API:"undefined"!=typeof globalThis&&(globalThis.AegisAvatarAssemble=API);