@echomem/mcp 1.4.46 → 1.4.48

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 (46) hide show
  1. package/README.md +22 -26
  2. package/dist/context-analysis/claude-native-canonical.js +2 -2
  3. package/dist/context-analysis/vendored-canonical.js +2 -2
  4. package/dist/context-analysis/workspace-report.js +3 -3
  5. package/dist/index.js +5 -8
  6. package/dist/local-jsonl.js +87 -0
  7. package/dist/migrate.js +6 -4
  8. package/dist/onboarding-stats.js +16 -0
  9. package/dist/save-checkpoint-hook.js +1 -1
  10. package/dist/setup-page/client-core.js +18 -372
  11. package/dist/setup-page/client-extraction.js +14 -129
  12. package/dist/setup-page/client-lifecycle.js +28 -101
  13. package/dist/setup-page/client.js +0 -2
  14. package/dist/setup-page/styles-extraction.js +1 -31
  15. package/dist/setup-page/styles-foundation.js +0 -89
  16. package/dist/setup-page/styles-mvp.js +10 -155
  17. package/dist/setup-page/styles-website-alignment.js +0 -204
  18. package/dist/setup-page/styles.js +0 -4
  19. package/dist/setup-page.js +4 -4
  20. package/dist/setup-preview.js +4 -212
  21. package/dist/setup.js +167 -622
  22. package/dist/v1-contract.js +0 -8
  23. package/package.json +5 -7
  24. package/dist/city/README.md +0 -9
  25. package/dist/city/echo-ai-city-only.html +0 -2232
  26. package/dist/city/echo-extraction-plate.html +0 -330
  27. package/dist/city/echo-face-cutout.png +0 -0
  28. package/dist/city/personality_stickers/bossy.png +0 -0
  29. package/dist/city/personality_stickers/ghosty.png +0 -0
  30. package/dist/city/personality_stickers/loopy.png +0 -0
  31. package/dist/city/personality_stickers/lusty.png +0 -0
  32. package/dist/city/personality_stickers/maxxy.png +0 -0
  33. package/dist/city/personality_stickers/tabby.png +0 -0
  34. package/dist/city/vendor/OrbitControls.js +0 -1417
  35. package/dist/city/vendor/RoundedBoxGeometry.js +0 -155
  36. package/dist/city/vendor/echo_general-file-21.riv +0 -0
  37. package/dist/city/vendor/rive.js +0 -8139
  38. package/dist/city/vendor/rive.wasm +0 -0
  39. package/dist/city/vendor/three.module.min.js +0 -6
  40. package/dist/forensics.js +0 -1531
  41. package/dist/report.js +0 -721
  42. package/dist/setup-page/client-report-audit.js +0 -819
  43. package/dist/setup-page/client-report-city.js +0 -356
  44. package/dist/setup-page/client-report.js +0 -6
  45. package/dist/setup-page/styles-city-report.js +0 -880
  46. package/dist/setup-page/styles-context-audit.js +0 -470
@@ -6,27 +6,28 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
6
6
  var app = document.getElementById("app");
7
7
  var title = document.getElementById("title");
8
8
  var status = document.getElementById("status");
9
- var report = null;
10
- var reportEnvelope = null;
11
- // The local bridge is authoritative. Login never gets access to scan/import routes;
9
+ // The local bridge is authoritative. Login never gets access to history/import routes;
12
10
  // onboarding may request the separate local-history permission.
13
11
  var setupFlow = "onboarding";
14
- var activeScanId = "";
15
12
  var stats = null;
16
13
  var statsSlow = false;
17
14
  var decisionMade = false;
18
15
  var connected = false;
19
16
  var localHistoryConsentGranted = false;
20
- var extractMounted = false; // WebGL extraction-plate iframe mounted once; persists across /progress polls
17
+ var extractMounted = false; // extraction shell mounted once; persists across /progress polls
21
18
  var lastExtractionProgress = null; // restores the live extraction view if ending fails
22
19
  var extractionRecoveryMessage = ""; // persists pause failures across resumed progress updates
23
20
  var exStartAt = 0; // extraction start (ms) — drives the countdown
24
21
  var exDeadline = 0; // first honest ETA deadline; passing it while running → "taking longer" banner
25
22
  var extractionEnded = false; // user chose to leave the extraction flow before natural completion
26
23
  var progressPollRunId = 0; // invalidates older progress loops when extraction resumes
27
- var dashMounted = false; // dashboard shell (incl. plate iframe) mounted once; persists across /stats polls
28
- var reportMounted = false; // report shell (city iframe) mounted once; loading is an overlay on it, not a separate page
24
+ var dashMounted = false; // dashboard shell mounted once; persists across /stats polls
29
25
  var workspacePath = "";
26
+ var setupPlatform = "unknown";
27
+ // Preview states render before /config; production init replaces these with bridge-reported
28
+ // capabilities before rendering a user-facing state.
29
+ var canOpenClaudeDesktop = true;
30
+ var canOpenAgentSessions = true;
30
31
  var billingStatus = null;
31
32
  var billingStatusLoading = false;
32
33
  var setupPlanChoice = "";
@@ -51,17 +52,7 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
51
52
  // Invalidates async work started for a previously connected account.
52
53
  var accountStateEpoch = 0;
53
54
  var statsPollStarted = false;
54
- var reportPollStarted = false;
55
- var reportEtaDeadline = 0;
56
- var reportEtaDisplaySeconds = 0;
57
- var reportEtaUpdatedAt = 0;
58
- var reportEtaStage = "";
59
- var reportEtaStageDone = 0;
60
- var reportEtaStageElapsed = 0;
61
- var reportEtaObservedRate = 0;
62
- var reportEtaScanId = "";
63
55
  var readyStage = "";
64
- var NIGHT_HOURS = { 22: 1, 23: 1, 0: 1, 1: 1, 2: 1, 3: 1 };
65
56
 
66
57
  try {
67
58
  var storedSetupPlanChoice = sessionStorage.getItem("echomem:setup-plan:" + nonce) || "";
@@ -107,7 +98,6 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
107
98
  var paths = {
108
99
  "shield-check": '<path d="M12 3 5 6v5c0 4.6 2.9 8.1 7 10 4.1-1.9 7-5.4 7-10V6l-7-3Z"/><path d="m8.8 12.1 2 2 4.4-4.5"/>',
109
100
  "folder-read": '<path d="M3 7.5h6l2-2h4.5A2.5 2.5 0 0 1 18 8v1"/><path d="M3 7.5V18a2 2 0 0 0 2 2h7"/><circle cx="17" cy="15" r="3.5"/><path d="m19.5 17.5 2 2"/>',
110
- "report": '<path d="M5 20V10"/><path d="M12 20V4"/><path d="M19 20v-7"/><path d="M3 20h18"/>',
111
101
  "cloud-off": '<path d="m3 3 18 18"/><path d="M6.7 6.7A5.5 5.5 0 0 0 6.5 17H17"/><path d="M10.7 5.2A7 7 0 0 1 19 12.1 4 4 0 0 1 19.5 19"/>',
112
102
  "list-check": '<rect x="4" y="4" width="16" height="16" rx="3"/><path d="m7.5 9 1.4 1.4L11.5 8"/><path d="M13.5 9h3"/><path d="m7.5 14 1.4 1.4 2.6-2.4"/><path d="M13.5 14h3"/>',
113
103
  "pause": '<path d="M9 7v10"/><path d="M15 7v10"/>',
@@ -118,7 +108,7 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
118
108
  return '<svg class="setupGlyph" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><g stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">' + (paths[name] || "") + '</g></svg>';
119
109
  }
120
110
  function setupJourney(currentStep) {
121
- var labels = ["Scan", "Report", "Connect", "Import", "Try Echo"];
111
+ var labels = ["Access", "Connect", "Import", "Try Echo"];
122
112
  var items = labels.map(function (label, index) {
123
113
  var step = index + 1;
124
114
  var state = step < currentStep ? " is-complete" : (step === currentStep ? " is-current" : "");
@@ -133,21 +123,13 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
133
123
  title.textContent = nextTitle;
134
124
  status.textContent = nextStatus;
135
125
  }
136
- function setCityMode(enabled) {
137
- document.body.classList.toggle("cityMode", !!enabled);
138
- if (enabled) document.body.classList.remove("scanMode", "extractMode", "readyMode");
139
- }
140
- function setScanMode(enabled) {
141
- document.body.classList.toggle("scanMode", !!enabled);
142
- if (enabled) document.body.classList.remove("cityMode", "extractMode", "readyMode");
143
- }
144
126
  function setExtractMode(enabled) {
145
127
  document.body.classList.toggle("extractMode", !!enabled);
146
- if (enabled) document.body.classList.remove("scanMode", "readyMode");
128
+ if (enabled) document.body.classList.remove("readyMode");
147
129
  }
148
130
  function setReadyMode(enabled) {
149
131
  document.body.classList.toggle("readyMode", !!enabled);
150
- if (enabled) document.body.classList.remove("scanMode", "cityMode", "extractMode");
132
+ if (enabled) document.body.classList.remove("extractMode");
151
133
  }
152
134
  function number(value) {
153
135
  return typeof value === "number" && isFinite(value) ? Math.round(value).toLocaleString() : "-";
@@ -159,14 +141,6 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
159
141
  if (abs >= 1e6) return (value / 1e6).toFixed(1) + "M";
160
142
  if (abs >= 1e3) return (value / 1e3).toFixed(1) + "K";
161
143
  return String(Math.round(value));
162
- }
163
- function big(value) {
164
- if (typeof value !== "number" || !isFinite(value)) return "-";
165
- var n = Math.abs(value);
166
- if (n >= 1e9) return (value / 1e9).toFixed(2) + "B";
167
- if (n >= 1e6) return (value / 1e6).toFixed(1) + "M";
168
- if (n >= 1e3) return Math.round(value / 1e3) + "K";
169
- return String(Math.round(value));
170
144
  }
171
145
  function sleep(ms) { return new Promise(function (resolve) { setTimeout(resolve, ms); }); }
172
146
  async function fetchWithTimeout(url, options, timeoutMs) {
@@ -200,12 +174,6 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
200
174
  try { data = raw ? JSON.parse(raw) : {}; } catch (_) {}
201
175
  if (!res.ok) throw new Error(data.message || data.error || raw || ("HTTP " + res.status));
202
176
  return data;
203
- }
204
- function metric(label, value, detail) {
205
- return '<section class="metric"><p class="label">' + esc(label) + '</p><strong class="value">' + esc(value) + '</strong><p class="detail">' + esc(detail) + '</p></section>';
206
- }
207
- function row(label, value) {
208
- return '<div class="row"><dt>' + esc(label) + '</dt><dd>' + esc(value) + '</dd></div>';
209
177
  }
210
178
  function routeError(code) {
211
179
  if (code === "NO_PENDING_SESSIONS") return "There are no unprocessed local conversations to extract right now.";
@@ -259,20 +227,18 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
259
227
  }
260
228
  function renderLocalLogin(message) {
261
229
  var onboardingConnect = setupFlow !== "login";
262
- setCityMode(false);
263
- setScanMode(false);
264
230
  setExtractMode(false);
265
231
  setReadyMode(true);
266
232
  setHead("Connect EchoMem", "Local login");
267
233
  app.className = "localAuthStage localAuthWelcomeStage localAuthEmailStage";
268
234
  app.innerHTML =
269
- setupJourney(3) +
235
+ setupJourney(2) +
270
236
  '<section class="localAuthWelcome" aria-labelledby="localAuthWelcomeTitle">' +
271
237
  '<div class="localAuthOrbScene" aria-hidden="true"><span class="localAuthOrb"></span><span class="localAuthOrbShadow"></span></div>' +
272
238
  '<div class="localAuthWelcomeCopy">' +
273
239
  '<h2 id="localAuthWelcomeTitle">' + (onboardingConnect ? 'Take your context with you.' : 'Welcome to Echo') + '</h2>' +
274
240
  (onboardingConnect
275
- ? '<p class="localAuthConnectPurpose">Connect this Mac to Echo to use the memories you choose across your AI tools.</p>'
241
+ ? '<p class="localAuthConnectPurpose">Connect this computer to Echo to use the memories you choose across your AI tools.</p>'
276
242
  : '') +
277
243
  '</div>' +
278
244
  '<form class="localAuthWelcomeForm" id="localAuthEmailForm">' +
@@ -297,8 +263,6 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
297
263
  updateLocalAuthSendButton();
298
264
  }
299
265
  function renderLocalLoginComplete() {
300
- setCityMode(false);
301
- setScanMode(false);
302
266
  setExtractMode(false);
303
267
  setReadyMode(true);
304
268
  setHead("EchoMem connected", "Done");
@@ -307,7 +271,7 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
307
271
  '<section class="localAuthComplete" aria-labelledby="localAuthCompleteTitle">' +
308
272
  '<img src="/hud-assets/echo-face-cutout.png" alt="" />' +
309
273
  '<h2 id="localAuthCompleteTitle">You\'re signed in.</h2>' +
310
- '<p>Return to Terminal.</p>' +
274
+ '<p>Return to your terminal.</p>' +
311
275
  '<p class="localAuthCompleteNext">Run <code>echomem-mcp init</code> when you\'re ready to start onboarding.</p>' +
312
276
  '</section>';
313
277
  }
@@ -347,14 +311,12 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
347
311
  }
348
312
  }
349
313
  function renderLocalOtp(message) {
350
- setCityMode(false);
351
- setScanMode(false);
352
314
  setExtractMode(false);
353
315
  setReadyMode(true);
354
316
  setHead("Check your email", "Local login");
355
317
  app.className = "localAuthStage localAuthWelcomeStage localAuthOtpStage";
356
318
  app.innerHTML =
357
- setupJourney(3) +
319
+ setupJourney(2) +
358
320
  '<section class="localAuthWelcome localAuthOtpWelcome" aria-labelledby="localAuthOtpTitle">' +
359
321
  '<div class="localAuthOrbScene" aria-hidden="true"><span class="localAuthOrb"></span><span class="localAuthOrbShadow"></span></div>' +
360
322
  '<div class="localAuthWelcomeCopy">' +
@@ -443,14 +405,12 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
443
405
  function renderLocalPassphrase(mode, email, message) {
444
406
  localAuthEmail = email || localAuthEmail;
445
407
  var setupMode = mode === "setup";
446
- setCityMode(false);
447
- setScanMode(false);
448
408
  setExtractMode(false);
449
409
  setReadyMode(true);
450
410
  setHead(setupMode ? "Create encrypted vault" : "Unlock encrypted vault", "Local passphrase");
451
411
  app.className = "localAuthStage localAuthWelcomeStage localAuthPassphraseStage";
452
412
  app.innerHTML =
453
- setupJourney(3) +
413
+ setupJourney(2) +
454
414
  '<section class="localAuthWelcome localAuthPassphraseWelcome">' +
455
415
  '<div class="localAuthOrbScene" aria-hidden="true"><span class="localAuthOrb"></span><span class="localAuthOrbShadow"></span></div>' +
456
416
  '<div class="localAuthWelcomeCopy">' +
@@ -511,8 +471,8 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
511
471
  }
512
472
  function onConnectClick() {
513
473
  if (!localHistoryConsentGranted) {
514
- renderLocalScanConsent();
515
- setConsentStatus("Allow the local scan to continue.", "required");
474
+ renderLocalHistoryConsent();
475
+ setConsentStatus("Allow local history access to continue.", "required");
516
476
  return;
517
477
  }
518
478
  if (connected) { void waitForStats(); return; }
@@ -524,318 +484,4 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
524
484
  signin.onclick = onConnectClick;
525
485
  });
526
486
  }
527
- // Retained from the archived pencil-pie prototype: 3D pencil-sketch pies drawn on a
528
- // transparent canvas so the graphite sits directly on the page background.
529
- function pencilMulberry(seed) {
530
- return function () {
531
- var t = seed += 0x6D2B79F5;
532
- t = Math.imul(t ^ t >>> 15, t | 1);
533
- t ^= t + Math.imul(t ^ t >>> 7, t | 61);
534
- return ((t ^ t >>> 14) >>> 0) / 4294967296;
535
- };
536
- }
537
- function drawPencilPie(cv, slices, R) {
538
- var TILT = 0.48, u = R / 270;
539
- var TH = 58 * u, SHLEN = 154 * u, SHANG = 14 * Math.PI / 180;
540
- var density = Math.min(1.1, Math.max(0.2, u * u * 2.6));
541
- cv.width = Math.round(R * 2.62);
542
- cv.height = Math.round(R * 1.9);
543
- var cx = R * 1.14, cy = R * 0.64;
544
- var g = cv.getContext("2d");
545
- var random = pencilMulberry(2117 + Math.round(R * 7) + slices.length * 131);
546
- function ePoint(radius, angle, yOff) { return { x: cx + Math.cos(angle) * radius, y: cy + Math.sin(angle) * radius * TILT + (yOff || 0) }; }
547
- function ePath(radius) { g.beginPath(); g.ellipse(cx, cy, radius, radius * TILT, 0, 0, Math.PI * 2); }
548
- function wPath(start, sweep) {
549
- var steps = Math.max(18, Math.ceil(Math.abs(sweep) / (Math.PI / 90)));
550
- g.beginPath();
551
- g.moveTo(cx, cy);
552
- for (var i = 0; i <= steps; i++) { var p = ePoint(R, start + sweep * (i / steps), 0); g.lineTo(p.x, p.y); }
553
- g.closePath();
554
- }
555
- function pStroke(pathFn, color, width, repeats) {
556
- var rnd = pencilMulberry(808 + Math.round(width * 100) + repeats * 47);
557
- for (var i = 0; i < repeats; i++) {
558
- g.save();
559
- g.translate((rnd() - 0.5) * 1.25, (rnd() - 0.5) * 0.8);
560
- pathFn();
561
- g.strokeStyle = color;
562
- g.globalAlpha = 0.58 / Math.max(1, repeats * 0.64);
563
- g.lineWidth = width + rnd() * 0.45;
564
- g.stroke();
565
- g.restore();
566
- }
567
- }
568
- function hatchClipped(clipFn, angle, spacing, color, alpha) {
569
- g.save();
570
- clipFn();
571
- g.clip();
572
- g.translate(cx, cy);
573
- g.rotate(angle);
574
- g.translate(-cx, -cy);
575
- g.strokeStyle = color;
576
- g.globalAlpha = alpha;
577
- g.lineWidth = 0.58;
578
- g.lineCap = "round";
579
- g.beginPath();
580
- for (var x = cx - R * 1.7; x < cx + R * 1.7; x += spacing) {
581
- var off = (random() - 0.5) * 2;
582
- g.moveTo(x + off, cy - R);
583
- g.lineTo(x + off + (random() - 0.5) * 2, cy + R);
584
- }
585
- g.stroke();
586
- g.restore();
587
- }
588
- // Graphite shadow rubbed into the page beneath the disc.
589
- var dx = Math.cos(SHANG) * SHLEN, dy = Math.sin(SHANG) * SHLEN;
590
- var shX = cx + dx * 0.46, shY = cy + TH + 12 * u + dy * 0.42;
591
- g.save();
592
- g.filter = "blur(" + Math.max(2, 6 * u * 1.6).toFixed(1) + "px)";
593
- g.fillStyle = "rgba(31,33,28,.05)";
594
- g.beginPath();
595
- g.ellipse(shX, shY, R * 1.08, R * TILT * 0.82, SHANG * 0.12, 0, Math.PI * 2);
596
- g.fill();
597
- g.restore();
598
- var L = Math.max(0.8, u * 1.9);
599
- var dust = Math.round(1380 * density), pass, i;
600
- var dustTones = ["rgba(35,37,32,.028)", "rgba(35,37,32,.055)", "rgba(35,37,32,.09)"];
601
- for (pass = 0; pass < 3; pass++) {
602
- g.beginPath();
603
- for (i = pass; i < dust; i += 3) {
604
- var ring = Math.pow(random(), 0.58 + pass * 0.16), ang = random() * Math.PI * 2, trail = Math.pow(random(), 2.1);
605
- g.rect(shX + Math.cos(ang) * R * 1.06 * ring + dx * trail * 0.48, shY + Math.sin(ang) * R * TILT * 0.76 * ring + dy * trail * 0.32, 0.35 + random() * (1.2 + pass * 0.28), (0.35 + random()) * (0.55 + random() * 0.8));
606
- }
607
- g.fillStyle = dustTones[pass];
608
- g.fill();
609
- }
610
- var strokes = Math.round(1040 * density);
611
- g.save();
612
- g.lineCap = "round";
613
- g.lineWidth = 0.72;
614
- for (pass = 0; pass < 2; pass++) {
615
- g.beginPath();
616
- var cross = pass === 0 ? -0.38 : 0.46;
617
- for (i = pass; i < strokes; i += 2) {
618
- var ring2 = Math.pow(random(), 0.62), ang2 = random() * Math.PI * 2, trail2 = Math.pow(random(), 2.05);
619
- var x0 = shX + Math.cos(ang2) * R * 1.04 * ring2 + dx * trail2 * 0.5;
620
- var y0 = shY + Math.sin(ang2) * R * TILT * 0.75 * ring2 + dy * trail2 * 0.34;
621
- var len = (3 + random() * (9 + (1 - ring2) * 14)) * L;
622
- var sa = SHANG + cross + (random() - 0.5) * 0.15;
623
- g.moveTo(x0, y0);
624
- g.lineTo(x0 + Math.cos(sa) * len, y0 + Math.sin(sa) * len * 0.62);
625
- }
626
- g.strokeStyle = pass === 0 ? "rgba(32,34,29,.14)" : "rgba(32,34,29,.11)";
627
- g.stroke();
628
- }
629
- g.restore();
630
- // Contact crevice hugging the lower ellipse.
631
- g.save();
632
- g.lineCap = "round";
633
- g.lineWidth = 0.9;
634
- var contact = Math.round(300 * density);
635
- for (pass = 0; pass < 3; pass++) {
636
- g.beginPath();
637
- for (i = pass; i < contact; i += 3) {
638
- var cAng = random() * Math.PI;
639
- var dark = 0.5 + Math.cos(cAng - SHANG) * 0.5;
640
- var depth = Math.pow(random(), 2.6) * (5 + dark * 8) * L + pass * 1.2;
641
- var cp = ePoint(R * (0.97 + random() * 0.045), cAng, TH + 1 + depth);
642
- var tx = -Math.sin(cAng), ty = Math.cos(cAng) * TILT;
643
- var norm = Math.hypot(tx, ty) || 1;
644
- var cLen = (3 + random() * (8 + dark * 11)) * L;
645
- g.moveTo(cp.x - tx / norm * cLen * 0.5, cp.y - ty / norm * cLen * 0.5);
646
- g.lineTo(cp.x + tx / norm * cLen * 0.5, cp.y + ty / norm * cLen * 0.5);
647
- }
648
- g.strokeStyle = ["rgba(27,29,25,.36)", "rgba(27,29,25,.25)", "rgba(27,29,25,.16)"][pass];
649
- g.stroke();
650
- }
651
- g.restore();
652
- // Cylinder wall.
653
- function sidePath() {
654
- var steps = 120, k;
655
- g.beginPath();
656
- for (k = 0; k <= steps; k++) { var sp = ePoint(R, Math.PI * k / steps, 0); if (k === 0) g.moveTo(sp.x, sp.y); else g.lineTo(sp.x, sp.y); }
657
- for (k = steps; k >= 0; k--) { var sp2 = ePoint(R, Math.PI * k / steps, TH); g.lineTo(sp2.x, sp2.y); }
658
- g.closePath();
659
- }
660
- sidePath();
661
- var sideTone = g.createLinearGradient(cx - Math.cos(SHANG) * R, cy - Math.sin(SHANG) * R * TILT, cx + Math.cos(SHANG) * R, cy + Math.sin(SHANG) * R * TILT);
662
- sideTone.addColorStop(0, "#e6e1d6");
663
- sideTone.addColorStop(0.47, "#d8d3c7");
664
- sideTone.addColorStop(1, "#aaa79f");
665
- g.fillStyle = sideTone;
666
- g.fill();
667
- g.strokeStyle = "rgba(39,41,35,.7)";
668
- g.lineWidth = 1.5;
669
- g.stroke();
670
- var hatchCount = Math.round(138 * Math.max(density, 0.45));
671
- g.save();
672
- g.lineCap = "round";
673
- g.lineWidth = 0.66;
674
- for (pass = 0; pass < 3; pass++) {
675
- g.beginPath();
676
- for (i = 1; i < hatchCount; i++) {
677
- var hAng = Math.PI * i / hatchCount;
678
- var hDark = 0.5 + Math.cos(hAng - SHANG) * 0.5;
679
- if (Math.min(2, Math.floor(hDark * 3)) !== pass) continue;
680
- var top = ePoint(R, hAng, 0);
681
- var jit = (random() - 0.5) * 4 * L;
682
- g.moveTo(top.x + jit * 0.25, top.y + 2 + random() * 3);
683
- g.lineTo(top.x + jit, top.y + TH - 2 - random() * 5);
684
- }
685
- g.strokeStyle = ["rgba(37,39,34,.13)", "rgba(37,39,34,.21)", "rgba(37,39,34,.31)"][pass];
686
- g.stroke();
687
- }
688
- g.restore();
689
- pStroke(function () { g.beginPath(); g.ellipse(cx, cy, R, R * TILT, 0, 0, Math.PI, false); }, "#272923", 1.05, 3);
690
- pStroke(function () { g.beginPath(); g.ellipse(cx, cy + TH, R, R * TILT, 0, 0, Math.PI, false); }, "#22241f", 1.45, 4);
691
- // Disc top.
692
- ePath(R);
693
- g.fillStyle = "#f1ede2";
694
- g.fill();
695
- hatchClipped(function () { ePath(R); }, -Math.PI / 4, Math.max(3, 8 * u * 2) / 1, "#4c4d47", 0.15);
696
- pStroke(function () { ePath(R); }, "#22241f", 1.35, 3);
697
- // Colored wedges: same clock as the CSS conic version (0% = 12 o'clock).
698
- slices.forEach(function (slice) {
699
- var pctSpan = Math.max(0, Number(slice.end || 0) - Number(slice.start || 0));
700
- if (pctSpan < 0.5) return;
701
- var start = (-90 + Number(slice.start || 0) * 3.6) * Math.PI / 180;
702
- var sweep = pctSpan * 3.6 * Math.PI / 180;
703
- wPath(start, sweep);
704
- g.fillStyle = slice.color || "#778db8";
705
- g.fill();
706
- hatchClipped(function () { wPath(start, sweep); }, Math.PI / 5, Math.max(2.4, 5.5 * u * 2), "#26354d", 0.24);
707
- pStroke(function () { wPath(start, sweep); }, "#20231f", 1.25, 3);
708
- g.save();
709
- wPath(start, sweep);
710
- g.strokeStyle = "rgba(30,32,28,.92)";
711
- g.lineWidth = 1.45;
712
- g.stroke();
713
- g.restore();
714
- });
715
- pStroke(function () { ePath(R); }, "#22241f", 1.15, 2);
716
- }
717
- function renderPencilPies() {
718
- Array.prototype.forEach.call(document.querySelectorAll("canvas[data-pencil-pie]"), function (cv) {
719
- if (cv.getAttribute("data-pie-drawn")) return;
720
- cv.setAttribute("data-pie-drawn", "1");
721
- var slices = [];
722
- try { slices = JSON.parse(cv.getAttribute("data-slices") || "[]"); } catch (_) { slices = []; }
723
- drawPencilPie(cv, slices, Number(cv.getAttribute("data-radius") || 100));
724
- });
725
- }
726
- function bindSessionLinks() {
727
- Array.prototype.forEach.call(document.querySelectorAll("[data-open-session]"), function (button) {
728
- button.onclick = async function () {
729
- var scope = button.closest(".audit-example-body");
730
- var status = scope && scope.querySelector(".audit-session-status");
731
- if (status) status.textContent = "Opening original session…";
732
- try {
733
- var result = await postJson("/open-session", {
734
- source: button.getAttribute("data-session-source"),
735
- sessionId: button.getAttribute("data-session-id")
736
- }, 8000);
737
- if (status) status.textContent = result && result.message ? result.message : "Original session opened.";
738
- } catch (error) {
739
- if (status) status.textContent = error && error.message ? error.message : "Could not open the original session.";
740
- }
741
- };
742
- });
743
- }
744
- function bindDetailsArrow() {
745
- var arrow = document.getElementById("details-arrow");
746
- var target = document.getElementById("setup-details");
747
- if (arrow && target) {
748
- arrow.onclick = function () { target.scrollIntoView({ behavior: "smooth", block: "start" }); };
749
- }
750
- }
751
- function bindCityFieldnote() {
752
- window.addEventListener("message", function (event) {
753
- if (event.origin !== location.origin) return;
754
- var data = event.data || {};
755
- if (data.type !== "echomem:city-fieldnote") return;
756
- var note = document.getElementById("city-fieldnote");
757
- if (!note) return;
758
- var sig = JSON.stringify([data.name, data.rank, data.lede]);
759
- if (note._fnSig === sig) return;
760
- note._fnSig = sig;
761
- var save = fnSavings();
762
- var saveLine = save
763
- ? '<p class="fn-save">I can help you get the same things done with ' +
764
- '<b class="fn-num" data-kind="days" data-target="' + save.days + '">0 days</b> and ' +
765
- '<b class="fn-num" data-kind="tokens" data-target="' + save.tokens + '">0 tokens</b> <b class="fn-less">less</b>.</p>'
766
- : '';
767
- note.innerHTML =
768
- '<div class="fn-speak">' +
769
- (data.avatar ? '<img src="' + esc(data.avatar) + '" alt="" />' : '') +
770
- '<div class="fn-bubble"><h2>hey, <b>' + esc(data.name || "") + '</b><span class="fn-dot">.</span></h2>' + saveLine + '</div>' +
771
- '</div>' +
772
- (data.rank ? '<p class="fn-rank">' + data.rank + (data.guide ? '<button class="fn-info" type="button" aria-label="What do these types mean?">i</button>' : '') + '</p>' : '') +
773
- (data.lede ? '<p class="fn-lede">' + data.lede + '</p>' : '');
774
- var infoBtn = note.querySelector(".fn-info");
775
- if (infoBtn) infoBtn.onclick = function () { openPersonaGuide(data.guide); };
776
- fnAnimateNums(note);
777
- });
778
- }
779
- // The taxonomy quote: every persona kind Echo can assign, with the user's own
780
- // rank/rhythm/personality highlighted. Data rides in from the city iframe.
781
- function openPersonaGuide(guide) {
782
- var dlg = document.getElementById("echo-note");
783
- if (!dlg || !guide) return;
784
- dlg.classList.remove("example");
785
- var cur = guide.current || {};
786
- function rows(list, curLabel) {
787
- return (list || []).map(function (t) {
788
- var you = t.label === curLabel;
789
- return '<div class="fn-guide-row' + (you ? ' is-you' : '') + '"><b>' + esc(t.label) + '</b>' + (you ? '<i>you</i>' : '') + '<span> &mdash; ' + esc(t.blurb) + '</span></div>';
790
- }).join("");
791
- }
792
- document.getElementById("echo-note-k").textContent = "Persona guide";
793
- document.getElementById("echo-note-t").textContent = "How Echo reads you";
794
- document.getElementById("echo-note-b").innerHTML =
795
- '<p class="fn-guide-sec">Personality &middot; ' + (guide.traits || []).length + ' kinds</p>' + rows(guide.traits, cur.primary) +
796
- '<p class="fn-guide-sec">Rank &middot; ' + (guide.ranks || []).length + ' levels</p>' + rows(guide.ranks, cur.rank) +
797
- '<p class="fn-guide-sec">Rhythm &middot; ' + (guide.rhythms || []).length + ' kinds</p>' + rows(guide.rhythms, cur.rhythm);
798
- var cl = dlg.querySelector(".nclose");
799
- if (cl) cl.onclick = function () { dlg.close(); };
800
- dlg.onclick = function (e) { if (e.target === dlg) dlg.close(); };
801
- if (dlg.showModal) dlg.showModal(); else dlg.setAttribute("open", "");
802
- }
803
- // Savings projection. BOTH figures apply the canonical terminal-window noise share to the
804
- // FULL ledger, so they share one basis: days = waste share of total attention hours; tokens =
805
- // projected waste share of total provider-billed input. The absolute token figure is a
806
- // calibrated projection because intermediate requests are billed but not independently scored.
807
- function fnSavings() {
808
- var r = report || {};
809
- var gs = gsClean(r.canonicalGoldenStandard);
810
- if (!gs) return null;
811
- var pct = gsDisplayWastePct(gs);
812
- var projection = gsBillingProjection(r, gs);
813
- var tokens = Number(projection.projectedWasteTokens || 0);
814
- var wh = r.userWorkingHours || {};
815
- var hours = Number(wh.userAttentionHours || 0) * pct / 100;
816
- var perDay = Number(wh.averageDailyAttentionHours || 0);
817
- var days = Math.round(perDay > 0 ? hours / perDay : hours / 8);
818
- if (!tokens && !days) return null;
819
- return { tokens: tokens, days: days };
820
- }
821
- function fnAnimateNums(root) {
822
- var els = root.querySelectorAll(".fn-num");
823
- if (!els.length) return;
824
- var DURATION = 1500, t0 = null;
825
- function label(kind, value) {
826
- if (kind === "days") return number(value) + " days";
827
- return big(value) + " tokens";
828
- }
829
- function step(ts) {
830
- if (t0 === null) t0 = ts;
831
- var p = Math.min(1, (ts - t0) / DURATION);
832
- var eased = 1 - Math.pow(1 - p, 3);
833
- Array.prototype.forEach.call(els, function (el) {
834
- var target = Number(el.getAttribute("data-target") || 0);
835
- el.textContent = label(el.getAttribute("data-kind"), Math.round(target * eased));
836
- });
837
- if (p < 1) requestAnimationFrame(step);
838
- }
839
- requestAnimationFrame(step);
840
- }
841
487
  `;