@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.
- package/README.md +22 -26
- package/dist/context-analysis/claude-native-canonical.js +2 -2
- package/dist/context-analysis/vendored-canonical.js +2 -2
- package/dist/context-analysis/workspace-report.js +3 -3
- package/dist/index.js +5 -8
- package/dist/local-jsonl.js +87 -0
- package/dist/migrate.js +6 -4
- package/dist/onboarding-stats.js +16 -0
- package/dist/save-checkpoint-hook.js +1 -1
- package/dist/setup-page/client-core.js +18 -372
- package/dist/setup-page/client-extraction.js +14 -129
- package/dist/setup-page/client-lifecycle.js +28 -101
- package/dist/setup-page/client.js +0 -2
- package/dist/setup-page/styles-extraction.js +1 -31
- package/dist/setup-page/styles-foundation.js +0 -89
- package/dist/setup-page/styles-mvp.js +10 -155
- package/dist/setup-page/styles-website-alignment.js +0 -204
- package/dist/setup-page/styles.js +0 -4
- package/dist/setup-page.js +4 -4
- package/dist/setup-preview.js +4 -212
- package/dist/setup.js +167 -622
- package/dist/v1-contract.js +0 -8
- package/package.json +5 -7
- package/dist/city/README.md +0 -9
- package/dist/city/echo-ai-city-only.html +0 -2232
- package/dist/city/echo-extraction-plate.html +0 -330
- package/dist/city/echo-face-cutout.png +0 -0
- package/dist/city/personality_stickers/bossy.png +0 -0
- package/dist/city/personality_stickers/ghosty.png +0 -0
- package/dist/city/personality_stickers/loopy.png +0 -0
- package/dist/city/personality_stickers/lusty.png +0 -0
- package/dist/city/personality_stickers/maxxy.png +0 -0
- package/dist/city/personality_stickers/tabby.png +0 -0
- package/dist/city/vendor/OrbitControls.js +0 -1417
- package/dist/city/vendor/RoundedBoxGeometry.js +0 -155
- package/dist/city/vendor/echo_general-file-21.riv +0 -0
- package/dist/city/vendor/rive.js +0 -8139
- package/dist/city/vendor/rive.wasm +0 -0
- package/dist/city/vendor/three.module.min.js +0 -6
- package/dist/forensics.js +0 -1531
- package/dist/report.js +0 -721
- package/dist/setup-page/client-report-audit.js +0 -819
- package/dist/setup-page/client-report-city.js +0 -356
- package/dist/setup-page/client-report.js +0 -6
- package/dist/setup-page/styles-city-report.js +0 -880
- 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
|
-
|
|
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; //
|
|
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
|
|
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 = ["
|
|
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("
|
|
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("
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
515
|
-
setConsentStatus("Allow
|
|
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> — ' + 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 · ' + (guide.traits || []).length + ' kinds</p>' + rows(guide.traits, cur.primary) +
|
|
796
|
-
'<p class="fn-guide-sec">Rank · ' + (guide.ranks || []).length + ' levels</p>' + rows(guide.ranks, cur.rank) +
|
|
797
|
-
'<p class="fn-guide-sec">Rhythm · ' + (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
|
`;
|