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,722 +1,5 @@
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
- * fingerprint.js — the φ(α) panel: authorship key, sealed records, the vault.
10
- *
11
- * The IPC surface has existed since 5072779 (`fingerprintStatus` …
12
- * `fingerprintVaultAudit`, desktop/preload.js:171-183, main.js:750-765) and
13
- * until now nothing in desktop/renderer/ called any of it —
14
- * `grep -rn fingerprint desktop/renderer/` returned nothing, so a GUI user had
15
- * no button for a feature the terminal has had since `aegiscode fingerprint`.
16
- * This file is that button.
17
- *
18
- * It obeys the same two rules the backend half already keeps:
19
- *
20
- * 1. **The key never reaches the DOM.** No payload from main carries
21
- * `secret`, and nothing here invents a field that might. What gets painted
22
- * is `status.keyFingerprint` — the derived 12-hex tag — which is public by
23
- * construction (client/authorship-key.js prints exactly this).
24
- * 2. **A refusal is shown, not swallowed.** Every handler resolves
25
- * `{ ok: false, error }` instead of throwing, so the panel prints the
26
- * reason ("this key file is readable by other users", "rclone is not
27
- * installed") rather than an empty state that reads like success.
28
- *
29
- * Pure formatting (`keyLine`, `anchorVerdict`, `auditVerdict`, …) is kept
30
- * separate from painting so test/renderer-fingerprint.test.mjs can assert the
31
- * strings that carry the three-claim anchor verdict without standing up a DOM —
32
- * and the functions it asserts are the exact ones that paint.
33
- *
34
- * Classic script, no imports: loaded before app.js by index.html and called
35
- * from init(). It does not auto-mount, so requiring it under test has no side
36
- * effects.
37
- */
38
-
39
- (function (root, factory) {
40
- const api = factory();
41
- // `module` first: under Node the test requires this file directly, and the
42
- // renderer's `window` is not there.
43
- if (typeof module !== 'undefined' && module.exports) module.exports = api;
44
- if (root) root.aegisFingerprint = api;
45
- })(typeof window !== 'undefined' ? window : null, function () {
46
- /** Every id this file reads. The test asserts each one exists in index.html —
47
- * a typo here is otherwise a silent no-op, which is how a panel ships with
48
- * one dead button and no error. */
49
- const IDS = {
50
- cardStatus: 'fp-card-status',
51
- cardOpen: 'fp-open',
52
- overlay: 'fp-overlay',
53
- backdrop: 'fp-backdrop',
54
- close: 'fp-close',
55
- keySource: 'fp-key-source',
56
- keyPath: 'fp-key-path',
57
- keyTag: 'fp-key-tag',
58
- keyWarning: 'fp-key-warning',
59
- vaultTarget: 'fp-vault-target',
60
- vaultBackendDrive: 'fp-vault-backend-drive',
61
- vaultBackendCloud: 'fp-vault-backend-aegiscloud',
62
- vaultBackendOff: 'fp-vault-backend-off',
63
- label: 'fp-label',
64
- publishOnMint: 'fp-publish-on-mint',
65
- autoSeal: 'fp-auto-seal',
66
- autoSealLabel: 'fp-auto-seal-label',
67
- autoHint: 'fp-auto-hint',
68
- sealNow: 'fp-seal-now',
69
- keygen: 'fp-keygen',
70
- seal: 'fp-seal',
71
- sealCard: 'fp-seal-card',
72
- verify: 'fp-verify',
73
- publish: 'fp-publish',
74
- anchor: 'fp-anchor',
75
- backupTo: 'fp-backup-to',
76
- backup: 'fp-backup',
77
- vault: 'fp-vault',
78
- audit: 'fp-audit',
79
- vaultList: 'fp-vault-list',
80
- out: 'fp-out',
81
- driveStatus: 'fp-drive-status',
82
- driveSignIn: 'fp-drive-signin',
83
- driveConnect: 'fp-drive-connect',
84
- driveSyncNow: 'fp-drive-sync',
85
- };
86
-
87
- // ------------------------------------------------------------- formatting
88
-
89
- const dash = (v) => (v === null || v === undefined || v === '' ? '–' : String(v));
90
-
91
- /** The one-line state that goes in the sidebar card, where there is room for
92
- * a sentence and not a table. */
93
- function keyLine(status) {
94
- if (!status) return 'checking…';
95
- if (status.ok === false) return status.error || 'unavailable';
96
- if (!status.configured) return 'no key — mint one to seal records';
97
- return `key ${dash(status.keyFingerprint)} · ${dash(status.keySource)}`;
98
- }
99
-
100
- /** Everything the panel can say about the key without saying the key. */
101
- function statusRows(status) {
102
- if (!status || status.ok === false) {
103
- return [{ k: 'state', v: (status && status.error) || 'unavailable' }];
104
- }
105
- const rows = [
106
- { k: 'key', v: status.configured ? status.keyFingerprint : 'none' },
107
- { k: 'source', v: dash(status.keySource) },
108
- { k: 'expected at', v: dash(status.defaultKeyPath) },
109
- { k: 'history', v: status.historyPresent ? status.historyPath : 'none yet' },
110
- ];
111
- // Only when it resolves somewhere else than expected is it worth a row: a
112
- // key coming from AEGIS_AUTHORSHIP_SECRET or --secret is exactly the
113
- // situation where "which key am I actually signing with" needs an answer.
114
- if (status.configured && status.keyPath && status.keyPath !== status.defaultKeyPath) {
115
- rows.splice(2, 0, { k: 'in use', v: status.keyPath });
116
- }
117
- return rows;
118
- }
119
-
120
- /** The 0644 refusal is the one failure where the fix is a command the user
121
- * can run, so it is surfaced as a warning line rather than buried in the
122
- * result pane. */
123
- function keyWarning(status) {
124
- const err = status && status.error ? String(status.error) : '';
125
- return /chmod/i.test(err) ? err : '';
126
- }
127
-
128
- /** One line per vault entry. Tolerant of the key the shared module chooses,
129
- * because the panel's job here is to show what came back, not to insist. */
130
- function vaultRows(list) {
131
- if (!list || list.ok === false) {
132
- return [{ name: 'vault unavailable', detail: (list && list.error) || 'unknown error' }];
133
- }
134
- const items = Array.isArray(list.records) ? list.records
135
- : Array.isArray(list.items) ? list.items
136
- : Array.isArray(list.entries) ? list.entries
137
- : [];
138
- if (!items.length) return [{ name: 'nothing published yet', detail: '' }];
139
- return items.map((it) => {
140
- const name = it.name || it.file || it.path || it.id || 'record';
141
- const parts = [it.id, it.publishedAt || it.createdAt, it.url].filter(Boolean);
142
- return { name: String(name), detail: parts.join(' · ') };
143
- });
144
- }
145
-
146
- /**
147
- * The one-line summary of the Google Drive card — `desktop/lib/
148
- * google-connect.js`'s `status()` shape, `{signedIn, connected, email}`.
149
- *
150
- * Three distinct states, because "not connected" means something different
151
- * depending on whether this machine has signed in with Google at all:
152
- * connecting Drive before signing in attaches the tokens to a different
153
- * account than the one this machine polls (see google-connect.js's header
154
- * comment), so the line has to say WHICH step is next, not just "no".
155
- */
156
- function driveLine(status) {
157
- if (!status || status.ok === false) return (status && status.error) || 'unavailable';
158
- if (!status.signedIn) return 'not signed in — sign in with Google first';
159
- if (!status.connected) return 'signed in · Google Drive not connected yet';
160
- return `Google Drive connected${status.email ? ` · ${status.email}` : ''}`;
161
- }
162
-
163
- /** The anchor answer, kept as three separate claims on purpose: "the file is
164
- * gone", "the file was replaced" and "the key that sealed it has moved on"
165
- * are three different situations, and one boolean hides the interesting one
166
- * (client/record-vault.js checkAnchor). `present` is read as well as
167
- * `presence` because that spelling was a real bug once. */
168
- function anchorVerdict(a) {
169
- if (!a || a.ok === false) {
170
- return { ok: false, label: (a && a.error) || 'anchor check failed', claims: [] };
171
- }
172
- const present = a.present !== undefined ? a.present : a.presence;
173
- const claims = [
174
- { k: 'present', v: present },
175
- { k: 'unchanged', v: a.unchanged },
176
- { k: 'attested', v: a.attested },
177
- ];
178
- const ok = present === true && a.unchanged === true && a.attested === true;
179
- return {
180
- ok,
181
- // Ambiguity is not success: an undefined claim is reported as unknown,
182
- // never folded into a yes.
183
- label: ok ? 'the published copy is this record' : 'the published copy is not verifiably this record',
184
- claims,
185
- };
186
- }
187
-
188
- /** What the audit found. A finding is a fact about the vault, not an error
189
- * from it, so a clean run says so explicitly rather than showing nothing. */
190
- function auditVerdict(a) {
191
- if (!a || a.ok === false) return { ok: false, label: (a && a.error) || 'audit failed', findings: [] };
192
- const findings = Array.isArray(a.findings) ? a.findings
193
- : Array.isArray(a.leaks) ? a.leaks
194
- : Array.isArray(a.hits) ? a.hits
195
- : [];
196
- if (findings.length) {
197
- return { ok: false, label: `${findings.length} item(s) in the vault look like key material`, findings };
198
- }
199
- return { ok: true, label: 'no key material in the vault', findings: [] };
200
- }
201
-
202
- /** A minted record as the panel reports it. The local file is the evidence —
203
- * a failed publish does not unseal it (desktop/lib/fingerprint.js mint) —
204
- * so an upload failure is said beside the seal, not instead of it. */
205
- function sealLine(r) {
206
- if (!r || r.ok === false) return { ok: false, text: (r && r.error) || 'seal failed' };
207
- const rec = r.record || {};
208
- const bits = [`sealed ${dash(rec.id)}`, `key ${dash(rec.keyFingerprint)}`, r.out];
209
- // Both halves of the session, in the record and in the line: the key that
210
- // sealed it, and the mind that answered. An un-nominated AI credential reads
211
- // as "ai unobserved", never as a borrowed key.
212
- bits.push(
213
- rec.aiKey
214
- ? `ai ${dash(rec.ai)} on ${dash(rec.aiKey)}`
215
- : `ai unobserved${rec.ai ? ` (${dash(rec.ai)})` : ''}`,
216
- );
217
- if (r.vault && r.vault.url) bits.push(`published ${r.vault.url}`);
218
- else if (r.vault) bits.push(`published ${dash(r.vault.remote || r.vault.path)}`);
219
- if (r.vaultError) bits.push(`publish failed: ${r.vaultError}`);
220
- return { ok: true, text: bits.filter(Boolean).join(' · '), file: r.out || null, record: r.record || null };
221
- }
222
-
223
- // ---------------------------------------------------------------- painting
224
-
225
- /**
226
- * One line describing the auto-seal state, in the same voice as the rest of
227
- * the panel: what is armed, what it has filed, and the last thing that went
228
- * wrong. Pure, so the test asserts the string the panel paints.
229
- */
230
- function autoMintLine(s) {
231
- if (!s || typeof s !== 'object') return '–';
232
- if (s.enabled !== true) return 'off — sealing stays a button';
233
- const bits = [];
234
- const secs = Number(s.quietMs);
235
- bits.push(Number.isFinite(secs) ? `seals ${Math.round(secs / 1000)}s after the last turn` : 'seals when the session goes quiet');
236
- if (s.publish) bits.push('publishes to the vault');
237
- if (s.pendingTurns) bits.push(`${s.pendingTurns} turn${s.pendingTurns === 1 ? '' : 's'} pending`);
238
- if (s.seals) bits.push(`${s.seals} sealed`);
239
- if (s.failures) bits.push(`${s.failures} failed`);
240
- if (s.lastError) bits.push(`last error: ${s.lastError}`);
241
- return bits.join(' · ');
242
- }
243
-
244
- function mount({ api, doc, auto } = {}) {
245
- const d = doc || (typeof document !== 'undefined' ? document : null);
246
- if (!d) throw new Error('fingerprint: mount needs a document');
247
- const bridge = api || (typeof window !== 'undefined' ? window.aegis : null);
248
- // The automation bridge (main.js registerAutoIpc). Injectable for the same
249
- // reason `api` is, and optional: a preload predating that surface leaves the
250
- // auto-seal control inert rather than throwing at mount.
251
- const autoBridge = auto || (typeof window !== 'undefined' ? window.auto : null);
252
-
253
- const el = (id) => d.getElementById(id);
254
- const state = { status: null, last: null, busy: false, mounted: true };
255
-
256
- function say(text, bad) {
257
- const out = el(IDS.out);
258
- if (!out) return;
259
- out.textContent = text;
260
- out.className = bad ? 'fp-out fp-bad' : 'fp-out';
261
- }
262
-
263
- /** Every bridge call goes through here. A handler that rejects (Electron
264
- * turns a thrown main-process error into a rejected invoke) must not leave
265
- * the panel mid-action, so the catch produces the same `{ ok, error }`
266
- * shape the handlers themselves do. */
267
- async function call(name, payload) {
268
- if (!bridge || typeof bridge[name] !== 'function') {
269
- return { ok: false, error: `this build has no ${name} bridge` };
270
- }
271
- try {
272
- const r = await bridge[name](payload);
273
- if (!r || typeof r !== 'object') return { ok: false, error: `${name} returned nothing` };
274
- return r;
275
- } catch (e) {
276
- return { ok: false, error: (e && e.message) || String(e) };
277
- }
278
- }
279
-
280
- function paintStatus() {
281
- const s = state.status;
282
- const card = el(IDS.cardStatus);
283
- if (card) card.textContent = keyLine(s);
284
- const src = el(IDS.keySource);
285
- const pth = el(IDS.keyPath);
286
- const tag = el(IDS.keyTag);
287
- const warn = el(IDS.keyWarning);
288
- const vt = el(IDS.vaultTarget);
289
- const rows = statusRows(s);
290
- const pick = (k) => {
291
- const row = rows.find((r) => r.k === k);
292
- return row ? row.v : '–';
293
- };
294
- if (src) src.textContent = pick('source');
295
- if (pth) pth.textContent = s && s.keyPath ? s.keyPath : pick('expected at');
296
- if (tag) tag.textContent = s && s.configured ? dash(s.keyFingerprint) : 'none';
297
- if (warn) {
298
- const w = keyWarning(s);
299
- warn.textContent = w;
300
- warn.hidden = !w;
301
- }
302
- if (vt && s && s.vault) {
303
- // The default backend is the account (`aegiscloud`), where "gdrive" is
304
- // not where anything goes — printing `gdrive:AEGIS-AGPL/records` for a
305
- // cloud publish would name a folder the record never touches. Say the
306
- // backend for that case, and keep the remembered rclone target for the
307
- // case where it IS a remote. 'off' names neither — nothing is resolved
308
- // behind it (desktop/lib/fingerprint.js's status() short-circuits
309
- // before ever calling vaultTarget()), so the line says so plainly
310
- // rather than printing the leftover "–:–" two dashes would read as.
311
- vt.textContent = s.vault.backend === 'off'
312
- ? 'nothing — sync is off'
313
- : s.vault.backend === 'aegiscloud'
314
- ? `aegiscloud:${dash(s.vault.recordsDir)}`
315
- : `${dash(s.vault.remote)}:${dash(s.vault.recordsDir || s.vault.root)}`;
316
- } else if (vt) {
317
- vt.textContent = '–';
318
- }
319
- // A key that is present makes keygen a REPLACE, which orphans every
320
- // record sealed with the old one. Saying that on the button is the whole
321
- // warning; the confirm below is the second line of defence.
322
- const kg = el(IDS.keygen);
323
- if (kg) kg.textContent = s && s.configured ? 'Replace key…' : 'Mint key';
324
- }
325
-
326
- /**
327
- * The three backend buttons' pressed state.
328
- *
329
- * Deliberately read from `getVaultBackend` (the preference the user set),
330
- * NOT from `status().vault.backend` (the resolved record-vault.js
331
- * backend id): the preference vocabulary is 'aegiscloud' | 'drive' | 'off',
332
- * the resolved one is 'aegiscloud' | 'rclone' | 'off' — a 'drive'
333
- * preference resolves to 'rclone', so pressing "Google Drive" from status
334
- * alone would never read back as pressed.
335
- */
336
- function paintVaultBackend(backend) {
337
- const map = {
338
- drive: el(IDS.vaultBackendDrive),
339
- aegiscloud: el(IDS.vaultBackendCloud),
340
- off: el(IDS.vaultBackendOff),
341
- };
342
- for (const [key, btn] of Object.entries(map)) {
343
- if (btn) btn.setAttribute('aria-pressed', String(key === backend));
344
- }
345
- }
346
-
347
- async function loadVaultBackend() {
348
- const r = await call('getVaultBackend');
349
- state.vaultBackend = r && r.backend ? r.backend : 'aegiscloud';
350
- paintVaultBackend(state.vaultBackend);
351
- return state.vaultBackend;
352
- }
353
-
354
- const doSetVaultBackend = (backend) =>
355
- action(`vault: ${backend === 'drive' ? 'Google Drive' : backend === 'off' ? 'no sync' : 'AE'}`, async () => {
356
- const r = await call('setVaultBackend', backend);
357
- return r && r.backend ? { ok: true, ...r } : { ok: false, error: 'could not save that' };
358
- }, {
359
- // The "publishes to" line names the backend too, so both it and the
360
- // three buttons must repaint the moment the preference changes — not
361
- // just on the next open().
362
- after: (r) => (r && r.ok ? refresh() : null),
363
- say: (r) => (r.ok ? `vault backend set to ${dash(r.backend)}` : `could not save that: ${r.error}`),
364
- });
365
-
366
- /**
367
- * Google Drive — memory backup (desktop/lib/google-connect.js, exposed
368
- * over `aegis.driveSignIn`/`driveConnect`/`driveStatus`/`driveSyncMemory`).
369
- *
370
- * Distinct from the vault backend buttons above: those choose where a
371
- * SEALED φ(α) RECORD is published (rclone or the account vault). This is
372
- * the account's AEGIS CLOUD MEMORY, backed up to the user's own Drive
373
- * through real Google OAuth — unrelated data, unrelated credential.
374
- */
375
- function paintDrive(status) {
376
- const line = el(IDS.driveStatus);
377
- if (line) line.textContent = driveLine(status);
378
- const connectBtn = el(IDS.driveConnect);
379
- if (connectBtn) connectBtn.disabled = Boolean(status && !status.signedIn);
380
- const syncBtn = el(IDS.driveSyncNow);
381
- if (syncBtn) syncBtn.disabled = Boolean(!status || !status.connected);
382
- }
383
-
384
- async function loadDriveStatus() {
385
- const r = await call('driveStatus');
386
- state.drive = r;
387
- paintDrive(r);
388
- return r;
389
- }
390
-
391
- const doDriveSignIn = () =>
392
- action('sign in with Google', () => call('driveSignIn'), {
393
- after: (r) => (r && r.ok ? loadDriveStatus() : null),
394
- say: (r) => (r.ok ? `signed in${r.username ? ` as ${r.username}` : ''}` : `sign-in failed: ${r.error}`),
395
- });
396
-
397
- const doDriveConnect = () =>
398
- action('connect Google Drive', () => call('driveConnect'), {
399
- after: (r) => (r && r.ok ? loadDriveStatus() : null),
400
- say: (r) => (r.ok ? `Google Drive connected${r.email ? ` · ${r.email}` : ''}` : `could not connect: ${r.error}`),
401
- });
402
-
403
- const doDriveSyncNow = () =>
404
- action('back up memory to Drive', () => call('driveSyncMemory'), {
405
- say: (r) => (r.ok
406
- ? `backed up ${r.count} memory entr${r.count === 1 ? 'y' : 'ies'}${r.webViewLink ? ` → ${r.webViewLink}` : ''}`
407
- : `backup failed: ${r.error}`),
408
- });
409
-
410
- function paintVault(list) {
411
- const ul = el(IDS.vaultList);
412
- if (!ul) return;
413
- ul.textContent = '';
414
- for (const row of vaultRows(list)) {
415
- const li = d.createElement('li');
416
- li.className = 'fp-vault-row';
417
- const name = d.createElement('span');
418
- name.className = 'fp-vault-name';
419
- name.textContent = row.name;
420
- li.appendChild(name);
421
- if (row.detail) {
422
- const det = d.createElement('span');
423
- det.className = 'fp-vault-detail';
424
- det.textContent = row.detail;
425
- li.appendChild(det);
426
- }
427
- ul.appendChild(li);
428
- }
429
- }
430
-
431
- async function refresh() {
432
- state.status = await call('fingerprintStatus');
433
- paintStatus();
434
- await loadAutoMint();
435
- await loadVaultBackend();
436
- await loadDriveStatus();
437
- return state.status;
438
- }
439
-
440
- /**
441
- * The auto-seal control (settings.js `__autoMint`, through lib/auto.js).
442
- *
443
- * Read-only paint from MAIN, for the reason the rest of this panel is: the
444
- * renderer must never be the place that decides a record gets filed, and a
445
- * checkbox state this file invented would disagree with the timer the moment
446
- * anything else (the queue card's window, a restart) changed the setting.
447
- */
448
- function renderAutoMint(s) {
449
- if (!s || typeof s !== 'object') return;
450
- const sw = el(IDS.autoSeal);
451
- if (sw) sw.checked = s.enabled === true;
452
- const label = el(IDS.autoSealLabel);
453
- if (label) label.textContent = s.enabled ? 'sealing automatically when quiet' : 'seal automatically when quiet';
454
- const hint = el(IDS.autoHint);
455
- if (!hint) return;
456
- hint.textContent = autoMintLine(s);
457
- }
458
-
459
- async function loadAutoMint() {
460
- if (!autoBridge || typeof autoBridge.status !== 'function') {
461
- const hint = el(IDS.autoHint);
462
- // Said plainly rather than left blank: an inert toggle with no
463
- // explanation is the failure mode this whole panel exists to avoid.
464
- if (hint) hint.textContent = 'this build has no automation bridge — sealing stays a button';
465
- return null;
466
- }
467
- try {
468
- const r = await autoBridge.status();
469
- if (r && r.mint) renderAutoMint(r.mint);
470
- return r;
471
- } catch (e) {
472
- const hint = el(IDS.autoHint);
473
- if (hint) hint.textContent = `automation status unavailable: ${(e && e.message) || String(e)}`;
474
- return null;
475
- }
476
- }
477
-
478
- /** Persist the switch through main and paint main's answer. */
479
- async function setAutoMint(enabled) {
480
- if (!autoBridge || typeof autoBridge.setMint !== 'function') return null;
481
- const publish = Boolean(el(IDS.publishOnMint) && el(IDS.publishOnMint).checked);
482
- try {
483
- const r = await autoBridge.setMint({ enabled: Boolean(enabled), publish });
484
- if (r && r.mint) renderAutoMint(r.mint);
485
- else if (r && r.ok === false) say(r.error || 'could not save that', true);
486
- return r;
487
- } catch (e) {
488
- say(`could not save that: ${(e && e.message) || String(e)}`, true);
489
- return null;
490
- }
491
- }
492
-
493
- /** Seal right now, through the automation rather than the raw mint: the
494
- * same call resets the pending-turn counter, so the quiet timer will not
495
- * file a second identical record for this session. */
496
- async function doSealNow() {
497
- const publish = Boolean(el(IDS.publishOnMint) && el(IDS.publishOnMint).checked);
498
- return action('seal now', async () => {
499
- if (!autoBridge || typeof autoBridge.sealNow !== 'function') {
500
- return { ok: false, error: 'this build has no automation bridge' };
501
- }
502
- return autoBridge.sealNow(publish);
503
- }, {
504
- paint: (r) => { if (r && r.ok && r.out) state.last = { file: r.out, record: r.record || null }; },
505
- say: (r) => {
506
- if (!r) return 'seal now: nothing happened';
507
- if (r.ok === false) return `seal failed: ${r.error || 'unknown'}`;
508
- if (r.type === 'skip') return `nothing to seal (${r.reason || 'no new turns'})`;
509
- const line = sealLine(r);
510
- return line.ok ? `sealed ${dash((r.record || {}).id)} → ${dash(r.out)}` : `seal failed: ${line.text}`;
511
- },
512
- });
513
- }
514
-
515
- /** Run one action, repaint what it changed, and say what it did. `confirm`
516
- * is injectable so the harness can answer it deterministically. */
517
- async function action(label, fn, opts) {
518
- const o = opts || {};
519
- if (state.busy) return null;
520
- state.busy = true;
521
- say(`${label}…`, false);
522
- try {
523
- const r = await fn();
524
- if (o.paint) o.paint(r);
525
- if (o.after) await o.after(r);
526
- say(o.say ? o.say(r) : `${label}: ${r && r.ok === false ? r.error : 'done'}`, Boolean(r && r.ok === false));
527
- return r;
528
- } finally {
529
- state.busy = false;
530
- }
531
- }
532
-
533
- const doKeygen = () =>
534
- action('mint key', () => call('fingerprintKeygen', {
535
- force: Boolean(state.status && state.status.configured),
536
- }), {
537
- // Only after a successful mint: status moved from "no key" to a key,
538
- // and repainting is what turns "Mint key" into "Replace key…".
539
- after: refresh,
540
- say: (r) => (r.ok
541
- ? `minted ${dash(r.keyFingerprint)} at ${dash(r.path)}`
542
- : `mint failed: ${r.error}`),
543
- });
544
-
545
- const doSeal = () =>
546
- action('seal session', () => call('fingerprintMint', {
547
- label: (el(IDS.label) && el(IDS.label).value.trim()) || null,
548
- publish: Boolean(el(IDS.publishOnMint) && el(IDS.publishOnMint).checked),
549
- }), {
550
- paint: (r) => { if (r.ok) state.last = { file: r.out, record: r.record }; },
551
- say: (r) => {
552
- const line = sealLine(r);
553
- return line.ok ? `sealed ${dash((r.record || {}).id)} → ${dash(r.out)}` : `seal failed: ${line.text}`;
554
- },
555
- });
556
-
557
- /** Verify/publish/anchor act on the record this panel sealed. A path is
558
- * preferred over the in-memory record because the sha256 in the anchor is
559
- * then about the bytes on disk rather than a re-serialisation of them
560
- * (desktop/lib/fingerprint.js publish makes the same argument) — so `file`
561
- * wins when there is one, and the record is the fallback for a host where
562
- * the write failed but the seal did not. */
563
- const doVerify = () =>
564
- action('verify', async () => {
565
- if (state.last && state.last.file) return call('fingerprintVerify', { file: state.last.file });
566
- if (state.last && state.last.record) return call('fingerprintVerify', { record: state.last.record });
567
- return { ok: false, error: 'nothing sealed in this window yet — seal a session first' };
568
- }, {
569
- say: (r) => (r.ok ? `verified ${dash(r.id)} · key ${dash(r.keySource)}` : `not verified: ${r.error || r.reason}`),
570
- });
571
-
572
- const doPublish = () =>
573
- action('publish', () => {
574
- if (state.last && state.last.file) return call('fingerprintPublish', { file: state.last.file });
575
- if (state.last && state.last.record) return call('fingerprintPublish', { record: state.last.record });
576
- return { ok: false, error: 'nothing sealed in this window yet — seal a session first' };
577
- }, {
578
- say: (r) => (r.ok
579
- ? `published ${dash(r.url || r.path)}${r.sha256 ? ` · sha256 ${String(r.sha256).slice(0, 12)}` : ''}`
580
- : `publish failed: ${r.error}`),
581
- });
582
-
583
- const doAnchor = () =>
584
- action('check anchor', () => {
585
- if (state.last && state.last.file) return call('fingerprintAnchor', { file: state.last.file });
586
- return call('fingerprintAnchor', {});
587
- }, {
588
- say: (r) => {
589
- const v = anchorVerdict(r);
590
- const claims = v.claims.map((c) => `${c.k} ${c.v === true ? 'yes' : c.v === false ? 'no' : 'unknown'}`).join(' · ');
591
- return `${v.label}${claims ? ` — ${claims}` : ''}`;
592
- },
593
- });
594
-
595
- const doBackup = () =>
596
- action('back up key', () => {
597
- const to = el(IDS.backupTo) && el(IDS.backupTo).value.trim();
598
- if (!to) return { ok: false, error: 'backup needs a destination directory' };
599
- return call('fingerprintBackup', { to });
600
- }, {
601
- say: (r) => (r.ok ? `key copied to ${dash(r.to || r.path)}` : `backup refused: ${r.error}`),
602
- });
603
-
604
- const doVault = () =>
605
- action('list vault', () => call('fingerprintVault'), {
606
- paint: paintVault,
607
- say: (r) => (r.ok ? `vault listed — ${vaultRows(r).length} entry(ies)` : `vault unavailable: ${r.error}`),
608
- });
609
-
610
- const doAudit = () =>
611
- action('audit vault', () => call('fingerprintVaultAudit'), {
612
- say: (r) => {
613
- const v = auditVerdict(r);
614
- const names = v.findings.map((f) => (f && (f.path || f.name)) || String(f)).slice(0, 3);
615
- return `${v.label}${names.length ? ` — ${names.join(', ')}` : ''}`;
616
- },
617
- });
618
-
619
- const isOpen = () => {
620
- const o = el(IDS.overlay);
621
- return Boolean(o) && !o.hidden;
622
- };
623
-
624
- async function open() {
625
- const o = el(IDS.overlay);
626
- if (!o) return;
627
- o.hidden = false;
628
- // Body class so the page behind cannot scroll under the sheet, matching
629
- // the memory inspector's `.memory-open` rule.
630
- if (d.body && d.body.classList) d.body.classList.add('fp-open');
631
- await refresh();
632
- await doVault();
633
- const f = el(IDS.close);
634
- if (f) f.focus();
635
- }
636
-
637
- function close() {
638
- const o = el(IDS.overlay);
639
- if (!o) return;
640
- o.hidden = true;
641
- if (d.body && d.body.classList) d.body.classList.remove('fp-open');
642
- const btn = el(IDS.cardOpen);
643
- if (btn) btn.focus();
644
- }
645
-
646
- // ------------------------------------------------------------- listeners
647
- // Bound here rather than in app.js so the panel owns its own markup: every
648
- // id in IDS is read by this file only, and the test asserts they all exist.
649
- const bind = (id, type, fn) => {
650
- const node = el(id);
651
- if (node) node.addEventListener(type, fn);
652
- };
653
-
654
- bind(IDS.cardOpen, 'click', open);
655
- bind(IDS.close, 'click', close);
656
- bind(IDS.backdrop, 'click', close);
657
- bind(IDS.keygen, 'click', doKeygen);
658
- bind(IDS.seal, 'click', doSeal);
659
- // The sidebar's one-click seal runs the same action; on success it also
660
- // opens the panel, because a record filed silently behind a closed sheet is
661
- // indistinguishable from a button that did nothing.
662
- bind(IDS.sealCard, 'click', async () => {
663
- const r = await doSeal();
664
- if (r && r.ok) await open();
665
- });
666
- bind(IDS.verify, 'click', doVerify);
667
- bind(IDS.publish, 'click', doPublish);
668
- bind(IDS.anchor, 'click', doAnchor);
669
- bind(IDS.backup, 'click', doBackup);
670
- bind(IDS.vault, 'click', doVault);
671
- bind(IDS.audit, 'click', doAudit);
672
- bind(IDS.vaultBackendDrive, 'click', () => doSetVaultBackend('drive'));
673
- bind(IDS.vaultBackendCloud, 'click', () => doSetVaultBackend('aegiscloud'));
674
- bind(IDS.vaultBackendOff, 'click', () => doSetVaultBackend('off'));
675
- bind(IDS.driveSignIn, 'click', doDriveSignIn);
676
- bind(IDS.driveConnect, 'click', doDriveConnect);
677
- bind(IDS.driveSyncNow, 'click', doDriveSyncNow);
678
- // The auto-seal switch is the ONE control on this panel that can file a
679
- // record with nobody watching, so it is the one that must repaint from
680
- // main's answer rather than from the checkbox the user just clicked.
681
- if (el(IDS.autoSeal)) {
682
- bind(IDS.autoSeal, 'change', () => {
683
- const box = el(IDS.autoSeal);
684
- return setAutoMint(Boolean(box && box.checked));
685
- });
686
- }
687
- if (el(IDS.sealNow)) bind(IDS.sealNow, 'click', doSealNow);
688
-
689
- return {
690
- open,
691
- close,
692
- isOpen,
693
- refresh,
694
- loadAutoMint,
695
- state,
696
- // Exposed for the test and for app.js's on-demand repaint. Every one is
697
- // idempotent, so calling them from a console is safe.
698
- actions: {
699
- doKeygen, doSeal, doSealNow, doVerify, doPublish, doAnchor, doBackup, doVault, doAudit,
700
- doSetVaultBackend, doDriveSignIn, doDriveConnect, doDriveSyncNow,
701
- },
702
- loadVaultBackend,
703
- loadDriveStatus,
704
- };
705
- }
706
-
707
- return {
708
- IDS,
709
- mount,
710
- // Pure, and asserted as such by the test.
711
- keyLine,
712
- statusRows,
713
- keyWarning,
714
- vaultRows,
715
- anchorVerdict,
716
- auditVerdict,
717
- sealLine,
718
- autoMintLine,
719
- driveLine,
720
- dash,
721
- };
722
- });
3
+ "use strict";!function(e,t){const n=function(){const e={cardStatus:"fp-card-status",cardOpen:"fp-open",overlay:"fp-overlay",backdrop:"fp-backdrop",close:"fp-close",keySource:"fp-key-source",keyPath:"fp-key-path",keyTag:"fp-key-tag",keyWarning:"fp-key-warning",vaultTarget:"fp-vault-target",vaultBackendDrive:"fp-vault-backend-drive",vaultBackendCloud:"fp-vault-backend-aegiscloud",vaultBackendOff:"fp-vault-backend-off",label:"fp-label",publishOnMint:"fp-publish-on-mint",autoSeal:"fp-auto-seal",autoSealLabel:"fp-auto-seal-label",autoHint:"fp-auto-hint",sealNow:"fp-seal-now",keygen:"fp-keygen",seal:"fp-seal",sealCard:"fp-seal-card",verify:"fp-verify",publish:"fp-publish",anchor:"fp-anchor",backupTo:"fp-backup-to",backup:"fp-backup",vault:"fp-vault",audit:"fp-audit",vaultList:"fp-vault-list",out:"fp-out",driveStatus:"fp-drive-status",driveSignIn:"fp-drive-signin",driveConnect:"fp-drive-connect",driveSyncNow:"fp-drive-sync"},t=e=>null==e||""===e?"–":String(e);function n(e){return e?!1===e.ok?e.error||"unavailable":e.configured?`key ${t(e.keyFingerprint)} · ${t(e.keySource)}`:"no key — mint one to seal records":"checking…"}function a(e){if(!e||!1===e.ok)return[{k:"state",v:e&&e.error||"unavailable"}];const n=[{k:"key",v:e.configured?e.keyFingerprint:"none"},{k:"source",v:t(e.keySource)},{k:"expected at",v:t(e.defaultKeyPath)},{k:"history",v:e.historyPresent?e.historyPath:"none yet"}];return e.configured&&e.keyPath&&e.keyPath!==e.defaultKeyPath&&n.splice(2,0,{k:"in use",v:e.keyPath}),n}function o(e){const t=e&&e.error?String(e.error):"";return/chmod/i.test(t)?t:""}function i(e){if(!e||!1===e.ok)return[{name:"vault unavailable",detail:e&&e.error||"unknown error"}];const t=Array.isArray(e.records)?e.records:Array.isArray(e.items)?e.items:Array.isArray(e.entries)?e.entries:[];return t.length?t.map((e=>{const t=e.name||e.file||e.path||e.id||"record",n=[e.id,e.publishedAt||e.createdAt,e.url].filter(Boolean);return{name:String(t),detail:n.join(" · ")}})):[{name:"nothing published yet",detail:""}]}function r(e){return e&&!1!==e.ok?e.signedIn?e.connected?"Google Drive connected"+(e.email?` · ${e.email}`:""):"signed in · Google Drive not connected yet":"not signed in — sign in with Google first":e&&e.error||"unavailable"}function s(e){if(!e||!1===e.ok)return{ok:!1,label:e&&e.error||"anchor check failed",claims:[]};const t=void 0!==e.present?e.present:e.presence,n=[{k:"present",v:t},{k:"unchanged",v:e.unchanged},{k:"attested",v:e.attested}],a=!0===t&&!0===e.unchanged&&!0===e.attested;return{ok:a,label:a?"the published copy is this record":"the published copy is not verifiably this record",claims:n}}function l(e){if(!e||!1===e.ok)return{ok:!1,label:e&&e.error||"audit failed",findings:[]};const t=Array.isArray(e.findings)?e.findings:Array.isArray(e.leaks)?e.leaks:Array.isArray(e.hits)?e.hits:[];return t.length?{ok:!1,label:`${t.length} item(s) in the vault look like key material`,findings:t}:{ok:!0,label:"no key material in the vault",findings:[]}}function u(e){if(!e||!1===e.ok)return{ok:!1,text:e&&e.error||"seal failed"};const n=e.record||{},a=[`sealed ${t(n.id)}`,`key ${t(n.keyFingerprint)}`,e.out];return a.push(n.aiKey?`ai ${t(n.ai)} on ${t(n.aiKey)}`:"ai unobserved"+(n.ai?` (${t(n.ai)})`:"")),e.vault&&e.vault.url?a.push(`published ${e.vault.url}`):e.vault&&a.push(`published ${t(e.vault.remote||e.vault.path)}`),e.vaultError&&a.push(`publish failed: ${e.vaultError}`),{ok:!0,text:a.filter(Boolean).join(" · "),file:e.out||null,record:e.record||null}}function c(e){if(!e||"object"!=typeof e)return"–";if(!0!==e.enabled)return"off — sealing stays a button";const t=[],n=Number(e.quietMs);return t.push(Number.isFinite(n)?`seals ${Math.round(n/1e3)}s after the last turn`:"seals when the session goes quiet"),e.publish&&t.push("publishes to the vault"),e.pendingTurns&&t.push(`${e.pendingTurns} turn${1===e.pendingTurns?"":"s"} pending`),e.seals&&t.push(`${e.seals} sealed`),e.failures&&t.push(`${e.failures} failed`),e.lastError&&t.push(`last error: ${e.lastError}`),t.join(" · ")}return{IDS:e,mount:function({api:d,doc:f,auto:p}={}){const k=f||("undefined"!=typeof document?document:null);if(!k)throw new Error("fingerprint: mount needs a document");const y=d||("undefined"!=typeof window?window.aegis:null),h=p||("undefined"!=typeof window?window.auto:null),v=e=>k.getElementById(e),g={status:null,last:null,busy:!1,mounted:!0};function b(t,n){const a=v(e.out);a&&(a.textContent=t,a.className=n?"fp-out fp-bad":"fp-out")}async function m(e,t){if(!y||"function"!=typeof y[e])return{ok:!1,error:`this build has no ${e} bridge`};try{const n=await y[e](t);return n&&"object"==typeof n?n:{ok:!1,error:`${e} returned nothing`}}catch(e){return{ok:!1,error:e&&e.message||String(e)}}}async function $(){const t=await m("getVaultBackend");return g.vaultBackend=t&&t.backend?t.backend:"aegiscloud",function(t){const n={drive:v(e.vaultBackendDrive),aegiscloud:v(e.vaultBackendCloud),off:v(e.vaultBackendOff)};for(const[e,a]of Object.entries(n))a&&a.setAttribute("aria-pressed",String(e===t))}(g.vaultBackend),g.vaultBackend}const w=e=>L("vault: "+("drive"===e?"Google Drive":"off"===e?"no sync":"AE"),(async()=>{const t=await m("setVaultBackend",e);return t&&t.backend?{ok:!0,...t}:{ok:!1,error:"could not save that"}}),{after:e=>e&&e.ok?N():null,say:e=>e.ok?`vault backend set to ${t(e.backend)}`:`could not save that: ${e.error}`});async function S(){const t=await m("driveStatus");return g.drive=t,function(t){const n=v(e.driveStatus);n&&(n.textContent=r(t));const a=v(e.driveConnect);a&&(a.disabled=Boolean(t&&!t.signedIn));const o=v(e.driveSyncNow);o&&(o.disabled=Boolean(!t||!t.connected))}(t),t}const B=()=>L("sign in with Google",(()=>m("driveSignIn")),{after:e=>e&&e.ok?S():null,say:e=>e.ok?"signed in"+(e.username?` as ${e.username}`:""):`sign-in failed: ${e.error}`}),C=()=>L("connect Google Drive",(()=>m("driveConnect")),{after:e=>e&&e.ok?S():null,say:e=>e.ok?"Google Drive connected"+(e.email?` · ${e.email}`:""):`could not connect: ${e.error}`}),x=()=>L("back up memory to Drive",(()=>m("driveSyncMemory")),{say:e=>e.ok?`backed up ${e.count} memory entr${1===e.count?"y":"ies"}${e.webViewLink?` → ${e.webViewLink}`:""}`:`backup failed: ${e.error}`});function A(t){const n=v(e.vaultList);if(n){n.textContent="";for(const e of i(t)){const t=k.createElement("li");t.className="fp-vault-row";const a=k.createElement("span");if(a.className="fp-vault-name",a.textContent=e.name,t.appendChild(a),e.detail){const n=k.createElement("span");n.className="fp-vault-detail",n.textContent=e.detail,t.appendChild(n)}n.appendChild(t)}}}async function N(){return g.status=await m("fingerprintStatus"),function(){const i=g.status,r=v(e.cardStatus);r&&(r.textContent=n(i));const s=v(e.keySource),l=v(e.keyPath),u=v(e.keyTag),c=v(e.keyWarning),d=v(e.vaultTarget),f=a(i),p=e=>{const t=f.find((t=>t.k===e));return t?t.v:"–"};if(s&&(s.textContent=p("source")),l&&(l.textContent=i&&i.keyPath?i.keyPath:p("expected at")),u&&(u.textContent=i&&i.configured?t(i.keyFingerprint):"none"),c){const e=o(i);c.textContent=e,c.hidden=!e}d&&i&&i.vault?
4
+ // not where anything goes — printing `gdrive:AEGIS-AGPL/records` for a
5
+ d.textContent="off"===i.vault.backend?"nothing — sync is off":"aegiscloud"===i.vault.backend?`aegiscloud:${t(i.vault.recordsDir)}`:`${t(i.vault.remote)}:${t(i.vault.recordsDir||i.vault.root)}`:d&&(d.textContent="–");const k=v(e.keygen);k&&(k.textContent=i&&i.configured?"Replace key…":"Mint key")}(),await M(),await $(),await S(),g.status}function D(t){if(!t||"object"!=typeof t)return;const n=v(e.autoSeal);n&&(n.checked=!0===t.enabled);const a=v(e.autoSealLabel);a&&(a.textContent=t.enabled?"sealing automatically when quiet":"seal automatically when quiet");const o=v(e.autoHint);o&&(o.textContent=c(t))}async function M(){if(!h||"function"!=typeof h.status){const t=v(e.autoHint);return t&&(t.textContent="this build has no automation bridge — sealing stays a button"),null}try{const e=await h.status();return e&&e.mint&&D(e.mint),e}catch(t){const n=v(e.autoHint);return n&&(n.textContent=`automation status unavailable: ${t&&t.message||String(t)}`),null}}async function V(){const n=Boolean(v(e.publishOnMint)&&v(e.publishOnMint).checked);return L("seal now",(async()=>h&&"function"==typeof h.sealNow?h.sealNow(n):{ok:!1,error:"this build has no automation bridge"}),{paint:e=>{e&&e.ok&&e.out&&(g.last={file:e.out,record:e.record||null})},say:e=>{if(!e)return"seal now: nothing happened";if(!1===e.ok)return`seal failed: ${e.error||"unknown"}`;if("skip"===e.type)return`nothing to seal (${e.reason||"no new turns"})`;const n=u(e);return n.ok?`sealed ${t((e.record||{}).id)} → ${t(e.out)}`:`seal failed: ${n.text}`}})}async function L(e,t,n){const a=n||{};if(g.busy)return null;g.busy=!0,b(`${e}…`,!1);try{const n=await t();return a.paint&&a.paint(n),a.after&&await a.after(n),b(a.say?a.say(n):`${e}: ${n&&!1===n.ok?n.error:"done"}`,Boolean(n&&!1===n.ok)),n}finally{g.busy=!1}}const O=()=>L("mint key",(()=>m("fingerprintKeygen",{force:Boolean(g.status&&g.status.configured)})),{after:N,say:e=>e.ok?`minted ${t(e.keyFingerprint)} at ${t(e.path)}`:`mint failed: ${e.error}`}),P=()=>L("seal session",(()=>m("fingerprintMint",{label:v(e.label)&&v(e.label).value.trim()||null,publish:Boolean(v(e.publishOnMint)&&v(e.publishOnMint).checked)})),{paint:e=>{e.ok&&(g.last={file:e.out,record:e.record})},say:e=>{const n=u(e);return n.ok?`sealed ${t((e.record||{}).id)} → ${t(e.out)}`:`seal failed: ${n.text}`}}),E=()=>L("verify",(async()=>g.last&&g.last.file?m("fingerprintVerify",{file:g.last.file}):g.last&&g.last.record?m("fingerprintVerify",{record:g.last.record}):{ok:!1,error:"nothing sealed in this window yet — seal a session first"}),{say:e=>e.ok?`verified ${t(e.id)} · key ${t(e.keySource)}`:`not verified: ${e.error||e.reason}`}),I=()=>L("publish",(()=>g.last&&g.last.file?m("fingerprintPublish",{file:g.last.file}):g.last&&g.last.record?m("fingerprintPublish",{record:g.last.record}):{ok:!1,error:"nothing sealed in this window yet — seal a session first"}),{say:e=>e.ok?`published ${t(e.url||e.path)}${e.sha256?` · sha256 ${String(e.sha256).slice(0,12)}`:""}`:`publish failed: ${e.error}`}),T=()=>L("check anchor",(()=>g.last&&g.last.file?m("fingerprintAnchor",{file:g.last.file}):m("fingerprintAnchor",{})),{say:e=>{const t=s(e),n=t.claims.map((e=>`${e.k} ${!0===e.v?"yes":!1===e.v?"no":"unknown"}`)).join(" · ");return`${t.label}${n?` — ${n}`:""}`}}),j=()=>L("back up key",(()=>{const t=v(e.backupTo)&&v(e.backupTo).value.trim();return t?m("fingerprintBackup",{to:t}):{ok:!1,error:"backup needs a destination directory"}}),{say:e=>e.ok?`key copied to ${t(e.to||e.path)}`:`backup refused: ${e.error}`}),F=()=>L("list vault",(()=>m("fingerprintVault")),{paint:A,say:e=>e.ok?`vault listed — ${i(e).length} entry(ies)`:`vault unavailable: ${e.error}`}),G=()=>L("audit vault",(()=>m("fingerprintVaultAudit")),{say:e=>{const t=l(e),n=t.findings.map((e=>e&&(e.path||e.name)||String(e))).slice(0,3);return`${t.label}${n.length?` — ${n.join(", ")}`:""}`}});async function K(t){const n=v(e.overlay);if(!n)return;if(n.hidden=!1,k.body&&k.body.classList&&k.body.classList.add("fp-open"),await N(),await F(),t&&"drive"===t.focus){const t=v(e.driveStatus);t&&t.scrollIntoView&&t.scrollIntoView({block:"center"});const n=[e.driveSignIn,e.driveConnect,e.driveSyncNow].map(v).find((e=>e&&!e.disabled));return void(n&&n.focus())}const a=v(e.close);a&&a.focus()}function q(){const t=v(e.overlay);if(!t)return;t.hidden=!0,k.body&&k.body.classList&&k.body.classList.remove("fp-open");const n=v(e.cardOpen);n&&n.focus()}const H=(e,t,n)=>{const a=v(e);a&&a.addEventListener(t,n)};return H(e.cardOpen,"click",K),H(e.close,"click",q),H(e.backdrop,"click",q),H(e.keygen,"click",O),H(e.seal,"click",P),H(e.sealCard,"click",(async()=>{const e=await P();e&&e.ok&&await K()})),H(e.verify,"click",E),H(e.publish,"click",I),H(e.anchor,"click",T),H(e.backup,"click",j),H(e.vault,"click",F),H(e.audit,"click",G),H(e.vaultBackendDrive,"click",(()=>w("drive"))),H(e.vaultBackendCloud,"click",(()=>w("aegiscloud"))),H(e.vaultBackendOff,"click",(()=>w("off"))),H(e.driveSignIn,"click",B),H(e.driveConnect,"click",C),H(e.driveSyncNow,"click",x),v(e.autoSeal)&&H(e.autoSeal,"change",(()=>{const t=v(e.autoSeal);return async function(t){if(!h||"function"!=typeof h.setMint)return null;const n=Boolean(v(e.publishOnMint)&&v(e.publishOnMint).checked);try{const e=await h.setMint({enabled:Boolean(t),publish:n});return e&&e.mint?D(e.mint):e&&!1===e.ok&&b(e.error||"could not save that",!0),e}catch(e){return b(`could not save that: ${e&&e.message||String(e)}`,!0),null}}(Boolean(t&&t.checked))})),v(e.sealNow)&&H(e.sealNow,"click",V),{open:K,close:q,isOpen:()=>{const t=v(e.overlay);return Boolean(t)&&!t.hidden},refresh:N,loadAutoMint:M,state:g,actions:{doKeygen:O,doSeal:P,doSealNow:V,doVerify:E,doPublish:I,doAnchor:T,doBackup:j,doVault:F,doAudit:G,doSetVaultBackend:w,doDriveSignIn:B,doDriveConnect:C,doDriveSyncNow:x},loadVaultBackend:$,loadDriveStatus:S}},keyLine:n,statusRows:a,keyWarning:o,vaultRows:i,anchorVerdict:s,auditVerdict:l,sealLine:u,autoMintLine:c,driveLine:r,dash:t}}();"undefined"!=typeof module&&module.exports&&(module.exports=n),e&&(e.aegisFingerprint=n)}("undefined"!=typeof window?window:null);