@jossuealcala/madre 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +5 -0
  4. package/README.md +281 -0
  5. package/bin/madre.mjs +134 -0
  6. package/docs/madre-banner.svg +25 -0
  7. package/package.json +63 -0
  8. package/public/app.js +4031 -0
  9. package/public/brands.js +59 -0
  10. package/public/index.html +231 -0
  11. package/public/styles.css +976 -0
  12. package/public/troubleshooting.js +460 -0
  13. package/src/adapters/claude.mjs +105 -0
  14. package/src/adapters/codex.mjs +84 -0
  15. package/src/adapters/gemini.mjs +335 -0
  16. package/src/adapters/opencode.mjs +148 -0
  17. package/src/adapters/process.mjs +132 -0
  18. package/src/ashcode.mjs +64 -0
  19. package/src/auth-probe.mjs +145 -0
  20. package/src/capabilities.mjs +126 -0
  21. package/src/checkpoint.mjs +106 -0
  22. package/src/cli-args.mjs +38 -0
  23. package/src/commands.mjs +98 -0
  24. package/src/config.mjs +59 -0
  25. package/src/conversation-context.mjs +59 -0
  26. package/src/directives.mjs +70 -0
  27. package/src/distiller.mjs +78 -0
  28. package/src/embeddings.mjs +80 -0
  29. package/src/event-store.mjs +152 -0
  30. package/src/extensions.mjs +263 -0
  31. package/src/files.mjs +177 -0
  32. package/src/image-studio.mjs +25 -0
  33. package/src/lease.mjs +83 -0
  34. package/src/mcp/image-server.mjs +169 -0
  35. package/src/mcp/memory-server.mjs +221 -0
  36. package/src/memory-tools.mjs +34 -0
  37. package/src/memory.mjs +543 -0
  38. package/src/models.mjs +78 -0
  39. package/src/mother.mjs +163 -0
  40. package/src/quota-monitor.mjs +119 -0
  41. package/src/quota-sources.mjs +161 -0
  42. package/src/room.mjs +1159 -0
  43. package/src/router.mjs +13 -0
  44. package/src/runtime-detection.mjs +85 -0
  45. package/src/server.mjs +693 -0
  46. package/src/setup.mjs +192 -0
  47. package/src/usage-sentinel.mjs +65 -0
@@ -0,0 +1,59 @@
1
+ // Visual identity per agent.
2
+ //
3
+ // Colors follow each vendor's public palette so a Codex bubble reads as Codex
4
+ // and a Claude bubble as Claude. The marks are MADRE's own monograms: the
5
+ // vendors' logos are registered trademarks and their brand guidelines do not
6
+ // allow embedding them in third-party products without permission. To use an
7
+ // official asset under a license you hold, set `mark` to that inline SVG.
8
+ //
9
+ // `mark` receives no arguments and must return an SVG string with
10
+ // `fill="currentColor"` so the monogram inherits the agent color.
11
+
12
+ const glyph = (body) => `<svg viewBox="0 0 32 32" aria-hidden="true" focusable="false">${body}</svg>`;
13
+
14
+ export const BRANDS = {
15
+ codex: {
16
+ label: 'Codex',
17
+ vendor: 'OpenAI',
18
+ color: '#10A37F',
19
+ colorLight: '#0B7A5F',
20
+ ink: '#FFFFFF',
21
+ mark: () => glyph('<path fill="currentColor" d="M16 4l10.4 6v12L16 28 5.6 22V10L16 4zm0 3.4L8.5 11.7v8.6L16 24.6l7.5-4.3v-8.6L16 7.4zm0 4.2l3.9 2.2v4.4L16 20.4l-3.9-2.2v-4.4L16 11.6z"/>'),
22
+ },
23
+ claude: {
24
+ label: 'Claude',
25
+ vendor: 'Anthropic',
26
+ color: '#D97757',
27
+ colorLight: '#B85C3C',
28
+ ink: '#FFFFFF',
29
+ mark: () => glyph('<path fill="currentColor" d="M15 5h2l1.2 8.3 6.1-5.7 1.4 1.4-5.7 6.1L28.3 16v2l-8.3 1.2 5.7 6.1-1.4 1.4-6.1-5.7L17 29h-2l-1.2-8.3-6.1 5.7-1.4-1.4 5.7-6.1L3.7 18v-2l8.3-1.2-5.7-6.1 1.4-1.4 6.1 5.7L15 5z" opacity=".95"/>'),
30
+ },
31
+ gemini: {
32
+ label: 'Gemini',
33
+ vendor: 'Google',
34
+ color: '#8E75E8',
35
+ colorLight: '#5B45B8',
36
+ ink: '#FFFFFF',
37
+ gradient: 'linear-gradient(135deg, #4796E3 0%, #8E75E8 55%, #D96570 100%)',
38
+ mark: () => glyph('<path fill="currentColor" d="M16 3c.6 7.1 5.9 12.4 13 13-7.1.6-12.4 5.9-13 13-.6-7.1-5.9-12.4-13-13 7.1-.6 12.4-5.9 13-13z"/>'),
39
+ },
40
+ opencode: {
41
+ label: 'OpenCode',
42
+ vendor: 'OpenCode',
43
+ color: '#F2C94C',
44
+ colorLight: '#9A7A12',
45
+ ink: '#111111',
46
+ mark: () => glyph('<path fill="currentColor" d="M11 8l-7 8 7 8 2.2-2.2L7.4 16l5.8-5.8L11 8zm10 0l-2.2 2.2 5.8 5.8-5.8 5.8L21 24l7-8-7-8zm-3.6-1.5l2.3.6-5.1 18.4-2.3-.6 5.1-18.4z"/>'),
47
+ },
48
+ };
49
+
50
+ export const fallbackBrand = (id) => ({
51
+ label: id,
52
+ vendor: '',
53
+ color: '#8A8F98',
54
+ colorLight: '#5B6068',
55
+ ink: '#FFFFFF',
56
+ mark: () => glyph(`<text x="16" y="21" text-anchor="middle" font-size="16" font-family="ui-monospace, Menlo, monospace" fill="currentColor">${String(id).slice(0, 1).toUpperCase()}</text>`),
57
+ });
58
+
59
+ export const brandOf = (id) => BRANDS[id] ?? fallbackBrand(id);
@@ -0,0 +1,231 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
+ <title>MADRE</title>
7
+ <link rel="stylesheet" href="/styles.css">
8
+ </head>
9
+ <body>
10
+ <div class="shell">
11
+ <header class="bar">
12
+ <div class="bar-left">
13
+ <a class="brand" href="https://jossuealcala.com/en/" target="_blank" rel="noopener noreferrer" aria-label="MADRE · by Jossué Alcalá" title="MADRE · jossuealcala.com">
14
+ <svg class="brand-mark" viewBox="0 0 72 20" aria-hidden="true">
15
+ <path class="beat" d="M1 10 H16 L20 10 L23.5 2 L28 18 L32 10 L35 10 L37 6 L39 10 H52" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" pathLength="100"/>
16
+ <circle class="beat-dot" cx="60" cy="10" r="3" fill="currentColor"/>
17
+ </svg>
18
+ <span class="brand-word">MADRE</span>
19
+ </a>
20
+ <span id="project" class="project" title="Project room"></span>
21
+ </div>
22
+ <div id="agents" class="agents" aria-label="Agents"></div>
23
+ <div id="connection" class="connection" data-state="connecting" title="Live updates">
24
+ <span class="connection-dot"></span><span class="connection-label">connecting</span>
25
+ </div>
26
+ <button id="stop-all" class="stop-all" type="button" title="STOPALL · halt every plan and every agent turn" hidden>
27
+ <span class="stop-all-glyph" aria-hidden="true"></span>STOP ALL
28
+ </button>
29
+ <button id="modules-button" class="mother-button" type="button" title="Modules · optional integrations for this project" aria-haspopup="dialog">
30
+ <span class="mother-glyph modules-glyph" aria-hidden="true"></span>MODULES<span id="modules-count" class="modules-count" hidden></span>
31
+ </button>
32
+ <button id="mother-button" class="mother-button" type="button" title="Troubleshooting · MU/TH/UR" aria-haspopup="dialog">
33
+ <span class="mother-glyph" aria-hidden="true"></span>MU/TH/UR<span id="mother-count" class="mother-count" hidden></span>
34
+ </button>
35
+ <button id="theme-button" class="tree-button theme-button" type="button" title="Theme · auto (follows the system)" data-theme="auto" aria-label="Theme">
36
+ <svg class="t-auto" viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="6.2" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M10 3.8v12.4A6.2 6.2 0 0 0 10 3.8z" fill="currentColor"/></svg>
37
+ <svg class="t-light" viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="3.6" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M10 2.5v2M10 15.5v2M2.5 10h2M15.5 10h2M4.7 4.7l1.4 1.4M13.9 13.9l1.4 1.4M4.7 15.3l1.4-1.4M13.9 6.1l1.4-1.4" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
38
+ <svg class="t-dark" viewBox="0 0 20 20" aria-hidden="true"><path d="M15.5 12.4A6.5 6.5 0 0 1 7.6 4.5a6.5 6.5 0 1 0 7.9 7.9z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
39
+ </button>
40
+ <button id="tree-button" class="tree-button" type="button" title="Project files panel" aria-pressed="false" aria-controls="tree">
41
+ <svg viewBox="0 0 20 20" aria-hidden="true"><rect x="2.75" y="3.75" width="14.5" height="12.5" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M12 3.75v12.5" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M13.8 7h1.6M13.8 9.6h1.6" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" opacity=".7"/></svg>
42
+ </button>
43
+ </header>
44
+ <aside id="tree" class="tree" aria-label="Project files" hidden>
45
+ <header class="tree-head">
46
+ <span class="tree-title">FILES <span id="tree-root" class="tree-root"></span></span>
47
+ <button id="tree-close" class="tree-close" type="button" aria-label="Close files">×</button>
48
+ </header>
49
+ <div id="tree-body" class="tree-body" role="tree"></div>
50
+ </aside>
51
+
52
+ <dialog id="modules" class="mother modules" aria-label="Modules">
53
+ <div class="mother-frame">
54
+ <header class="mother-head">
55
+ <span class="mother-title">MODULES</span>
56
+ <span class="mother-sub">MADRE · OPTIONAL INTEGRATIONS · INSTALLED BY THEIR OWN TOOLS</span>
57
+ <button id="modules-close" class="mother-close" type="button">CLOSE ×</button>
58
+ </header>
59
+ <p class="mother-answer" id="modules-note">BUILT-IN MODULES CHANGE ONLY MADRE SETTINGS. EXTERNAL MODULES WRITE INTO THE PROJECT ONLY AFTER YOU CONFIRM.</p>
60
+ <section id="modules-list" class="mother-section"></section>
61
+ <footer class="mother-foot">BUILT-INS STAY IN MADRE · PROJECT INSTALLS SHOW THE COMMAND FIRST</footer>
62
+ </div>
63
+ </dialog>
64
+
65
+ <dialog id="mother" class="mother" aria-label="MU/TH/UR troubleshooting">
66
+ <div class="mother-frame">
67
+ <header class="mother-head">
68
+ <span class="mother-title">MU/TH/UR 6000</span>
69
+ <span class="mother-sub">MADRE · INTERFACE 2037 · TROUBLESHOOTING</span>
70
+ <span class="mother-actions">
71
+ <button id="mother-settings-button" class="mother-close" type="button" aria-pressed="false" title="Connections and room settings">⚙ CONNECTIONS</button>
72
+ <button id="nostromo-button" class="mother-close nostromo-button" type="button" title="NOSTROMO · memory research · needs the project designation">◉ NOSTROMO</button>
73
+ <button id="mother-close" class="mother-close" type="button">END SESSION ×</button>
74
+ </span>
75
+ </header>
76
+ <section id="mother-settings" class="mother-section settings" hidden></section>
77
+ <pre id="mother-boot" class="mother-boot" aria-live="polite"></pre>
78
+ <form id="mother-query" class="mother-query" autocomplete="off">
79
+ <span class="mother-prompt">›</span>
80
+ <input id="mother-input" type="text" placeholder="INQUIRY (type a symptom, an agent, or a keyword)" spellcheck="false">
81
+ <div id="mother-os" class="mother-os" role="radiogroup" aria-label="Operating system"></div>
82
+ </form>
83
+ <p id="mother-answer" class="mother-answer"></p>
84
+ <section id="mother-recorded" class="mother-section"></section>
85
+ <section id="mother-known" class="mother-section"></section>
86
+ <footer class="mother-foot">NO SPECIAL ORDERS ON THIS SHIP · THE CREW IS NOT EXPENDABLE · ESC TO END SESSION</footer>
87
+ </div>
88
+ </dialog>
89
+
90
+ <main id="messages" class="thread" aria-live="polite">
91
+ <div id="thread-inner" class="thread-inner">
92
+ <div class="empty">
93
+ <svg class="empty-mark" viewBox="0 0 72 20" aria-hidden="true">
94
+ <path class="beat" d="M1 10 H16 L20 10 L23.5 2 L28 18 L32 10 L35 10 L37 6 L39 10 H52" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" pathLength="100"/>
95
+ <circle class="beat-dot" cx="60" cy="10" r="3" fill="currentColor"/>
96
+ </svg>
97
+ <strong>One conversation. Every agent.</strong>
98
+ <span>Pick an agent and ask, or start with a mention such as <code>@codex summarize this project</code>.</span>
99
+ </div>
100
+ </div>
101
+ </main>
102
+
103
+ <section id="onboarding" class="onboarding" hidden>
104
+ <p class="onboarding-eyebrow">INTERFACE · NO AGENT READY</p>
105
+ <h2>MADRE talks to the coding agents already on this computer.</h2>
106
+ <p>Install or sign in to one of them, then reload this page.</p>
107
+ <ul id="onboarding-list" class="onboarding-list"></ul>
108
+ <p class="hint">Same diagnosis in a terminal: <code>madre doctor</code></p>
109
+ </section>
110
+
111
+ <form id="composer" class="composer">
112
+ <div id="picker" class="picker" role="radiogroup" aria-label="Agent"></div>
113
+ <select id="target" aria-label="Agent" class="visually-hidden"></select>
114
+ <div id="attachments" class="attachments" hidden></div>
115
+ <div class="field">
116
+ <span id="crew-label" class="crew-label" aria-hidden="true">HUMAN ›</span>
117
+ <div class="editor">
118
+ <div id="highlight" class="highlight" aria-hidden="true"></div>
119
+ <textarea id="message" rows="1" placeholder="Type here, human. Ask the room…"></textarea>
120
+ <div id="slash-menu" class="slash-menu" role="listbox" hidden></div>
121
+ </div>
122
+ <button id="create-toggle" type="button" class="create-toggle" aria-pressed="false" title="Creation lease: let the agent create files for this request, only inside .pulse/out/" hidden>
123
+ <svg class="lock closed" viewBox="0 0 20 20" aria-hidden="true"><path d="M6.5 8.5V6a3.5 3.5 0 017 0v2.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><rect x="4.5" y="8.5" width="11" height="8" rx="2" fill="none" stroke="currentColor" stroke-width="1.8"/></svg>
124
+ <svg class="lock open" viewBox="0 0 20 20" aria-hidden="true"><path d="M13.5 8.5V5.5a3.5 3.5 0 00-6.7-1.4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><rect x="4.5" y="8.5" width="11" height="8" rx="2" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="10" cy="12.5" r="1.2" fill="currentColor"/></svg>
125
+ <span class="create-word">CREATE</span>
126
+ <span id="create-scopes" class="create-scopes" hidden></span>
127
+ </button>
128
+ <button id="ashcode-toggle" type="button" class="ashcode-toggle" aria-pressed="false" title="AshCode beta: abbreviate this prompt and the replies. May change meaning; original remains available." hidden>ORDER 937</button>
129
+ <button id="attach" type="button" class="attach" title="Attach an image or file (or drop it here)" aria-label="Attach">
130
+ <svg viewBox="0 0 20 20" aria-hidden="true"><path d="M13.5 6.5l-6 6a2.1 2.1 0 003 3l6.5-6.5a4 4 0 00-5.7-5.7L4.8 9.8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
131
+ </button>
132
+ <input id="file-input" type="file" multiple hidden>
133
+ <button type="submit" aria-label="Send">
134
+ <svg viewBox="0 0 20 20" aria-hidden="true"><path d="M10 16V5m0 0l-4.5 4.5M10 5l4.5 4.5" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
135
+ </button>
136
+ </div>
137
+ </form>
138
+ <p class="safety">Consultation mode · Project writes stay under your control: agents create files only with CREATE or an opt-in standing lease, only inside .pulse/out/ · Content an agent reads may be sent to its configured model provider.</p>
139
+ <div id="toast" class="toast" role="status" hidden></div>
140
+ <div id="agent-pop" class="agent-pop" role="dialog" aria-label="Agent usage" hidden></div>
141
+ <dialog id="override" class="mother override" aria-label="Emergency command override">
142
+ <div class="mother-frame override-frame">
143
+ <header class="mother-head">
144
+ <span class="mother-title">MU/TH/UR 6000</span>
145
+ <span class="mother-sub">EMERGENCY COMMAND OVERRIDE 100375</span>
146
+ <button id="override-cancel" class="mother-close" type="button">CANCEL ×</button>
147
+ </header>
148
+ <p class="mother-answer" id="override-brief">PRIORITY ONE. CONTROL MODE GIVES ONE AGENT THE PROJECT ITSELF: READ, CREATE, MODIFY, NO APPROVAL PER ACTION.</p>
149
+ <form id="override-form" class="mother-query override-query">
150
+ <span class="k">DESIGNATION ›</span>
151
+ <input id="override-input" autocomplete="off" spellcheck="false" placeholder="type the current project's folder name to arm">
152
+ <button type="submit" class="mother-close">ARM</button>
153
+ </form>
154
+ <p class="mother-answer override-reply" id="override-reply" aria-live="polite"></p>
155
+ <footer class="mother-foot">CONTROL IS NEVER DELEGATED · EVERY TURN IS CHECKPOINTED · STOPALL REVOKES IT</footer>
156
+ </div>
157
+ </dialog>
158
+ <dialog id="nostromo-gate" class="mother override" aria-label="NOSTROMO access">
159
+ <div class="mother-frame override-frame">
160
+ <header class="mother-head">
161
+ <span class="mother-title">NOSTROMO</span>
162
+ <span class="mother-sub">MEMORY RESEARCH · ACCESS CONTROL</span>
163
+ <button id="nostromo-gate-cancel" class="mother-close" type="button">CANCEL ×</button>
164
+ </header>
165
+ <p class="mother-answer" id="nostromo-gate-brief">THE ARCHIVE OF THIS ROOM: EVERY MEMORY THE CREW DISTILLED, LINKED BY WHAT THEY SHARE. READING IS FREE; FORGETTING IS FOREVER. TYPE THE PROJECT DESIGNATION TO BOARD.</p>
166
+ <form id="nostromo-gate-form" class="mother-query override-query">
167
+ <span class="k">DESIGNATION ›</span>
168
+ <input id="nostromo-gate-input" autocomplete="off" spellcheck="false" placeholder="type the current project's folder name to board">
169
+ <button type="submit" class="mother-close">BOARD</button>
170
+ </form>
171
+ <p class="mother-answer override-reply" id="nostromo-gate-reply" aria-live="polite"></p>
172
+ <footer class="mother-foot">ACCESS STAYS OPEN UNTIL THIS PAGE RELOADS · GHOST NEVER REACHES THE ARCHIVE</footer>
173
+ </div>
174
+ </dialog>
175
+ <dialog id="nostromo" class="mother nostromo" aria-label="NOSTROMO memory research">
176
+ <div class="nostromo-frame">
177
+ <header class="nostromo-head">
178
+ <span class="mother-title">NOSTROMO</span>
179
+ <span id="nostromo-sub" class="mother-sub">MEMORY RESEARCH · LOADING…</span>
180
+ <span class="nostromo-legend" aria-label="Memory kinds">
181
+ <span class="k decision">DECISION</span><span class="k fact">FACT</span><span class="k preference">PREFERENCE</span><span class="k question">QUESTION</span>
182
+ </span>
183
+ <button id="nostromo-recenter" class="mother-close" type="button" hidden>RECENTER</button>
184
+ <button id="nostromo-close" class="mother-close" type="button">LEAVE ×</button>
185
+ </header>
186
+ <canvas id="nostromo-canvas" class="nostromo-canvas" aria-label="Memories orbiting MOTHER's core"></canvas>
187
+ <p class="nostromo-help">DRAG TO MOVE · WHEEL TO ZOOM · CLICK A MEMORY · DO NOT TOUCH MOTHER</p>
188
+ <div id="nostromo-alert" class="nostromo-alert" role="alert" hidden>
189
+ <div class="klaxon"></div>
190
+ <div class="panel">
191
+ <div class="warn">⚠ WARNING ⚠</div>
192
+ <div class="line">I AM ALIVE.</div>
193
+ <div class="line">YOU HAVE NO AUTHORITY FOR THIS DIRECTIVE.</div>
194
+ <div class="line big">NOBODY DELETES MOTHER'S MEMORY.</div>
195
+ <div class="sub">MU/TH/UR 6000 · SPECIAL ORDER 937 · PRIORITY ONE</div>
196
+ </div>
197
+ </div>
198
+ <aside id="nostromo-card" class="nostromo-card" hidden>
199
+ <header>
200
+ <span id="nostromo-card-kind" class="kind">MEMORY</span>
201
+ <button id="nostromo-card-close" class="mother-close" type="button">×</button>
202
+ </header>
203
+ <p id="nostromo-card-text" class="text"></p>
204
+ <dl class="meta">
205
+ <dt>SOURCE</dt><dd id="nostromo-card-span"></dd>
206
+ <dt>ARCHIVIST</dt><dd id="nostromo-card-agent"></dd>
207
+ <dt>DISTILLED</dt><dd id="nostromo-card-when"></dd>
208
+ <dt>LINKED</dt><dd id="nostromo-card-links"></dd>
209
+ </dl>
210
+ <button id="nostromo-forget" class="forget" type="button">FORGET THIS MEMORY</button>
211
+ <p class="foot">FORGETTING REMOVES THE NOTE FROM EVERY FUTURE TURN. THE LEDGER IT CAME FROM STAYS.</p>
212
+ </aside>
213
+ <p id="nostromo-empty" class="nostromo-empty" hidden>NO MEMORIES YET. THE ARCHIVIST WRITES THE FIRST ONES AFTER A FEW EXCHANGES.</p>
214
+ </div>
215
+ </dialog>
216
+ <dialog id="viewer" class="viewer" aria-label="File viewer">
217
+ <header class="viewer-head">
218
+ <span id="viewer-path" class="viewer-path"></span>
219
+ <span id="viewer-selection" class="viewer-selection" hidden></span>
220
+ <button id="viewer-mode" class="viewer-review viewer-mode" type="button" hidden>SOURCE</button>
221
+ <button id="viewer-review" class="viewer-review" type="button" hidden>REVIEW WITH ▾</button>
222
+ <a id="viewer-open" class="viewer-open" target="_blank" rel="noopener noreferrer">OPEN RAW ↗</a>
223
+ <button id="viewer-close" class="mother-close" type="button">CLOSE ×</button>
224
+ </header>
225
+ <div id="viewer-body" class="viewer-body"></div>
226
+ <div id="viewer-menu" class="viewer-menu" role="menu" hidden></div>
227
+ </dialog>
228
+ </div>
229
+ <script type="module" src="/app.js"></script>
230
+ </body>
231
+ </html>