@echomem/mcp 1.4.7 → 1.4.8
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 +14 -8
- package/assets/hud/claude.svg +1 -0
- package/assets/hud/codex.svg +1 -0
- package/assets/hud/session-viewer.html +35 -0
- package/dist/city/echo-ai-city-only.html +31 -13
- package/dist/city/echo-ai-city-only.template.html +31 -13
- package/dist/city/echo-face-cutout.png +0 -0
- package/dist/hud/autostart.js +66 -0
- package/dist/hud/cli.js +31 -0
- package/dist/hud/electron-main.js +182 -19
- package/dist/hud/monitor.js +36 -68
- package/dist/hud/preload.cjs +3 -0
- package/dist/hud/server.js +321 -4
- package/dist/hud/web.js +633 -69
- package/dist/index.js +116 -22
- package/dist/migrate.js +18 -0
- package/dist/setup-page.js +978 -60
- package/dist/setup.js +355 -42
- package/dist/v1-contract.js +20 -2
- package/package.json +3 -2
package/dist/setup-page.js
CHANGED
|
@@ -125,12 +125,41 @@ export function renderSetupPage() {
|
|
|
125
125
|
body.cityMode header {
|
|
126
126
|
display: none;
|
|
127
127
|
}
|
|
128
|
+
body.extractMode header {
|
|
129
|
+
display: none;
|
|
130
|
+
}
|
|
131
|
+
body.readyMode header {
|
|
132
|
+
display: none;
|
|
133
|
+
}
|
|
134
|
+
body.extractMode main {
|
|
135
|
+
min-height: 100vh;
|
|
136
|
+
display: grid;
|
|
137
|
+
align-items: center;
|
|
138
|
+
justify-items: center;
|
|
139
|
+
}
|
|
140
|
+
body.readyMode main {
|
|
141
|
+
min-height: 100vh;
|
|
142
|
+
display: grid;
|
|
143
|
+
align-items: center;
|
|
144
|
+
justify-items: center;
|
|
145
|
+
padding: clamp(24px, 5vw, 56px);
|
|
146
|
+
}
|
|
128
147
|
body.cityMode #app {
|
|
129
148
|
display: block;
|
|
130
149
|
min-height: 100vh;
|
|
131
150
|
color: var(--echo-ink-text);
|
|
132
151
|
text-align: left;
|
|
133
152
|
}
|
|
153
|
+
body.extractMode #app {
|
|
154
|
+
width: 100%;
|
|
155
|
+
display: grid;
|
|
156
|
+
justify-items: center;
|
|
157
|
+
}
|
|
158
|
+
body.readyMode #app {
|
|
159
|
+
width: 100%;
|
|
160
|
+
display: grid;
|
|
161
|
+
justify-items: center;
|
|
162
|
+
}
|
|
134
163
|
|
|
135
164
|
/* clean, consistent loader for the post-connect "working/starting" states */
|
|
136
165
|
.miniLoad { display: grid; gap: 12px; max-width: 520px; padding: clamp(24px, 6vw, 64px) 0; }
|
|
@@ -689,21 +718,651 @@ export function renderSetupPage() {
|
|
|
689
718
|
}
|
|
690
719
|
/* extraction dashboard — plate layout */
|
|
691
720
|
.exHero { display: grid; grid-template-columns: minmax(0, 1fr) minmax(300px, 0.95fr); gap: clamp(20px, 3vw, 40px); align-items: center; margin-top: 8px; }
|
|
692
|
-
|
|
693
|
-
.
|
|
721
|
+
/* Centered composition: mascot on top, then everything stacked and centered on one axis. */
|
|
722
|
+
.readyDecision {
|
|
723
|
+
width: 100%;
|
|
724
|
+
max-width: min(720px, calc(100vw - clamp(32px, 8vw, 120px)));
|
|
725
|
+
display: grid;
|
|
726
|
+
justify-items: center;
|
|
727
|
+
text-align: center;
|
|
728
|
+
gap: 20px;
|
|
729
|
+
margin: 0 auto;
|
|
730
|
+
}
|
|
731
|
+
.readyHero {
|
|
732
|
+
display: grid;
|
|
733
|
+
justify-items: center;
|
|
734
|
+
gap: 16px;
|
|
735
|
+
margin: 0 auto;
|
|
736
|
+
}
|
|
737
|
+
.readyMascot {
|
|
738
|
+
width: 84px;
|
|
739
|
+
height: 84px;
|
|
740
|
+
object-fit: contain;
|
|
741
|
+
filter: drop-shadow(0 12px 18px rgba(26,58,143,0.18));
|
|
742
|
+
}
|
|
743
|
+
.readyHero .exCopy h2 {
|
|
744
|
+
margin: 0;
|
|
745
|
+
font-size: clamp(32px, 4.6vw, 60px);
|
|
746
|
+
line-height: 0.98;
|
|
747
|
+
}
|
|
748
|
+
.readyHero .exCopy .exSub {
|
|
749
|
+
max-width: 560px;
|
|
750
|
+
margin: 12px auto 0;
|
|
751
|
+
font-size: clamp(16px, 1.45vw, 18px);
|
|
752
|
+
}
|
|
753
|
+
.readyAction {
|
|
754
|
+
display: grid;
|
|
755
|
+
grid-template-columns: minmax(0, 1fr) minmax(210px, auto);
|
|
756
|
+
gap: clamp(18px, 3vw, 34px);
|
|
757
|
+
align-items: end;
|
|
758
|
+
margin-top: 2px;
|
|
759
|
+
padding: 22px 0 24px;
|
|
760
|
+
border-top: 1px solid var(--echo-line-soft);
|
|
761
|
+
border-bottom: 1px solid var(--echo-line-soft);
|
|
762
|
+
}
|
|
763
|
+
.readyRun {
|
|
764
|
+
display: grid;
|
|
765
|
+
grid-template-columns: minmax(220px, 0.8fr) minmax(280px, 1.2fr);
|
|
766
|
+
gap: clamp(18px, 3vw, 34px);
|
|
767
|
+
align-items: start;
|
|
768
|
+
}
|
|
769
|
+
.exHeroReady {
|
|
770
|
+
width: 100%;
|
|
771
|
+
max-width: min(860px, calc(100vw - clamp(36px, 9vw, 140px)));
|
|
772
|
+
grid-template-columns: minmax(0, 1fr);
|
|
773
|
+
justify-content: center;
|
|
774
|
+
justify-items: stretch;
|
|
775
|
+
margin: clamp(8px, 2vh, 24px) auto 0;
|
|
776
|
+
}
|
|
777
|
+
.exHeroReady .exCopy { width: 100%; }
|
|
778
|
+
.exHeroResult {
|
|
779
|
+
width: 100%;
|
|
780
|
+
max-width: min(920px, calc(100vw - clamp(36px, 8vw, 120px)));
|
|
781
|
+
grid-template-columns: minmax(0, 760px);
|
|
782
|
+
justify-content: center;
|
|
783
|
+
justify-items: stretch;
|
|
784
|
+
margin: 0 auto;
|
|
785
|
+
}
|
|
786
|
+
.exHeroResult .exPlate { display: none; }
|
|
787
|
+
.exHeroResult .exCopy { width: 100%; margin: 0 auto; }
|
|
788
|
+
.exCopy h2 { max-width: 620px; margin: 8px 0 0; font-size: clamp(34px, 4.8vw, 58px); line-height: 1.01; }
|
|
789
|
+
.exCopy .exSub { margin: 14px 0 0; max-width: 590px; font-size: clamp(15px, 1.3vw, 17px); line-height: 1.6; color: var(--echo-ink-mute); }
|
|
694
790
|
.exLegend { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 16px; font-family: var(--echo-font-mono); font-size: 12px; color: var(--echo-ink-mute); }
|
|
695
791
|
.exLegend .lgDot { width: 12px; height: 12px; border-radius: 3px; display: inline-block; margin-left: 10px; }
|
|
696
792
|
.exLegend .lgDot:first-child { margin-left: 0; }
|
|
697
793
|
.lgNew { background: #2f6df0; }
|
|
698
794
|
.lgDim { background: #cfc8b8; }
|
|
699
795
|
.exHero .actions { margin-top: 22px; }
|
|
700
|
-
.
|
|
796
|
+
.readyDecision .actions {
|
|
797
|
+
justify-content: center;
|
|
798
|
+
margin-top: 0;
|
|
799
|
+
}
|
|
800
|
+
.readyDecision .actions .primary {
|
|
801
|
+
min-width: 240px;
|
|
802
|
+
min-height: 60px;
|
|
803
|
+
padding: 0 30px;
|
|
804
|
+
font-size: 16px;
|
|
805
|
+
box-shadow: 0 16px 28px -20px rgba(26,58,143,0.55);
|
|
806
|
+
}
|
|
807
|
+
/* Proof strip: one quiet line of evidence (source split), not stat cards. Centered. */
|
|
808
|
+
.proofStrip {
|
|
809
|
+
display: flex;
|
|
810
|
+
flex-wrap: wrap;
|
|
811
|
+
justify-content: center;
|
|
812
|
+
align-items: center;
|
|
813
|
+
gap: 8px 12px;
|
|
814
|
+
min-height: 26px;
|
|
815
|
+
margin-top: 2px;
|
|
816
|
+
font-family: var(--echo-font-mono);
|
|
817
|
+
font-size: 13px;
|
|
818
|
+
color: var(--echo-ink-mute);
|
|
819
|
+
}
|
|
820
|
+
.proofStrip .pf { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; }
|
|
821
|
+
.proofStrip .pf strong { color: var(--echo-ink-primary); font-weight: 800; font-variant-numeric: tabular-nums; }
|
|
822
|
+
.proofStrip .pfIcon { position: relative; width: 22px; height: 22px; border-radius: 6px; overflow: hidden; background: var(--echo-ink-primary); display: grid; place-items: center; flex: 0 0 auto; }
|
|
823
|
+
.proofStrip .pfIcon img { width: 22px; height: 22px; object-fit: cover; display: block; }
|
|
824
|
+
.proofStrip .pfFallback { display: none; place-items: center; width: 22px; height: 22px; color: #fff; font-size: 8px; font-weight: 900; }
|
|
825
|
+
.proofStrip .pfDot { color: var(--echo-ink-faint); }
|
|
826
|
+
.proofStrip .pfNote { color: var(--echo-ink-faint); font-size: 12px; }
|
|
827
|
+
/* The action block: button first, reassurances attached right under it. Centered. */
|
|
828
|
+
.readyGo { display: grid; gap: 11px; margin-top: 6px; justify-items: center; }
|
|
829
|
+
.ctaMeta { margin: 0; max-width: 480px; display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 4px 9px; font-family: var(--echo-font-mono); font-size: 12px; line-height: 1.5; color: var(--echo-ink-mute); }
|
|
830
|
+
.ctaMeta .cm { white-space: nowrap; }
|
|
831
|
+
.ctaMeta .cmDot { color: var(--echo-ink-faint); }
|
|
832
|
+
.exHint { margin: 0; font-family: var(--echo-font-mono); font-size: 11px; color: var(--echo-ink-faint); }
|
|
833
|
+
.readyFoot {
|
|
834
|
+
display: grid;
|
|
835
|
+
justify-items: center;
|
|
836
|
+
gap: 14px;
|
|
837
|
+
margin-top: clamp(24px, 5vh, 52px);
|
|
838
|
+
opacity: 0.82;
|
|
839
|
+
}
|
|
840
|
+
.readyTime {
|
|
841
|
+
width: 100%;
|
|
842
|
+
display: grid;
|
|
843
|
+
gap: 7px;
|
|
844
|
+
align-content: start;
|
|
845
|
+
padding: 0;
|
|
846
|
+
border: 0;
|
|
847
|
+
background: transparent;
|
|
848
|
+
box-shadow: none;
|
|
849
|
+
}
|
|
850
|
+
.readyTime span {
|
|
851
|
+
font-family: var(--echo-font-mono);
|
|
852
|
+
font-size: 11px;
|
|
853
|
+
font-weight: 800;
|
|
854
|
+
letter-spacing: 0.08em;
|
|
855
|
+
text-transform: uppercase;
|
|
856
|
+
color: var(--echo-ink-mute);
|
|
857
|
+
}
|
|
858
|
+
.readyTime strong {
|
|
859
|
+
font-family: var(--echo-font-brand);
|
|
860
|
+
font-size: clamp(36px, 4vw, 54px);
|
|
861
|
+
font-weight: 900;
|
|
862
|
+
line-height: 0.92;
|
|
863
|
+
color: var(--echo-ink-primary);
|
|
864
|
+
}
|
|
865
|
+
.readyTime em {
|
|
866
|
+
max-width: 390px;
|
|
867
|
+
font-style: normal;
|
|
868
|
+
color: var(--echo-ink-mute);
|
|
869
|
+
font-size: 13px;
|
|
870
|
+
line-height: 1.45;
|
|
871
|
+
}
|
|
872
|
+
.sourceGrid {
|
|
873
|
+
display: grid;
|
|
874
|
+
gap: 8px;
|
|
875
|
+
margin-top: 8px;
|
|
876
|
+
}
|
|
877
|
+
.sourceHeading {
|
|
878
|
+
margin: 0;
|
|
879
|
+
font-family: var(--echo-font-mono);
|
|
880
|
+
font-size: 11px;
|
|
881
|
+
font-weight: 800;
|
|
882
|
+
letter-spacing: 0.08em;
|
|
883
|
+
text-transform: uppercase;
|
|
884
|
+
color: var(--echo-ink-mute);
|
|
885
|
+
}
|
|
886
|
+
.sourceCard {
|
|
887
|
+
display: grid;
|
|
888
|
+
grid-template-columns: 30px minmax(0, 1fr);
|
|
889
|
+
align-items: center;
|
|
890
|
+
gap: 10px;
|
|
891
|
+
border: 1px solid var(--echo-line-soft);
|
|
892
|
+
border-radius: 8px;
|
|
893
|
+
background: var(--echo-paper-soft);
|
|
894
|
+
padding: 8px 11px 8px 8px;
|
|
895
|
+
}
|
|
896
|
+
.sourceIcon {
|
|
897
|
+
width: 30px;
|
|
898
|
+
height: 30px;
|
|
899
|
+
border-radius: 7px;
|
|
900
|
+
overflow: hidden;
|
|
901
|
+
background: var(--echo-ink-primary);
|
|
902
|
+
display: grid;
|
|
903
|
+
place-items: center;
|
|
904
|
+
flex: 0 0 auto;
|
|
905
|
+
}
|
|
906
|
+
.sourceIcon img { width: 30px; height: 30px; object-fit: cover; display: block; }
|
|
907
|
+
.sourceFallback {
|
|
908
|
+
display: none;
|
|
909
|
+
color: #fff;
|
|
910
|
+
font-family: var(--echo-font-mono);
|
|
911
|
+
font-size: 9px;
|
|
912
|
+
font-weight: 900;
|
|
913
|
+
}
|
|
914
|
+
.sourceCard > div {
|
|
915
|
+
min-width: 0;
|
|
916
|
+
display: grid;
|
|
917
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
918
|
+
align-items: baseline;
|
|
919
|
+
gap: 2px 7px;
|
|
920
|
+
}
|
|
921
|
+
.sourceCard strong {
|
|
922
|
+
font-family: var(--echo-font-brand);
|
|
923
|
+
font-size: 24px;
|
|
924
|
+
font-weight: 900;
|
|
925
|
+
line-height: 1;
|
|
926
|
+
color: var(--echo-ink-primary);
|
|
927
|
+
font-variant-numeric: tabular-nums;
|
|
928
|
+
}
|
|
929
|
+
.sourceCard span {
|
|
930
|
+
font-family: var(--echo-font-mono);
|
|
931
|
+
font-size: 11px;
|
|
932
|
+
font-weight: 800;
|
|
933
|
+
letter-spacing: 0.04em;
|
|
934
|
+
color: var(--echo-ink-mute);
|
|
935
|
+
}
|
|
936
|
+
.sourceCard small {
|
|
937
|
+
display: block;
|
|
938
|
+
grid-column: 1 / -1;
|
|
939
|
+
color: var(--echo-ink-faint);
|
|
940
|
+
font-size: 12px;
|
|
941
|
+
line-height: 1.35;
|
|
942
|
+
}
|
|
943
|
+
.readyExplain {
|
|
944
|
+
max-width: 760px;
|
|
945
|
+
border-top: 1px solid var(--echo-line-soft);
|
|
946
|
+
padding-top: 12px;
|
|
947
|
+
color: var(--echo-ink-mute);
|
|
948
|
+
}
|
|
949
|
+
.readyExplain summary {
|
|
950
|
+
cursor: pointer;
|
|
951
|
+
font-family: var(--echo-font-mono);
|
|
952
|
+
font-size: 11px;
|
|
953
|
+
font-weight: 800;
|
|
954
|
+
letter-spacing: 0.08em;
|
|
955
|
+
text-transform: uppercase;
|
|
956
|
+
color: var(--echo-ink-primary);
|
|
957
|
+
}
|
|
958
|
+
.readyExplain summary::marker { color: var(--echo-ink-faint); }
|
|
959
|
+
.readyExplain p {
|
|
960
|
+
margin: 7px 0 0;
|
|
961
|
+
max-width: 640px;
|
|
962
|
+
color: var(--echo-ink-mute);
|
|
963
|
+
font-size: 14px;
|
|
964
|
+
line-height: 1.55;
|
|
965
|
+
}
|
|
966
|
+
.readySteps {
|
|
967
|
+
display: grid;
|
|
968
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
969
|
+
gap: 8px;
|
|
970
|
+
margin-top: 11px;
|
|
971
|
+
}
|
|
972
|
+
.readyStep {
|
|
973
|
+
border: 1px solid var(--echo-line-soft);
|
|
974
|
+
border-radius: 8px;
|
|
975
|
+
background: transparent;
|
|
976
|
+
padding: 10px 11px;
|
|
977
|
+
}
|
|
978
|
+
.readyStep strong {
|
|
979
|
+
display: block;
|
|
980
|
+
font-family: var(--echo-font-mono);
|
|
981
|
+
font-size: 10px;
|
|
982
|
+
font-weight: 800;
|
|
983
|
+
letter-spacing: 0.06em;
|
|
984
|
+
text-transform: uppercase;
|
|
985
|
+
color: var(--echo-ink-primary);
|
|
986
|
+
}
|
|
987
|
+
.readyStep span {
|
|
988
|
+
display: block;
|
|
989
|
+
margin-top: 5px;
|
|
990
|
+
color: var(--echo-ink-faint);
|
|
991
|
+
font-size: 12px;
|
|
992
|
+
line-height: 1.35;
|
|
993
|
+
}
|
|
701
994
|
.exPlate { width: 100%; }
|
|
702
995
|
/* WebGL clay city (echo-extraction-plate.html) — the only plate; no SVG layer */
|
|
703
996
|
.plateStack { position: relative; width: 100%; aspect-ratio: 640 / 470; }
|
|
704
997
|
.plateStack .plateGl { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; background: transparent; }
|
|
705
|
-
.
|
|
706
|
-
|
|
998
|
+
.exHeroActive {
|
|
999
|
+
grid-template-columns: minmax(0, 640px);
|
|
1000
|
+
justify-content: center;
|
|
1001
|
+
justify-items: stretch;
|
|
1002
|
+
width: 100%;
|
|
1003
|
+
max-width: min(920px, calc(100vw - clamp(36px, 8vw, 120px)));
|
|
1004
|
+
margin: 0 auto;
|
|
1005
|
+
}
|
|
1006
|
+
.exHeroActive .exPlate { display: none; }
|
|
1007
|
+
.exHeroActive .exCopy { width: 100%; }
|
|
1008
|
+
.workbench { display: grid; gap: 14px; max-width: 640px; }
|
|
1009
|
+
.workbench h2 {
|
|
1010
|
+
max-width: 520px;
|
|
1011
|
+
font-size: clamp(24px, 3vw, 34px);
|
|
1012
|
+
line-height: 1.04;
|
|
1013
|
+
}
|
|
1014
|
+
.exMeter {
|
|
1015
|
+
display: flex;
|
|
1016
|
+
align-items: baseline;
|
|
1017
|
+
justify-content: space-between;
|
|
1018
|
+
gap: 14px;
|
|
1019
|
+
margin-top: 10px;
|
|
1020
|
+
padding-bottom: 8px;
|
|
1021
|
+
border-bottom: 1px solid var(--echo-line-soft);
|
|
1022
|
+
}
|
|
1023
|
+
.exMeter strong {
|
|
1024
|
+
font-family: var(--echo-font-brand);
|
|
1025
|
+
font-size: clamp(38px, 5vw, 66px);
|
|
1026
|
+
font-weight: 900;
|
|
1027
|
+
line-height: 0.9;
|
|
1028
|
+
color: var(--echo-ink-primary);
|
|
1029
|
+
font-variant-numeric: tabular-nums;
|
|
1030
|
+
}
|
|
1031
|
+
.exMeter span {
|
|
1032
|
+
font-family: var(--echo-font-mono);
|
|
1033
|
+
font-size: 11px;
|
|
1034
|
+
font-weight: 800;
|
|
1035
|
+
letter-spacing: 0.07em;
|
|
1036
|
+
text-transform: uppercase;
|
|
1037
|
+
color: var(--echo-ink-mute);
|
|
1038
|
+
}
|
|
1039
|
+
.exCurrent {
|
|
1040
|
+
display: grid;
|
|
1041
|
+
grid-template-columns: auto 1fr;
|
|
1042
|
+
gap: 10px;
|
|
1043
|
+
align-items: center;
|
|
1044
|
+
margin-top: 2px;
|
|
1045
|
+
padding: 11px 0;
|
|
1046
|
+
border-top: 1px solid var(--echo-line-soft);
|
|
1047
|
+
border-bottom: 1px solid var(--echo-line-soft);
|
|
1048
|
+
}
|
|
1049
|
+
.exCurrent i {
|
|
1050
|
+
width: 9px;
|
|
1051
|
+
height: 9px;
|
|
1052
|
+
border-radius: 999px;
|
|
1053
|
+
background: var(--echo-ink-accent);
|
|
1054
|
+
box-shadow: 0 0 0 4px rgba(78,205,196,0.15);
|
|
1055
|
+
}
|
|
1056
|
+
.exCurrent span {
|
|
1057
|
+
font-family: var(--echo-font-mono);
|
|
1058
|
+
font-size: 11px;
|
|
1059
|
+
font-weight: 800;
|
|
1060
|
+
letter-spacing: 0.07em;
|
|
1061
|
+
text-transform: uppercase;
|
|
1062
|
+
color: var(--echo-ink-primary);
|
|
1063
|
+
}
|
|
1064
|
+
.exCurrent strong {
|
|
1065
|
+
display: block;
|
|
1066
|
+
margin-top: 3px;
|
|
1067
|
+
color: var(--echo-ink-text);
|
|
1068
|
+
font-size: 14px;
|
|
1069
|
+
line-height: 1.45;
|
|
1070
|
+
font-weight: 700;
|
|
1071
|
+
}
|
|
1072
|
+
.extractFacts {
|
|
1073
|
+
margin: 0;
|
|
1074
|
+
font-family: var(--echo-font-mono);
|
|
1075
|
+
font-size: 12px;
|
|
1076
|
+
line-height: 1.5;
|
|
1077
|
+
color: var(--echo-ink-mute);
|
|
1078
|
+
}
|
|
1079
|
+
.extractFacts strong {
|
|
1080
|
+
color: var(--echo-ink-primary);
|
|
1081
|
+
font-weight: 900;
|
|
1082
|
+
}
|
|
1083
|
+
.extractEndRow {
|
|
1084
|
+
display: flex;
|
|
1085
|
+
align-items: center;
|
|
1086
|
+
gap: 10px;
|
|
1087
|
+
flex-wrap: wrap;
|
|
1088
|
+
margin-top: 2px;
|
|
1089
|
+
}
|
|
1090
|
+
.extractEndRow button {
|
|
1091
|
+
min-height: 0;
|
|
1092
|
+
border: 0;
|
|
1093
|
+
border-radius: 0;
|
|
1094
|
+
background: transparent;
|
|
1095
|
+
color: var(--echo-ink-primary);
|
|
1096
|
+
padding: 0;
|
|
1097
|
+
font-family: var(--echo-font-mono);
|
|
1098
|
+
font-size: 12px;
|
|
1099
|
+
font-weight: 800;
|
|
1100
|
+
letter-spacing: 0.04em;
|
|
1101
|
+
text-transform: none;
|
|
1102
|
+
}
|
|
1103
|
+
.extractEndRow button:hover {
|
|
1104
|
+
text-decoration: underline;
|
|
1105
|
+
}
|
|
1106
|
+
.extractEndRow span {
|
|
1107
|
+
color: var(--echo-ink-faint);
|
|
1108
|
+
font-size: 12px;
|
|
1109
|
+
line-height: 1.45;
|
|
1110
|
+
}
|
|
1111
|
+
.skipStage {
|
|
1112
|
+
display: grid;
|
|
1113
|
+
gap: 14px;
|
|
1114
|
+
max-width: 680px;
|
|
1115
|
+
margin: 0 auto;
|
|
1116
|
+
text-align: center;
|
|
1117
|
+
justify-items: center;
|
|
1118
|
+
}
|
|
1119
|
+
.skipStage h2 {
|
|
1120
|
+
max-width: 620px;
|
|
1121
|
+
font-size: clamp(38px, 5vw, 64px);
|
|
1122
|
+
line-height: 0.98;
|
|
1123
|
+
}
|
|
1124
|
+
.skipStage p {
|
|
1125
|
+
max-width: 560px;
|
|
1126
|
+
margin: 0;
|
|
1127
|
+
color: var(--echo-ink-mute);
|
|
1128
|
+
font-size: 16px;
|
|
1129
|
+
line-height: 1.6;
|
|
1130
|
+
}
|
|
1131
|
+
.skipStage .seal {
|
|
1132
|
+
width: fit-content;
|
|
1133
|
+
border: 1px solid var(--echo-line-soft);
|
|
1134
|
+
border-radius: 999px;
|
|
1135
|
+
background: var(--echo-paper-white);
|
|
1136
|
+
padding: 8px 13px;
|
|
1137
|
+
font-family: var(--echo-font-mono);
|
|
1138
|
+
font-size: 11px;
|
|
1139
|
+
font-weight: 800;
|
|
1140
|
+
letter-spacing: 0.07em;
|
|
1141
|
+
text-transform: uppercase;
|
|
1142
|
+
color: var(--echo-ink-primary);
|
|
1143
|
+
}
|
|
1144
|
+
.resultStage { display: grid; gap: 18px; max-width: 760px; padding: 8px 0 2px; }
|
|
1145
|
+
.resultStage .kicker-mono { color: var(--echo-ink-seal); }
|
|
1146
|
+
.resultTop {
|
|
1147
|
+
display: grid;
|
|
1148
|
+
gap: 12px;
|
|
1149
|
+
align-items: start;
|
|
1150
|
+
}
|
|
1151
|
+
.resultTop h2 { margin-top: 6px; font-size: clamp(36px, 5vw, 58px); line-height: 0.98; }
|
|
1152
|
+
.resultNumber {
|
|
1153
|
+
width: fit-content;
|
|
1154
|
+
display: inline-flex;
|
|
1155
|
+
align-items: baseline;
|
|
1156
|
+
gap: 8px;
|
|
1157
|
+
border: 1px solid var(--echo-line-soft);
|
|
1158
|
+
border-radius: 999px;
|
|
1159
|
+
background: var(--echo-paper-white);
|
|
1160
|
+
padding: 7px 12px;
|
|
1161
|
+
}
|
|
1162
|
+
.resultNumber strong {
|
|
1163
|
+
font-family: var(--echo-font-brand);
|
|
1164
|
+
font-size: 18px;
|
|
1165
|
+
font-weight: 900;
|
|
1166
|
+
line-height: 1;
|
|
1167
|
+
color: var(--echo-ink-primary);
|
|
1168
|
+
font-variant-numeric: tabular-nums;
|
|
1169
|
+
}
|
|
1170
|
+
.resultNumber span {
|
|
1171
|
+
font-family: var(--echo-font-mono);
|
|
1172
|
+
font-size: 10px;
|
|
1173
|
+
font-weight: 800;
|
|
1174
|
+
letter-spacing: 0.07em;
|
|
1175
|
+
text-transform: uppercase;
|
|
1176
|
+
color: var(--echo-ink-mute);
|
|
1177
|
+
text-align: right;
|
|
1178
|
+
}
|
|
1179
|
+
.memoryReceipt {
|
|
1180
|
+
display: grid;
|
|
1181
|
+
gap: 10px;
|
|
1182
|
+
max-width: 700px;
|
|
1183
|
+
padding-top: 2px;
|
|
1184
|
+
}
|
|
1185
|
+
.memoryReceipt .receiptK {
|
|
1186
|
+
margin: 0;
|
|
1187
|
+
font-family: var(--echo-font-mono);
|
|
1188
|
+
font-size: 10px;
|
|
1189
|
+
font-weight: 800;
|
|
1190
|
+
letter-spacing: 0.08em;
|
|
1191
|
+
text-transform: uppercase;
|
|
1192
|
+
color: var(--echo-ink-mute);
|
|
1193
|
+
}
|
|
1194
|
+
.finishStats {
|
|
1195
|
+
display: flex;
|
|
1196
|
+
flex-wrap: wrap;
|
|
1197
|
+
gap: 6px 18px;
|
|
1198
|
+
}
|
|
1199
|
+
.finishStat {
|
|
1200
|
+
display: inline-flex;
|
|
1201
|
+
align-items: baseline;
|
|
1202
|
+
gap: 6px;
|
|
1203
|
+
border: 0;
|
|
1204
|
+
background: transparent;
|
|
1205
|
+
padding: 0;
|
|
1206
|
+
}
|
|
1207
|
+
.finishStat strong {
|
|
1208
|
+
font-family: var(--echo-font-brand);
|
|
1209
|
+
font-size: 17px;
|
|
1210
|
+
font-weight: 900;
|
|
1211
|
+
line-height: 1;
|
|
1212
|
+
color: var(--echo-ink-primary);
|
|
1213
|
+
font-variant-numeric: tabular-nums;
|
|
1214
|
+
}
|
|
1215
|
+
.finishStat span {
|
|
1216
|
+
font-family: var(--echo-font-mono);
|
|
1217
|
+
font-size: 10px;
|
|
1218
|
+
font-weight: 800;
|
|
1219
|
+
letter-spacing: 0.07em;
|
|
1220
|
+
text-transform: uppercase;
|
|
1221
|
+
color: var(--echo-ink-mute);
|
|
1222
|
+
}
|
|
1223
|
+
.retryNote {
|
|
1224
|
+
margin: 0;
|
|
1225
|
+
max-width: 640px;
|
|
1226
|
+
font-size: 12px;
|
|
1227
|
+
line-height: 1.5;
|
|
1228
|
+
color: var(--echo-ink-faint);
|
|
1229
|
+
}
|
|
1230
|
+
.recon { margin-top: 0; display: inline-block; font-family: var(--echo-font-mono); font-size: 13px; font-weight: 700; color: var(--echo-ink-primary); background: var(--echo-paper-soft); border: 1px solid var(--echo-line-soft); border-radius: 8px; padding: 8px 12px; }
|
|
1231
|
+
.exCountdown { margin: 0; font-family: var(--echo-font-mono); font-size: 12px; color: var(--echo-ink-mute); }
|
|
1232
|
+
.exCountdown strong { color: var(--echo-ink-primary); font-weight: 700; }
|
|
1233
|
+
.exOverdue { margin: 0; max-width: 620px; border: 1px solid rgba(196, 148, 42, 0.45); background: rgba(233, 185, 73, 0.12); border-radius: 8px; padding: 12px 14px; font-size: 13px; line-height: 1.5; color: var(--echo-ink-text); }
|
|
1234
|
+
.exOverdue strong { display: block; margin-bottom: 4px; color: var(--echo-ink-primary); }
|
|
1235
|
+
.tryCard { margin-top: 2px; max-width: 760px; border: 2px solid var(--echo-ink-primary); border-radius: 8px; background: var(--echo-paper-white); padding: clamp(18px, 3vw, 26px); box-shadow: 0 18px 38px -28px rgba(26,58,143,0.45); }
|
|
1236
|
+
.tryCard .tryK { margin: 0 0 10px; font-family: var(--echo-font-mono); font-size: 12px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--echo-ink-primary); }
|
|
1237
|
+
.tryCard .tryTitle {
|
|
1238
|
+
margin: 0 0 12px;
|
|
1239
|
+
font-family: var(--echo-font-brand);
|
|
1240
|
+
font-size: clamp(18px, 1.9vw, 23px);
|
|
1241
|
+
line-height: 1.15;
|
|
1242
|
+
font-weight: 900;
|
|
1243
|
+
color: var(--echo-ink-primary);
|
|
1244
|
+
}
|
|
1245
|
+
.tryCard pre { margin: 0; max-height: 72px; overflow: hidden; white-space: pre-wrap; word-break: break-word; font-family: var(--echo-font-mono); font-size: 12px; line-height: 1.5; color: var(--echo-ink-mute); background: var(--echo-paper-soft); border: 1px solid var(--echo-line-soft); border-radius: 8px; padding: 11px 13px; -webkit-mask-image: linear-gradient(to bottom, #000 52%, transparent); mask-image: linear-gradient(to bottom, #000 52%, transparent); }
|
|
1246
|
+
.agentLaunch {
|
|
1247
|
+
display: grid;
|
|
1248
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1249
|
+
gap: 10px;
|
|
1250
|
+
margin-top: 18px;
|
|
1251
|
+
}
|
|
1252
|
+
.agentLaunch button {
|
|
1253
|
+
min-height: 72px;
|
|
1254
|
+
justify-content: flex-start;
|
|
1255
|
+
border-color: var(--echo-line-soft);
|
|
1256
|
+
background: var(--echo-paper-white);
|
|
1257
|
+
color: var(--echo-ink-primary);
|
|
1258
|
+
padding: 10px 14px;
|
|
1259
|
+
text-align: left;
|
|
1260
|
+
}
|
|
1261
|
+
.agentLaunch button:hover { border-color: var(--echo-ink-primary); }
|
|
1262
|
+
.agentIcon {
|
|
1263
|
+
width: 46px;
|
|
1264
|
+
height: 46px;
|
|
1265
|
+
display: grid;
|
|
1266
|
+
place-items: center;
|
|
1267
|
+
flex: 0 0 auto;
|
|
1268
|
+
border-radius: 11px;
|
|
1269
|
+
background: var(--echo-ink-primary);
|
|
1270
|
+
color: #fff;
|
|
1271
|
+
font-family: var(--echo-font-mono);
|
|
1272
|
+
font-size: 11px;
|
|
1273
|
+
font-weight: 900;
|
|
1274
|
+
letter-spacing: 0;
|
|
1275
|
+
overflow: hidden;
|
|
1276
|
+
box-shadow: 0 8px 16px -10px rgba(20,20,20,0.45);
|
|
1277
|
+
}
|
|
1278
|
+
.agentIcon img {
|
|
1279
|
+
width: 100%;
|
|
1280
|
+
height: 100%;
|
|
1281
|
+
object-fit: cover;
|
|
1282
|
+
display: block;
|
|
1283
|
+
}
|
|
1284
|
+
.agentIcon .agentFallback {
|
|
1285
|
+
width: 100%;
|
|
1286
|
+
height: 100%;
|
|
1287
|
+
display: none;
|
|
1288
|
+
place-items: center;
|
|
1289
|
+
}
|
|
1290
|
+
.copyGlyph {
|
|
1291
|
+
position: relative;
|
|
1292
|
+
width: 21px;
|
|
1293
|
+
height: 25px;
|
|
1294
|
+
display: block;
|
|
1295
|
+
}
|
|
1296
|
+
.copyGlyph::before,
|
|
1297
|
+
.copyGlyph::after {
|
|
1298
|
+
content: "";
|
|
1299
|
+
position: absolute;
|
|
1300
|
+
width: 15px;
|
|
1301
|
+
height: 18px;
|
|
1302
|
+
border: 2px solid #fff;
|
|
1303
|
+
border-radius: 4px;
|
|
1304
|
+
background: var(--echo-ink-primary);
|
|
1305
|
+
}
|
|
1306
|
+
.copyGlyph::before { left: 0; top: 0; opacity: 0.72; }
|
|
1307
|
+
.copyGlyph::after { right: 0; bottom: 0; }
|
|
1308
|
+
.agentText {
|
|
1309
|
+
display: block;
|
|
1310
|
+
min-width: 0;
|
|
1311
|
+
}
|
|
1312
|
+
.agentLaunch strong {
|
|
1313
|
+
display: block;
|
|
1314
|
+
font-family: var(--echo-font-brand);
|
|
1315
|
+
font-size: 17px;
|
|
1316
|
+
font-weight: 900;
|
|
1317
|
+
line-height: 1.1;
|
|
1318
|
+
}
|
|
1319
|
+
.agentText small {
|
|
1320
|
+
display: block;
|
|
1321
|
+
margin-top: 2px;
|
|
1322
|
+
color: var(--echo-ink-mute);
|
|
1323
|
+
font-size: 11px;
|
|
1324
|
+
line-height: 1.3;
|
|
1325
|
+
font-weight: 600;
|
|
1326
|
+
}
|
|
1327
|
+
.launchStatus { margin: 9px 0 0; min-height: 18px; font-size: 12px; color: var(--echo-ink-mute); }
|
|
1328
|
+
.hudTip { margin: 14px 0 0; max-width: 700px; display: grid; grid-template-columns: 52px 1fr; gap: 12px; align-items: center; font-size: 13px; line-height: 1.55; color: var(--echo-ink-mute); }
|
|
1329
|
+
.hudTip img { width: 52px; height: 52px; object-fit: contain; filter: drop-shadow(0 10px 16px rgba(26,58,143,0.18)); }
|
|
1330
|
+
.hudTip strong { color: var(--echo-ink-primary); }
|
|
1331
|
+
.hudTip code { font-family: var(--echo-font-mono); font-size: 12px; background: var(--echo-paper-soft); border: 1px solid var(--echo-line-soft); border-radius: 5px; padding: 1px 5px; }
|
|
1332
|
+
.doneCloseNote { margin: clamp(30px, 5vh, 58px) 0 0; max-width: 700px; font-family: var(--echo-font-mono); font-size: 11px; line-height: 1.5; color: var(--echo-ink-faint); }
|
|
1333
|
+
|
|
1334
|
+
/* ===== Calm redesign: one focal point per state, progressive disclosure ===== */
|
|
1335
|
+
.calm { max-width: 640px; }
|
|
1336
|
+
/* Ready: a single compact meta strip replaces the giant ETA box + source cards. */
|
|
1337
|
+
.readyMeta { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 20px 0 2px; font-family: var(--echo-font-mono); font-size: 12.5px; color: var(--echo-ink-mute); }
|
|
1338
|
+
.readyMeta .rm { display: inline-flex; align-items: center; gap: 7px; padding: 6px 12px; border: 1px solid var(--echo-line-soft); border-radius: 999px; background: var(--echo-paper-soft); white-space: nowrap; }
|
|
1339
|
+
.readyMeta .rm strong { color: var(--echo-ink-primary); font-weight: 800; }
|
|
1340
|
+
.readyMeta .rm img { width: 15px; height: 15px; border-radius: 4px; object-fit: contain; }
|
|
1341
|
+
.readyMeta .rm .sf { display: none; width: 15px; height: 15px; border-radius: 4px; background: var(--echo-ink-primary); color: #fff; font-size: 8px; font-weight: 800; align-items: center; justify-content: center; }
|
|
1342
|
+
/* Done: the number is the hero; a small ceremonial tick, one supporting line, no duplicate receipt. */
|
|
1343
|
+
.doneMark { display: inline-flex; align-items: center; gap: 9px; margin: 0; font-family: var(--echo-font-mono); font-size: 12px; font-weight: 800; letter-spacing: 0.09em; text-transform: uppercase; color: var(--echo-ink-accent-deep); }
|
|
1344
|
+
.doneMark .tick { display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; background: var(--echo-ink-accent-deep); color: #fff; font-size: 13px; animation: donePop 0.5s cubic-bezier(0.2, 1.5, 0.4, 1) both; }
|
|
1345
|
+
@keyframes donePop { from { transform: scale(0); } to { transform: scale(1); } }
|
|
1346
|
+
.doneNum { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 14px; margin: 14px 0 0; }
|
|
1347
|
+
.doneNum strong { font-family: var(--echo-font-brand); font-size: clamp(52px, 9vw, 96px); line-height: 0.9; color: var(--echo-ink-primary); letter-spacing: -0.01em; }
|
|
1348
|
+
.doneNum span { font-size: clamp(15px, 1.6vw, 19px); font-weight: 700; color: var(--echo-ink-mute); }
|
|
1349
|
+
.doneSub { margin: 16px 0 0; max-width: 560px; font-size: clamp(15px, 1.4vw, 17px); line-height: 1.5; color: var(--echo-ink-text); }
|
|
1350
|
+
/* Collapse the long warm-up prompt behind a disclosure so the buttons lead. */
|
|
1351
|
+
.promptPeek { margin: 14px 0 0; }
|
|
1352
|
+
.promptPeek summary { cursor: pointer; font-family: var(--echo-font-mono); font-size: 12px; color: var(--echo-ink-mute); list-style: none; }
|
|
1353
|
+
.promptPeek summary::-webkit-details-marker { display: none; }
|
|
1354
|
+
.promptPeek summary::before { content: "▸ "; color: var(--echo-ink-faint); }
|
|
1355
|
+
.promptPeek[open] summary::before { content: "▾ "; }
|
|
1356
|
+
.promptPeek pre { margin: 10px 0 0; white-space: pre-wrap; word-break: break-word; font-family: var(--echo-font-mono); font-size: 12px; line-height: 1.6; color: var(--echo-ink-mute); background: var(--echo-paper-soft); border: 1px solid var(--echo-line-soft); border-radius: 8px; padding: 12px 14px; }
|
|
1357
|
+
|
|
1358
|
+
@media (max-width: 820px) {
|
|
1359
|
+
.exHero, .resultTop, .finishStats, .agentLaunch, .sourceGrid, .readySteps, .readyRun, .readyAction { grid-template-columns: 1fr; }
|
|
1360
|
+
.readyHero { grid-template-columns: 1fr; }
|
|
1361
|
+
.readyMascot { width: 76px; height: 76px; }
|
|
1362
|
+
.readyDecision .actions { justify-content: stretch; }
|
|
1363
|
+
.readyDecision .actions .primary { width: 100%; }
|
|
1364
|
+
.resultNumber span { text-align: left; }
|
|
1365
|
+
}
|
|
707
1366
|
|
|
708
1367
|
/* ===== workspace readout dashboard (Part 2 — replaces explainPage) ===== */
|
|
709
1368
|
@keyframes rdoRise { from { opacity: 0; translate: 0 14px; } to { opacity: 1; translate: 0 0; } }
|
|
@@ -856,9 +1515,13 @@ export function renderSetupPage() {
|
|
|
856
1515
|
var decisionMade = false;
|
|
857
1516
|
var connected = false;
|
|
858
1517
|
var extractMounted = false; // WebGL extraction-plate iframe mounted once; persists across /progress polls
|
|
1518
|
+
var exStartAt = 0; // extraction start (ms) — drives the countdown
|
|
1519
|
+
var exDeadline = 0; // first honest ETA deadline; passing it while running → "taking longer" banner
|
|
1520
|
+
var extractionEnded = false; // user chose to leave the extraction flow before natural completion
|
|
859
1521
|
var dashMounted = false; // dashboard shell (incl. plate iframe) mounted once; persists across /stats polls
|
|
860
1522
|
var reportMounted = false; // report shell (city iframe) mounted once; loading is an overlay on it, not a separate page
|
|
861
1523
|
var authUrl = "";
|
|
1524
|
+
var workspacePath = "";
|
|
862
1525
|
var authWindow = null;
|
|
863
1526
|
var connectionPollStarted = false;
|
|
864
1527
|
var statsPollStarted = false;
|
|
@@ -876,6 +1539,15 @@ export function renderSetupPage() {
|
|
|
876
1539
|
}
|
|
877
1540
|
function setCityMode(enabled) {
|
|
878
1541
|
document.body.classList.toggle("cityMode", !!enabled);
|
|
1542
|
+
if (enabled) document.body.classList.remove("extractMode", "readyMode");
|
|
1543
|
+
}
|
|
1544
|
+
function setExtractMode(enabled) {
|
|
1545
|
+
document.body.classList.toggle("extractMode", !!enabled);
|
|
1546
|
+
if (enabled) document.body.classList.remove("readyMode");
|
|
1547
|
+
}
|
|
1548
|
+
function setReadyMode(enabled) {
|
|
1549
|
+
document.body.classList.toggle("readyMode", !!enabled);
|
|
1550
|
+
if (enabled) document.body.classList.remove("cityMode", "extractMode");
|
|
879
1551
|
}
|
|
880
1552
|
function number(value) {
|
|
881
1553
|
return typeof value === "number" && isFinite(value) ? Math.round(value).toLocaleString() : "-";
|
|
@@ -926,8 +1598,10 @@ export function renderSetupPage() {
|
|
|
926
1598
|
body: JSON.stringify(Object.assign({ nonce: nonce }, body || {}))
|
|
927
1599
|
};
|
|
928
1600
|
var res = timeoutMs ? await fetchWithTimeout(path, options, timeoutMs) : await fetch(path, options);
|
|
929
|
-
var
|
|
930
|
-
|
|
1601
|
+
var raw = await res.text();
|
|
1602
|
+
var data = {};
|
|
1603
|
+
try { data = raw ? JSON.parse(raw) : {}; } catch (_) {}
|
|
1604
|
+
if (!res.ok) throw new Error(data.message || data.error || raw || ("HTTP " + res.status));
|
|
931
1605
|
return data;
|
|
932
1606
|
}
|
|
933
1607
|
function metric(label, value, detail) {
|
|
@@ -1331,10 +2005,9 @@ export function renderSetupPage() {
|
|
|
1331
2005
|
async function waitForStats() {
|
|
1332
2006
|
if (statsPollStarted) return;
|
|
1333
2007
|
statsPollStarted = true;
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
app.innerHTML = '<div class="miniLoad"><h2>Preparing your conversations</h2><p>Sizing what is new since your last run.</p><div class="miniBar"><i></i></div></div>';
|
|
2008
|
+
// Use the same one-reading-line stage as the partial scan state. This avoids a jarring
|
|
2009
|
+
// old setup shell between device approval and the ready decision.
|
|
2010
|
+
renderDashboard();
|
|
1338
2011
|
try {
|
|
1339
2012
|
var started = Date.now();
|
|
1340
2013
|
var lastError = "";
|
|
@@ -1369,6 +2042,8 @@ export function renderSetupPage() {
|
|
|
1369
2042
|
}
|
|
1370
2043
|
function renderDashboard(error) {
|
|
1371
2044
|
setCityMode(false);
|
|
2045
|
+
setExtractMode(false);
|
|
2046
|
+
setReadyMode(true);
|
|
1372
2047
|
var migratable = stats && stats.migratable ? stats.migratable : {};
|
|
1373
2048
|
var pending = stats && stats.migratable ? stats.migratable.pending : null;
|
|
1374
2049
|
var hasPendingCount = typeof pending === "number";
|
|
@@ -1386,18 +2061,19 @@ export function renderSetupPage() {
|
|
|
1386
2061
|
var knownDone = pendingTrusted && pending === 0;
|
|
1387
2062
|
var canExtract = pendingTrusted && pending > 0;
|
|
1388
2063
|
var pendN = hasPendingCount ? pending : 0;
|
|
1389
|
-
var alreadyN = Math.max(0, (sessions.total || 0) - pendN);
|
|
1390
2064
|
var etaLabel = eta.estimatedLabel || migratable.estimatedLabel || "a couple of minutes";
|
|
1391
|
-
|
|
2065
|
+
var pendingCodex = typeof migratable.pendingCodex === "number" ? migratable.pendingCodex : null;
|
|
2066
|
+
var pendingClaudeCode = typeof migratable.pendingClaudeCode === "number" ? migratable.pendingClaudeCode : null;
|
|
2067
|
+
setHead("Turn coding history into memory", pendingTrusted ? (knownDone ? "Done" : "Ready") : (localScanReady ? "Ready" : "Scanning"));
|
|
1392
2068
|
var headline = !pendingTrusted
|
|
1393
2069
|
? "Counting your new conversations…"
|
|
1394
|
-
: (knownDone ? "You are all caught up." : (pendN
|
|
2070
|
+
: (knownDone ? "You are all caught up." : "Echo found " + number(pendN) + " new coding session" + (pendN === 1 ? "" : "s") + ".");
|
|
1395
2071
|
var sub = !pendingTrusted
|
|
1396
2072
|
? "Echo is matching your local history against what is already in memory."
|
|
1397
2073
|
: (knownDone
|
|
1398
|
-
? (skippedActive ? number(skippedActive) + " active conversation" + (skippedActive === 1 ? " is" : "s are") + " still changing
|
|
1399
|
-
: "
|
|
1400
|
-
var extractLabel = !pendingTrusted ? "Counting…" : (canExtract ?
|
|
2074
|
+
? (skippedActive ? number(skippedActive) + " active conversation" + (skippedActive === 1 ? " is" : "s are") + " still changing, so Echo will pick them up later." : "Your history is already in EchoMem. Nothing new to extract.")
|
|
2075
|
+
: "Turn them into memory your agents can recall: decisions, fixes, and where you left off. No more re-explaining your project every session.");
|
|
2076
|
+
var extractLabel = !pendingTrusted ? "Counting…" : (canExtract ? "Start extraction" : "All caught up");
|
|
1401
2077
|
// Mount the dashboard shell ONCE so the WebGL plate iframe is never re-created across /stats polls.
|
|
1402
2078
|
// (Rebuilding app.innerHTML on every partial-scan poll reloaded the iframe → the city flickered.)
|
|
1403
2079
|
// Later polls only patch the text/counts in place; the iframe stays mounted.
|
|
@@ -1405,49 +2081,89 @@ export function renderSetupPage() {
|
|
|
1405
2081
|
if (!live) {
|
|
1406
2082
|
extractMounted = false;
|
|
1407
2083
|
app.className = "";
|
|
2084
|
+
// One reading line, top to bottom: the find (headline) → the value (sub) → quiet proof
|
|
2085
|
+
// (source strip) → the single action with its reassurances attached → optional detail.
|
|
2086
|
+
// The 19/9 split is EVIDENCE ("Echo scanned MY machine"), not data to study — so it is a
|
|
2087
|
+
// one-line strip, not stat cards; the ETA is a commitment-moment note under the button,
|
|
2088
|
+
// not a display-size headline.
|
|
1408
2089
|
app.innerHTML =
|
|
1409
2090
|
'<div id="exBanner"></div>' +
|
|
1410
|
-
'<section class="
|
|
1411
|
-
'<div class="
|
|
1412
|
-
'<
|
|
1413
|
-
'<
|
|
1414
|
-
|
|
1415
|
-
|
|
2091
|
+
'<section class="readyDecision">' +
|
|
2092
|
+
'<div class="readyHero">' +
|
|
2093
|
+
'<img class="readyMascot" src="/hud-assets/echo-face-cutout.png" alt="" />' +
|
|
2094
|
+
'<div class="exCopy">' +
|
|
2095
|
+
'<h2 id="exHeadline"></h2>' +
|
|
2096
|
+
'<p class="exSub" id="exSub"></p>' +
|
|
2097
|
+
'</div>' +
|
|
2098
|
+
'</div>' +
|
|
2099
|
+
'<div class="proofStrip" id="exSources"></div>' +
|
|
2100
|
+
'<div class="readyGo">' +
|
|
1416
2101
|
'<div class="actions">' +
|
|
1417
2102
|
'<button id="migrate" class="primary"></button>' +
|
|
1418
|
-
'<button id="skip" class="secondary"></button>' +
|
|
1419
2103
|
'</div>' +
|
|
1420
|
-
'<p class="
|
|
2104
|
+
'<p class="ctaMeta" id="exEta"></p>' +
|
|
2105
|
+
'</div>' +
|
|
2106
|
+
'<div class="readyFoot">' +
|
|
2107
|
+
'<details class="readyExplain" id="readyExplain"></details>' +
|
|
1421
2108
|
'</div>' +
|
|
1422
|
-
plateBlock() +
|
|
1423
2109
|
'</section>';
|
|
1424
2110
|
document.getElementById("migrate").onclick = startMigrate;
|
|
1425
|
-
document.getElementById("skip").onclick = skip;
|
|
1426
2111
|
dashMounted = true;
|
|
1427
2112
|
}
|
|
1428
2113
|
document.getElementById("exBanner").innerHTML =
|
|
1429
2114
|
(statsSlow && !stats ? '<div class="warning">Local counts are taking longer than expected. You can still ask the bridge to extract anything unprocessed.</div>' : '') +
|
|
1430
2115
|
(error ? '<div class="error">' + esc(error) + '</div>' : '');
|
|
1431
|
-
document.getElementById("exKicker").textContent = "Local scan · " + number(sessions.total) + " sessions found";
|
|
1432
2116
|
document.getElementById("exHeadline").textContent = headline;
|
|
1433
2117
|
document.getElementById("exSub").textContent = sub;
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
//
|
|
1438
|
-
|
|
2118
|
+
var codexN = pendingTrusted && pendingCodex !== null ? pendingCodex : (sessions.codex || 0);
|
|
2119
|
+
var claudeN = pendingTrusted && pendingClaudeCode !== null ? pendingClaudeCode : (sessions.claudeCode || 0);
|
|
2120
|
+
var alreadyN = Math.max(0, (sessions.total || 0) - pendN);
|
|
2121
|
+
// Proof strip: one quiet line of evidence that the scan is real. Hidden when there is nothing to show.
|
|
2122
|
+
var srcIcon = function (id, fallback) {
|
|
2123
|
+
return '<span class="pfIcon"><img src="/agent-icons/' + id + '.png" alt="" onerror="this.style.display="none";this.nextElementSibling.style.display="grid";" /><span class="pfFallback">' + fallback + '</span></span>';
|
|
2124
|
+
};
|
|
2125
|
+
document.getElementById("exSources").innerHTML = !pendingTrusted
|
|
2126
|
+
? '<span class="pfNote">Scanning local history…</span>'
|
|
2127
|
+
: (canExtract
|
|
2128
|
+
? '<span class="pf">' + srcIcon("codex", "CX") + '<strong>' + esc(number(codexN)) + '</strong> Codex</span>' +
|
|
2129
|
+
'<span class="pfDot">·</span>' +
|
|
2130
|
+
'<span class="pf">' + srcIcon("claude-desktop", "CL") + '<strong>' + esc(number(claudeN)) + '</strong> Claude Code</span>' +
|
|
2131
|
+
'<span class="pfNote">found on this Mac</span>'
|
|
2132
|
+
: "");
|
|
2133
|
+
// Reassurances live at the moment of commitment — right under the button.
|
|
2134
|
+
document.getElementById("exEta").innerHTML = pendingTrusted
|
|
2135
|
+
? (canExtract
|
|
2136
|
+
? '<span class="cm">≈ ' + esc(etaLabel) + '</span><span class="cmDot">·</span><span class="cm">runs locally</span>' + (alreadyN ? '<span class="cmDot">·</span><span class="cm">' + esc(number(alreadyN)) + ' already-saved skipped</span>' : '')
|
|
2137
|
+
: 'Nothing new to extract right now.')
|
|
2138
|
+
: 'Echo is checking local history against saved memory…';
|
|
2139
|
+
document.getElementById("readyExplain").innerHTML =
|
|
2140
|
+
'<summary>What exactly happens?</summary>' +
|
|
2141
|
+
'<p>A coding session is a local Codex or Claude Code transcript from this Mac. Echo extracts compact memories from it, not a full chat replay.</p>' +
|
|
2142
|
+
'<div class="readySteps">' +
|
|
2143
|
+
'<div class="readyStep"><strong>Read locally</strong><span>Echo scans session logs on this device.</span></div>' +
|
|
2144
|
+
'<div class="readyStep"><strong>Skip repeats</strong><span>Anything already in memory is ignored.</span></div>' +
|
|
2145
|
+
'<div class="readyStep"><strong>Save memory</strong><span>Decisions, fixes, context, and next steps become searchable.</span></div>' +
|
|
2146
|
+
'</div>';
|
|
2147
|
+
// While still counting, hide the primary button entirely; the headline + source split
|
|
2148
|
+
// already say it is working, and the ready state should present one clear action.
|
|
1439
2149
|
var migrateBtn = document.getElementById("migrate");
|
|
1440
|
-
if (pendingTrusted) {
|
|
1441
|
-
|
|
1442
|
-
|
|
2150
|
+
if (pendingTrusted) {
|
|
2151
|
+
migrateBtn.style.display = "";
|
|
2152
|
+
migrateBtn.disabled = false;
|
|
2153
|
+
migrateBtn.textContent = canExtract ? extractLabel : "Finish setup";
|
|
2154
|
+
migrateBtn.onclick = canExtract ? startMigrate : skip;
|
|
2155
|
+
} else {
|
|
2156
|
+
migrateBtn.style.display = "none";
|
|
2157
|
+
}
|
|
1443
2158
|
}
|
|
1444
2159
|
async function startMigrate() {
|
|
1445
2160
|
if (decisionMade) return;
|
|
1446
2161
|
decisionMade = true;
|
|
2162
|
+
extractionEnded = false;
|
|
1447
2163
|
setCityMode(false);
|
|
1448
2164
|
setHead("Extracting", "Starting");
|
|
1449
2165
|
app.className = "";
|
|
1450
|
-
app.innerHTML = '<div class="miniLoad"><h2>Starting extraction</h2><p>Echo is sizing the remaining jobs, then imports automatically
|
|
2166
|
+
app.innerHTML = '<div class="miniLoad"><h2>Starting extraction</h2><p>Echo is sizing the remaining jobs, then imports automatically. Finished work is saved as it goes.</p><div class="miniBar"><i></i></div></div>';
|
|
1451
2167
|
try {
|
|
1452
2168
|
var started = await postJson("/migrate", {});
|
|
1453
2169
|
extractMounted = false;
|
|
@@ -1460,14 +2176,18 @@ export function renderSetupPage() {
|
|
|
1460
2176
|
}
|
|
1461
2177
|
async function pollProgress() {
|
|
1462
2178
|
for (;;) {
|
|
2179
|
+
if (extractionEnded) return;
|
|
1463
2180
|
var progress = await getJson("/progress");
|
|
2181
|
+
if (extractionEnded) return;
|
|
1464
2182
|
renderProgress(progress);
|
|
1465
2183
|
if (progress.status === "completed" || progress.status === "failed") return;
|
|
1466
2184
|
await sleep(1200);
|
|
1467
2185
|
}
|
|
1468
2186
|
}
|
|
1469
2187
|
function renderProgress(progress) {
|
|
2188
|
+
if (extractionEnded) return;
|
|
1470
2189
|
setCityMode(false);
|
|
2190
|
+
setExtractMode(true);
|
|
1471
2191
|
var status = progress.status;
|
|
1472
2192
|
var done = status === "completed";
|
|
1473
2193
|
var stopped = status === "failed";
|
|
@@ -1487,39 +2207,199 @@ export function renderSetupPage() {
|
|
|
1487
2207
|
'</section>';
|
|
1488
2208
|
extractMounted = true;
|
|
1489
2209
|
}
|
|
2210
|
+
var shell = app.querySelector(".exHero");
|
|
2211
|
+
if (shell) {
|
|
2212
|
+
shell.classList.toggle("exHeroResult", done || stopped);
|
|
2213
|
+
shell.classList.toggle("exHeroActive", !done && !stopped);
|
|
2214
|
+
}
|
|
1490
2215
|
var t = document.getElementById("exText");
|
|
1491
2216
|
if (!t) return;
|
|
1492
2217
|
if (done || stopped) {
|
|
1493
2218
|
setHead(done ? "Memory captured" : "Extraction stopped", done ? "Done" : "Needs attention");
|
|
2219
|
+
var memoryNoun = extracted === 1 ? "new memory" : "new memories";
|
|
2220
|
+
var conversationNoun = completed === 1 ? "conversation" : "conversations";
|
|
2221
|
+
var stopDetail = String(progress.error) === "key-expired"
|
|
2222
|
+
? "Your encryption key was not accepted. Re-run extraction; if it keeps failing, run <code>echomem-mcp unlock</code> first. Already-done conversations are skipped."
|
|
2223
|
+
: esc(progress.error || "Re-run extraction to finish the rest. Already-done conversations are skipped.");
|
|
1494
2224
|
t.innerHTML =
|
|
1495
|
-
'<
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
? "
|
|
1501
|
-
|
|
1502
|
-
|
|
2225
|
+
'<div class="resultStage ' + (done ? "ok" : "warn") + '">' +
|
|
2226
|
+
(done ? "" : '<p class="kicker-mono">Extraction stopped</p>') +
|
|
2227
|
+
'<div class="resultTop">' +
|
|
2228
|
+
'<div>' +
|
|
2229
|
+
'<h2>' + (done ? "Memory is live." : "Some conversations need another pass.") + '</h2>' +
|
|
2230
|
+
(done ? "" : '<p class="exSub">' + stopDetail + '</p>') +
|
|
2231
|
+
'<div class="resultNumber"><strong>' + esc(number(extracted)) + '</strong><span>' + esc(memoryNoun) + '</span></div>' +
|
|
2232
|
+
'</div>' +
|
|
2233
|
+
'</div>' +
|
|
2234
|
+
(done ? tryItCard() : "") +
|
|
2235
|
+
'<div class="memoryReceipt">' +
|
|
2236
|
+
'<p class="receiptK">What just happened</p>' +
|
|
2237
|
+
'<div class="finishStats">' +
|
|
2238
|
+
'<div class="finishStat"><strong>' + esc(number(completed)) + '</strong><span>' + esc(conversationNoun) + ' extracted</span></div>' +
|
|
2239
|
+
'<div class="finishStat"><strong>' + esc(number(extracted)) + '</strong><span>memories ready</span></div>' +
|
|
2240
|
+
'</div>' +
|
|
2241
|
+
'</div>' +
|
|
2242
|
+
'</div>' +
|
|
2243
|
+
(done ? hudTip() + doneCloseNote() : "");
|
|
2244
|
+
var copyBtn = document.getElementById("tryCopy");
|
|
2245
|
+
if (copyBtn) copyBtn.addEventListener("click", copyPromptOnly);
|
|
2246
|
+
var codexBtn = document.getElementById("tryCodex");
|
|
2247
|
+
if (codexBtn) codexBtn.addEventListener("click", tryInCodex);
|
|
2248
|
+
var claudeBtn = document.getElementById("tryClaudeDesktop");
|
|
2249
|
+
if (claudeBtn) claudeBtn.addEventListener("click", openClaudeDesktop);
|
|
1503
2250
|
return;
|
|
1504
2251
|
}
|
|
1505
2252
|
setHead(preparing ? "Preparing" : "Extracting", preparing ? "Preparing" : "Extracting");
|
|
1506
|
-
var
|
|
2253
|
+
var checkingEdgeCases = failed > 0 && !preparing;
|
|
2254
|
+
var statusLine = checkingEdgeCases
|
|
2255
|
+
? "Finishing a few checks without stopping the import."
|
|
2256
|
+
: (progress.latest ? (progress.latest + (progress.latestRepo ? " · " + progress.latestRepo : "")) : (preparing ? "Getting your sessions ready locally…" : "Importing the next batch…"));
|
|
2257
|
+
// Honest countdown: first ETA from batch size (throughput is rate-capped ≈ 26/min), then
|
|
2258
|
+
// refined from the observed rate. Blowing past the FIRST estimate flips on the reassurance banner.
|
|
2259
|
+
var countdownHtml = "";
|
|
2260
|
+
var overdueHtml = "";
|
|
2261
|
+
if (!preparing && total > 0) {
|
|
2262
|
+
var now = Date.now();
|
|
2263
|
+
if (!exStartAt) { exStartAt = now; exDeadline = now + Math.max(60, total * 2.6) * 1000; }
|
|
2264
|
+
var elapsed = (now - exStartAt) / 1000;
|
|
2265
|
+
var remainSec;
|
|
2266
|
+
if (completed >= 5 && elapsed > 20) remainSec = (total - completed) / (completed / elapsed);
|
|
2267
|
+
else remainSec = Math.max(0, (exDeadline - now) / 1000);
|
|
2268
|
+
if (now > exDeadline) {
|
|
2269
|
+
overdueHtml = '<div class="exOverdue"><strong>Taking longer than expected. Still working.</strong>' +
|
|
2270
|
+
'Everything already processed is saved. Ending now is safe; the next run resumes from the remaining conversations.</div>';
|
|
2271
|
+
} else {
|
|
2272
|
+
countdownHtml = '<p class="extractFacts"><strong>' + esc(fmtCountdown(remainSec)) + '</strong> left · saved as it goes · safe to end and resume</p>';
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
1507
2275
|
t.innerHTML =
|
|
1508
|
-
'<
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
2276
|
+
'<div class="workbench">' +
|
|
2277
|
+
'<h2>' + (preparing ? "Setting up the import." : "Building your memory.") + '</h2>' +
|
|
2278
|
+
'<div class="exMeter"><strong>' + (preparing ? "Local" : esc(number(pct)) + "%") + '</strong><span>' + (preparing ? "sizing sessions" : (total ? esc(number(completed)) + " of " + esc(number(total)) + " conversations" : "starting")) + '</span></div>' +
|
|
2279
|
+
'<div class="track' + (preparing ? " indef" : "") + '"><div class="fill"' + (preparing ? "" : ' style="width:' + pct + '%"') + '></div></div>' +
|
|
2280
|
+
'<p class="exSub">' + esc(number(extracted)) + ' ' + (extracted === 1 ? "memory" : "memories") + ' captured' + (checkingEdgeCases ? ' · finishing checks' : "") + '.</p>' +
|
|
2281
|
+
'<div class="exCurrent"><i></i><div><span>Now handling</span><strong>' + esc(statusLine) + '</strong></div></div>' +
|
|
2282
|
+
countdownHtml + overdueHtml +
|
|
2283
|
+
'<div class="extractEndRow"><button type="button" class="secondary" id="endExtraction">End for now</button><span>Ends cleanly; next time continues from saved work.</span></div>' +
|
|
2284
|
+
'<p class="exHint">You can step away from this page. Echo saves each finished conversation as it goes.</p>' +
|
|
2285
|
+
'</div>';
|
|
2286
|
+
var endBtn = document.getElementById("endExtraction");
|
|
2287
|
+
if (endBtn) endBtn.onclick = endExtraction;
|
|
2288
|
+
}
|
|
2289
|
+
function fmtCountdown(sec) {
|
|
2290
|
+
sec = Math.max(0, Math.round(sec));
|
|
2291
|
+
var m = Math.floor(sec / 60), s = sec % 60;
|
|
2292
|
+
if (m >= 60) { var h = Math.floor(m / 60); return h + "h " + (m % 60) + "m"; }
|
|
2293
|
+
return m > 0 ? m + "m " + s + "s" : s + "s";
|
|
2294
|
+
}
|
|
2295
|
+
function recallPrompt() {
|
|
2296
|
+
return "Warm up from EchoMem: call search_memories for what I have been working on recently, then give me a 5-line recap of my current projects and where each one stands, so we can pick up right where I left off. If this is Codex or Claude Code, open the EchoMem context HUD by running echomem-hud app. Use the HUD's context-health score to tell me when to renew into a fresh session and carry a checkpoint forward. As we work, when we make a decision, finish a fix, or hit a milestone, save it with save_conversation. Use this prompt in Codex, Claude Code, or Claude Desktop; if you are in another chat surface, tell me EchoMem tools may not be available here.";
|
|
2297
|
+
}
|
|
2298
|
+
function setLaunchStatus(message) {
|
|
2299
|
+
var node = document.getElementById("launchStatus");
|
|
2300
|
+
if (node) node.textContent = message || "";
|
|
2301
|
+
}
|
|
2302
|
+
async function copyRecallPrompt() {
|
|
2303
|
+
var prompt = recallPrompt();
|
|
2304
|
+
try {
|
|
2305
|
+
await navigator.clipboard.writeText(prompt);
|
|
2306
|
+
return true;
|
|
2307
|
+
} catch (_) {
|
|
2308
|
+
return false;
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
async function copyPromptOnly() {
|
|
2312
|
+
var copied = await copyRecallPrompt();
|
|
2313
|
+
setLaunchStatus(copied ? "Prompt copied." : "Could not copy automatically. Select the prompt and copy it.");
|
|
2314
|
+
}
|
|
2315
|
+
function codexDeepLink(prompt) {
|
|
2316
|
+
var q = "prompt=" + encodeURIComponent(prompt);
|
|
2317
|
+
if (workspacePath) q += "&path=" + encodeURIComponent(workspacePath);
|
|
2318
|
+
return "codex://new?" + q;
|
|
2319
|
+
}
|
|
2320
|
+
async function tryInCodex() {
|
|
2321
|
+
var prompt = recallPrompt();
|
|
2322
|
+
var copied = await copyRecallPrompt();
|
|
2323
|
+
setLaunchStatus(copied ? "Copied prompt. Opening Codex..." : "Opening Codex. If the prompt is empty, copy it manually.");
|
|
2324
|
+
window.location.href = codexDeepLink(prompt);
|
|
2325
|
+
}
|
|
2326
|
+
async function openClaudeDesktop() {
|
|
2327
|
+
var copied = await copyRecallPrompt();
|
|
2328
|
+
setLaunchStatus(copied ? "Copied prompt. Opening Claude Desktop..." : "Opening Claude Desktop. If the prompt is empty, copy it manually.");
|
|
2329
|
+
try {
|
|
2330
|
+
var res = await postJson("/launch-agent", { agent: "claude-desktop" }, 8000);
|
|
2331
|
+
setLaunchStatus(res && res.message ? res.message : "Prompt copied. Paste it into Claude Desktop.");
|
|
2332
|
+
} catch (error) {
|
|
2333
|
+
var msg = error && error.message ? error.message : "Could not auto-open Claude.";
|
|
2334
|
+
if (msg.indexOf("prompt") === -1 && msg.indexOf("Prompt") === -1) msg += " The prompt is copied - open Claude Desktop and paste it.";
|
|
2335
|
+
setLaunchStatus(msg);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
function tryItCard() {
|
|
2339
|
+
var prompt = recallPrompt();
|
|
2340
|
+
return '<div class="tryCard">' +
|
|
2341
|
+
'<p class="tryK">Try it now</p>' +
|
|
2342
|
+
'<h3 class="tryTitle">Ask your agent to pick up where you left off.</h3>' +
|
|
2343
|
+
'<pre id="tryPrompt">' + esc(prompt) + '</pre>' +
|
|
2344
|
+
'<div class="agentLaunch" aria-label="Open in a coding agent">' +
|
|
2345
|
+
'<button type="button" id="tryCodex"><span class="agentIcon"><img src="/agent-icons/codex.png" alt="" onerror="this.style.display="none";this.nextElementSibling.style.display="grid";" /><span class="agentFallback">CX</span></span><span class="agentText"><strong>Try in Codex</strong><small>copy prompt + open session</small></span></button>' +
|
|
2346
|
+
'<button type="button" id="tryClaudeDesktop"><span class="agentIcon"><img src="/agent-icons/claude-desktop.png" alt="" onerror="this.style.display="none";this.nextElementSibling.style.display="grid";" /><span class="agentFallback">CL</span></span><span class="agentText"><strong>Open Claude Desktop</strong><small>copy prompt + open app</small></span></button>' +
|
|
2347
|
+
'<button type="button" id="tryCopy"><span class="agentIcon"><span class="copyGlyph" aria-hidden="true"></span></span><span class="agentText"><strong>Copy prompt</strong><small>paste anywhere</small></span></button>' +
|
|
2348
|
+
'</div>' +
|
|
2349
|
+
'<p class="launchStatus" id="launchStatus">Paste into Codex, Claude Code, or Claude Desktop. EchoMem tools need to be available there.</p>' +
|
|
2350
|
+
'</div>';
|
|
2351
|
+
}
|
|
2352
|
+
function hudTip() {
|
|
2353
|
+
return '<p class="hudTip"><img src="/hud-assets/echo-face-cutout.png" alt="" /><span><strong>Live HUD:</strong> Echo watches the context window while you work. When the score drifts low, start a fresh session and carry the checkpoint forward. In Codex or Claude Code, ask “<strong>open the EchoMem HUD</strong>” or run <code>echomem-hud app</code>.</span></p>';
|
|
2354
|
+
}
|
|
2355
|
+
function doneCloseNote() {
|
|
2356
|
+
return '<p class="doneCloseNote">You can safely close this page. EchoMem is set up, and your memories are saved.</p>';
|
|
2357
|
+
}
|
|
2358
|
+
function renderEnding() {
|
|
2359
|
+
setCityMode(false);
|
|
2360
|
+
setExtractMode(true);
|
|
2361
|
+
setHead("Ending extraction", "Ending");
|
|
2362
|
+
app.className = "";
|
|
2363
|
+
app.innerHTML =
|
|
2364
|
+
'<div class="miniLoad" style="margin:0 auto;text-align:center;place-items:center">' +
|
|
2365
|
+
'<h2>Ending cleanly</h2>' +
|
|
2366
|
+
'<p>Give Echo a moment to mark this setup as finished for now. Everything already captured stays saved.</p>' +
|
|
2367
|
+
'<div class="miniBar"><i></i></div>' +
|
|
2368
|
+
'</div>';
|
|
2369
|
+
}
|
|
2370
|
+
function renderSkipped() {
|
|
2371
|
+
setCityMode(false);
|
|
2372
|
+
setExtractMode(true);
|
|
2373
|
+
setHead("Setup paused", "Skipped");
|
|
2374
|
+
app.className = "";
|
|
2375
|
+
app.innerHTML =
|
|
2376
|
+
'<div class="skipStage">' +
|
|
2377
|
+
'<span class="seal">Skipped for now</span>' +
|
|
2378
|
+
'<h2>You can come back anytime.</h2>' +
|
|
2379
|
+
'<p>Echo saved what already finished. When you are ready, run <code>echomem-mcp migrate</code> and it will continue from the remaining conversations.</p>' +
|
|
2380
|
+
hudTip() +
|
|
2381
|
+
'</div>';
|
|
2382
|
+
}
|
|
2383
|
+
async function endExtraction() {
|
|
2384
|
+
if (extractionEnded) return;
|
|
2385
|
+
var ok = window.confirm("End extraction for now? You can come back anytime. Everything already captured stays saved, and the next run continues from what remains.");
|
|
2386
|
+
if (!ok) return;
|
|
2387
|
+
extractionEnded = true;
|
|
2388
|
+
renderEnding();
|
|
2389
|
+
try {
|
|
2390
|
+
await postJson("/skip", {});
|
|
2391
|
+
} catch (_) {
|
|
2392
|
+
/* The bridge may close while ending; the local page can still show the paused state. */
|
|
2393
|
+
}
|
|
2394
|
+
await sleep(650);
|
|
2395
|
+
renderSkipped();
|
|
1513
2396
|
}
|
|
1514
2397
|
async function skip() {
|
|
1515
2398
|
if (decisionMade) return;
|
|
1516
2399
|
decisionMade = true;
|
|
1517
2400
|
try {
|
|
1518
2401
|
await postJson("/skip", {});
|
|
1519
|
-
|
|
1520
|
-
setHead("Setup complete", "Done");
|
|
1521
|
-
app.className = "notice";
|
|
1522
|
-
app.innerHTML = '<h2 style="color:#203820">All set</h2><p style="margin-top:10px">You can extract unprocessed conversations later with echomem-mcp migrate.</p>';
|
|
2402
|
+
renderSkipped();
|
|
1523
2403
|
} catch (e) {
|
|
1524
2404
|
decisionMade = false;
|
|
1525
2405
|
renderDashboard(e && e.message);
|
|
@@ -1605,15 +2485,53 @@ export function renderSetupPage() {
|
|
|
1605
2485
|
getJson("/config").then(function (cfg) {
|
|
1606
2486
|
authUrl = cfg.authUrl || authUrl;
|
|
1607
2487
|
connected = !!cfg.connected;
|
|
2488
|
+
workspacePath = cfg.workspacePath || workspacePath;
|
|
1608
2489
|
}).catch(function () {});
|
|
1609
2490
|
// Scan-first: show the local forensic report before asking the user to connect.
|
|
1610
2491
|
await showReport();
|
|
1611
2492
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
2493
|
+
// Dev/design preview: ?preview=extract-counting|extract-ready|extract-run|extract-overdue|extract-done|extract-ending|extract-skipped renders that extraction
|
|
2494
|
+
// state directly with sample data (no server, no nonce). Lets us eyeball every state of the
|
|
2495
|
+
// first-impression flow without running a real 3000-session import.
|
|
2496
|
+
var previewState = params.get("preview");
|
|
2497
|
+
if (previewState === "extract-counting") {
|
|
2498
|
+
stats = null;
|
|
2499
|
+
renderDashboard();
|
|
2500
|
+
} else if (previewState === "extract-ready") {
|
|
2501
|
+
stats = {
|
|
2502
|
+
sessions: { total: 136, codex: 82, claudeCode: 54 },
|
|
2503
|
+
migratable: {
|
|
2504
|
+
pending: 28,
|
|
2505
|
+
pendingTotal: 28,
|
|
2506
|
+
pendingCodex: 19,
|
|
2507
|
+
pendingClaudeCode: 9,
|
|
2508
|
+
alreadyMigrated: 108,
|
|
2509
|
+
skippedActive: 0,
|
|
2510
|
+
eta: { estimatedLabel: "under 2 minutes" },
|
|
2511
|
+
estimatedLabel: "under 2 minutes",
|
|
2512
|
+
},
|
|
2513
|
+
discovery: { phase: "account", exact: false },
|
|
2514
|
+
};
|
|
2515
|
+
renderDashboard();
|
|
2516
|
+
} else if (previewState === "extract-run") {
|
|
2517
|
+
renderProgress({ status: "running", total: 28, completed: 4, extracted: 19, failed: 1, latest: "codex 2026-07-01", latestRepo: "EchoMem-Chrome" });
|
|
2518
|
+
} else if (previewState === "extract-overdue") {
|
|
2519
|
+
exStartAt = Date.now() - 200000;
|
|
2520
|
+
exDeadline = Date.now() - 1000;
|
|
2521
|
+
renderProgress({ status: "running", total: 28, completed: 21, extracted: 96, failed: 2, latest: "claude-code 2026-06-28", latestRepo: "DG-Web" });
|
|
2522
|
+
} else if (previewState === "extract-done") {
|
|
2523
|
+
renderProgress({ status: "completed", total: 28, completed: 28, extracted: 128, failed: 0 });
|
|
2524
|
+
} else if (previewState === "extract-ending") {
|
|
2525
|
+
renderEnding();
|
|
2526
|
+
} else if (previewState === "extract-skipped") {
|
|
2527
|
+
renderSkipped();
|
|
2528
|
+
} else {
|
|
2529
|
+
init().catch(function (e) {
|
|
2530
|
+
setHead("Connection issue", "Needs attention");
|
|
2531
|
+
app.className = "error";
|
|
2532
|
+
app.textContent = e && e.message ? e.message : String(e);
|
|
2533
|
+
});
|
|
2534
|
+
}
|
|
1617
2535
|
})();
|
|
1618
2536
|
</script>
|
|
1619
2537
|
</body>
|