@sansynx/erroratlas 0.1.1 → 0.1.3
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 +30 -67
- package/dist/worker/supabase.js +36 -11
- package/dist/worker/ui.js +207 -37
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -4,6 +4,36 @@ ErrorAtlas is hosted debugging memory for coding agents.
|
|
|
4
4
|
|
|
5
5
|
Developers do not clone this repo, create Supabase projects, deploy Workers, or manage ErrorAtlas infrastructure. They install the npm MCP client, generate one hosted MCP key, run Supermemory locally, and let their coding agent search and publish verified fixes.
|
|
6
6
|
|
|
7
|
+
## Working flow
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart TD
|
|
11
|
+
Dev[Developer] --> Console[Hosted ErrorAtlas dashboard]
|
|
12
|
+
Console --> Key[Generate one MCP key]
|
|
13
|
+
|
|
14
|
+
Dev --> Agent[Codex / Cursor / Windsurf / OpenCode]
|
|
15
|
+
Agent --> MCP[Local ErrorAtlas MCP server]
|
|
16
|
+
|
|
17
|
+
MCP --> LocalMemory[Supermemory local]
|
|
18
|
+
MCP --> API[Hosted ErrorAtlas API]
|
|
19
|
+
|
|
20
|
+
LocalMemory --> PrivateContext[Private project memory stays on machine]
|
|
21
|
+
API --> Search[Search sanitized shared fixes]
|
|
22
|
+
API --> Store[Store sanitized incidents and verified playbooks]
|
|
23
|
+
|
|
24
|
+
Search --> Agent
|
|
25
|
+
PrivateContext --> Agent
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```txt
|
|
29
|
+
1. Developer signs in at the hosted dashboard.
|
|
30
|
+
2. Dashboard generates one MCP key.
|
|
31
|
+
3. Developer installs @sansynx/erroratlas in their project.
|
|
32
|
+
4. Coding agent connects to the local ErrorAtlas MCP server.
|
|
33
|
+
5. MCP searches Supermemory local plus hosted ErrorAtlas fixes.
|
|
34
|
+
6. After a verified fix, MCP stores sanitized incidents and playbooks remotely.
|
|
35
|
+
```
|
|
36
|
+
|
|
7
37
|
## End-user flow
|
|
8
38
|
|
|
9
39
|
1. Open the hosted dashboard:
|
|
@@ -89,70 +119,3 @@ erroratlas.get_playbook
|
|
|
89
119
|
- ErrorAtlas remote stores sanitized searchable incidents and playbooks.
|
|
90
120
|
- Agent keys are shown once and stored remotely only as hashes.
|
|
91
121
|
- Sensitive incident payload snapshots are encrypted before database insert.
|
|
92
|
-
|
|
93
|
-
## Operator setup
|
|
94
|
-
|
|
95
|
-
This section is only for the ErrorAtlas service owner.
|
|
96
|
-
|
|
97
|
-
```bash
|
|
98
|
-
cp .dev.vars.example .dev.vars
|
|
99
|
-
npm install
|
|
100
|
-
npm run dev
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Apply the database schema from:
|
|
104
|
-
|
|
105
|
-
```txt
|
|
106
|
-
supabase/schema.sql
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Set production Worker secrets:
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
npx wrangler secret put PUBLIC_SUPABASE_URL
|
|
113
|
-
npx wrangler secret put PUBLIC_SUPABASE_ANON_KEY
|
|
114
|
-
npx wrangler secret put SUPABASE_SERVICE_ROLE_KEY
|
|
115
|
-
npx wrangler secret put FIELD_ENCRYPTION_KEY
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Deploy:
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
npm run deploy
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
Publish the npm package from an npm account or organization you control:
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
npm login
|
|
128
|
-
npm publish --access public
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
The package name is `@sansynx/erroratlas` because the unscoped npm name `erroratlas` is already owned by another package. The CLI command remains `erroratlas`.
|
|
132
|
-
|
|
133
|
-
## Project structure
|
|
134
|
-
|
|
135
|
-
```txt
|
|
136
|
-
src/worker Cloudflare Worker UI and API
|
|
137
|
-
src/mcp local MCP server for coding agents
|
|
138
|
-
src/cli npm package CLI for init and MCP startup
|
|
139
|
-
src/shared redaction and playbook utilities
|
|
140
|
-
supabase service-owner database schema
|
|
141
|
-
templates files copied by npx erroratlas init
|
|
142
|
-
public Worker-served public assets
|
|
143
|
-
scripts maintainer build and smoke-test scripts
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
## Important routes
|
|
147
|
-
|
|
148
|
-
```txt
|
|
149
|
-
GET / landing page
|
|
150
|
-
GET /setup public setup guide
|
|
151
|
-
GET /dashboard protected MCP key console
|
|
152
|
-
GET /llms.txt machine-readable agent guide
|
|
153
|
-
GET /api/health service health
|
|
154
|
-
POST /api/search search approved fixes
|
|
155
|
-
POST /api/agent-keys create or rotate MCP key
|
|
156
|
-
POST /api/incidents capture sanitized error signal
|
|
157
|
-
POST /api/resolutions publish verified resolution
|
|
158
|
-
```
|
package/dist/worker/supabase.js
CHANGED
|
@@ -62,21 +62,12 @@ async function getOrCreateHumanActor(env, user) {
|
|
|
62
62
|
}
|
|
63
63
|
const slug = `personal-${user.id.slice(0, 8)}`;
|
|
64
64
|
const orgName = "Personal atlas";
|
|
65
|
-
const
|
|
66
|
-
method: "POST",
|
|
67
|
-
body: JSON.stringify([{ name: orgName, slug }])
|
|
68
|
-
});
|
|
69
|
-
const orgId = org[0]?.id;
|
|
70
|
-
if (!orgId)
|
|
71
|
-
throw new HttpError(500, "org_create_failed", "Could not create organization.");
|
|
65
|
+
const orgId = await ensurePersonalOrg(env, slug, orgName);
|
|
72
66
|
await supabaseRest(env, "organization_members", {
|
|
73
67
|
method: "POST",
|
|
74
68
|
body: JSON.stringify([{ org_id: orgId, user_id: user.id, role: "owner" }])
|
|
75
69
|
});
|
|
76
|
-
await
|
|
77
|
-
method: "POST",
|
|
78
|
-
body: JSON.stringify([{ org_id: orgId, name: "Default", slug: "default", visibility: "team" }])
|
|
79
|
-
});
|
|
70
|
+
await ensureDefaultProject(env, orgId);
|
|
80
71
|
return {
|
|
81
72
|
kind: "human",
|
|
82
73
|
userId: user.id,
|
|
@@ -87,6 +78,40 @@ async function getOrCreateHumanActor(env, user) {
|
|
|
87
78
|
role: "owner"
|
|
88
79
|
};
|
|
89
80
|
}
|
|
81
|
+
async function ensurePersonalOrg(env, slug, orgName) {
|
|
82
|
+
try {
|
|
83
|
+
const org = await supabaseRest(env, "organizations", {
|
|
84
|
+
method: "POST",
|
|
85
|
+
body: JSON.stringify([{ name: orgName, slug }])
|
|
86
|
+
});
|
|
87
|
+
const orgId = org[0]?.id;
|
|
88
|
+
if (!orgId)
|
|
89
|
+
throw new HttpError(500, "org_create_failed", "Could not create workspace.");
|
|
90
|
+
return orgId;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (!(error instanceof HttpError && /duplicate|unique|23505|409/i.test(error.message)))
|
|
94
|
+
throw error;
|
|
95
|
+
const existing = await supabaseRest(env, `organizations?select=id&slug=eq.${encodeURIComponent(slug)}&limit=1`);
|
|
96
|
+
const orgId = existing[0]?.id;
|
|
97
|
+
if (!orgId)
|
|
98
|
+
throw error;
|
|
99
|
+
return orgId;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function ensureDefaultProject(env, orgId) {
|
|
103
|
+
try {
|
|
104
|
+
await supabaseRest(env, "projects", {
|
|
105
|
+
method: "POST",
|
|
106
|
+
body: JSON.stringify([{ org_id: orgId, name: "Default", slug: "default", visibility: "team" }])
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (error instanceof HttpError && /duplicate|unique|23505|409/i.test(error.message))
|
|
111
|
+
return;
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
90
115
|
async function ensureUserProfile(env, user) {
|
|
91
116
|
const existing = await supabaseRest(env, `user_profiles?select=user_id,username,display_name,bio&user_id=eq.${encodeURIComponent(user.id)}&limit=1`);
|
|
92
117
|
if (existing[0])
|
package/dist/worker/ui.js
CHANGED
|
@@ -369,6 +369,9 @@ button { cursor: pointer; }
|
|
|
369
369
|
.nav-links { display: flex; align-items: center; gap: 10px; color: var(--body); }
|
|
370
370
|
.nav-links a,
|
|
371
371
|
.nav-links button { display: inline-flex; align-items: center; justify-content: center; }
|
|
372
|
+
.nav-actions {
|
|
373
|
+
display: contents;
|
|
374
|
+
}
|
|
372
375
|
.nav-menu {
|
|
373
376
|
display: none;
|
|
374
377
|
min-height: 38px;
|
|
@@ -674,7 +677,6 @@ ul, ol { margin: 0; padding-left: 20px; color: var(--body); }
|
|
|
674
677
|
}
|
|
675
678
|
.nav-menu {
|
|
676
679
|
display: inline-flex;
|
|
677
|
-
justify-self: end;
|
|
678
680
|
width: 40px;
|
|
679
681
|
height: 40px;
|
|
680
682
|
padding: 0;
|
|
@@ -712,6 +714,21 @@ ul, ol { margin: 0; padding-left: 20px; color: var(--body); }
|
|
|
712
714
|
transform: translateY(-4px);
|
|
713
715
|
transition: max-height 240ms ease, opacity 180ms ease, transform 220ms ease, margin 220ms ease, padding 220ms ease, border-color 180ms ease;
|
|
714
716
|
}
|
|
717
|
+
.nav-actions {
|
|
718
|
+
display: flex;
|
|
719
|
+
justify-self: end;
|
|
720
|
+
align-items: center;
|
|
721
|
+
gap: 4px;
|
|
722
|
+
}
|
|
723
|
+
.nav-actions .theme-toggle {
|
|
724
|
+
display: inline-flex;
|
|
725
|
+
width: 38px;
|
|
726
|
+
min-width: 38px;
|
|
727
|
+
height: 38px;
|
|
728
|
+
min-height: 38px;
|
|
729
|
+
border: 0;
|
|
730
|
+
background: transparent;
|
|
731
|
+
}
|
|
715
732
|
.nav.open .nav-links {
|
|
716
733
|
max-height: 260px;
|
|
717
734
|
margin: 6px 0 0;
|
|
@@ -722,7 +739,6 @@ ul, ol { margin: 0; padding-left: 20px; color: var(--body); }
|
|
|
722
739
|
transform: translateY(0);
|
|
723
740
|
}
|
|
724
741
|
.nav-links .button,
|
|
725
|
-
.nav-links .theme-toggle,
|
|
726
742
|
.nav-links > a:not(.button) {
|
|
727
743
|
width: 100%;
|
|
728
744
|
min-height: 40px;
|
|
@@ -812,33 +828,178 @@ ul, ol { margin: 0; padding-left: 20px; color: var(--body); }
|
|
|
812
828
|
padding-left: 12px;
|
|
813
829
|
transform: none;
|
|
814
830
|
}
|
|
815
|
-
.nav.open .
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
831
|
+
.nav.open .nav-links > *:last-child {
|
|
832
|
+
border-bottom: 0;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
.nav {
|
|
837
|
+
min-height: 64px;
|
|
838
|
+
margin-top: 18px;
|
|
839
|
+
padding: 10px 12px;
|
|
840
|
+
border: 1px solid var(--line);
|
|
841
|
+
border-radius: 20px;
|
|
842
|
+
background:
|
|
843
|
+
linear-gradient(180deg, color-mix(in srgb, var(--surface) 92%, transparent), color-mix(in srgb, var(--surface-2) 78%, transparent));
|
|
844
|
+
box-shadow: 0 14px 48px rgba(0, 0, 0, 0.08);
|
|
845
|
+
}
|
|
846
|
+
.brand {
|
|
847
|
+
gap: 9px;
|
|
848
|
+
padding-left: 4px;
|
|
849
|
+
}
|
|
850
|
+
.brand::before {
|
|
851
|
+
display: none;
|
|
852
|
+
content: none;
|
|
853
|
+
}
|
|
854
|
+
.nav-links {
|
|
855
|
+
gap: 6px;
|
|
856
|
+
}
|
|
857
|
+
.nav-links > a:not(.button),
|
|
858
|
+
.nav-links .button,
|
|
859
|
+
.nav-links .theme-toggle,
|
|
860
|
+
.nav-actions .theme-toggle {
|
|
861
|
+
height: 38px;
|
|
862
|
+
min-height: 38px;
|
|
863
|
+
border-radius: 12px;
|
|
864
|
+
}
|
|
865
|
+
.nav-links > a:not(.button) {
|
|
866
|
+
padding: 0 12px;
|
|
867
|
+
color: var(--body);
|
|
868
|
+
}
|
|
869
|
+
.nav-links > a:not(.button):hover {
|
|
870
|
+
color: var(--fg);
|
|
871
|
+
background: var(--surface-2);
|
|
872
|
+
}
|
|
873
|
+
.theme-toggle {
|
|
874
|
+
border-color: transparent;
|
|
875
|
+
background: var(--surface-2);
|
|
876
|
+
}
|
|
877
|
+
.secret-strip {
|
|
878
|
+
display: grid;
|
|
879
|
+
gap: 14px;
|
|
880
|
+
margin: 18px 0 20px;
|
|
881
|
+
padding: 16px;
|
|
882
|
+
border: 1px solid var(--line);
|
|
883
|
+
border-radius: 16px;
|
|
884
|
+
background: var(--surface-2);
|
|
885
|
+
}
|
|
886
|
+
.secret-strip.is-ready {
|
|
887
|
+
border-color: color-mix(in srgb, var(--good) 45%, var(--line));
|
|
888
|
+
background: color-mix(in srgb, var(--green-100) 48%, var(--surface));
|
|
889
|
+
}
|
|
890
|
+
.secret-strip strong {
|
|
891
|
+
display: block;
|
|
892
|
+
margin-bottom: 4px;
|
|
893
|
+
color: var(--fg);
|
|
894
|
+
}
|
|
895
|
+
.secret-strip span {
|
|
896
|
+
color: var(--muted);
|
|
897
|
+
}
|
|
898
|
+
.secret-copy-row {
|
|
899
|
+
display: grid;
|
|
900
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
901
|
+
align-items: center;
|
|
902
|
+
gap: 10px;
|
|
903
|
+
}
|
|
904
|
+
.secret-token {
|
|
905
|
+
display: block;
|
|
906
|
+
min-width: 0;
|
|
907
|
+
padding: 10px 12px;
|
|
908
|
+
overflow: hidden;
|
|
909
|
+
border: 1px solid var(--line);
|
|
910
|
+
border-radius: 10px;
|
|
911
|
+
background: var(--surface);
|
|
912
|
+
color: var(--fg) !important;
|
|
913
|
+
font-family: var(--font-mono);
|
|
914
|
+
font-size: 13px;
|
|
915
|
+
text-overflow: ellipsis;
|
|
916
|
+
white-space: nowrap;
|
|
917
|
+
}
|
|
918
|
+
.secret-copy-row .button {
|
|
919
|
+
height: 40px;
|
|
920
|
+
min-height: 40px;
|
|
921
|
+
border-radius: 10px;
|
|
922
|
+
}
|
|
923
|
+
@media (max-width: 760px) {
|
|
924
|
+
.nav {
|
|
925
|
+
margin-top: 12px;
|
|
926
|
+
padding: 10px 0;
|
|
927
|
+
border-color: transparent;
|
|
825
928
|
border-radius: 0;
|
|
826
929
|
background: transparent;
|
|
827
|
-
color: var(--fg);
|
|
828
930
|
box-shadow: none;
|
|
829
931
|
}
|
|
830
|
-
.nav.open
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
932
|
+
.nav.open {
|
|
933
|
+
padding: 10px 0 0;
|
|
934
|
+
border-radius: 0;
|
|
935
|
+
background: transparent;
|
|
936
|
+
border-color: transparent;
|
|
937
|
+
box-shadow: none;
|
|
834
938
|
}
|
|
835
|
-
.nav.open .
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
939
|
+
.nav.open .nav-links {
|
|
940
|
+
margin-top: 14px;
|
|
941
|
+
padding: 12px 0 0;
|
|
942
|
+
border-top: 1px solid var(--line);
|
|
943
|
+
background: transparent;
|
|
839
944
|
}
|
|
840
|
-
.nav.open .nav-links
|
|
945
|
+
.nav.open .nav-links .button,
|
|
946
|
+
.nav.open .nav-links > a:not(.button) {
|
|
947
|
+
justify-content: flex-start;
|
|
948
|
+
height: 44px;
|
|
949
|
+
min-height: 44px;
|
|
950
|
+
padding: 0 10px;
|
|
951
|
+
border-radius: 12px;
|
|
952
|
+
border: 0;
|
|
841
953
|
border-bottom: 0;
|
|
954
|
+
background: transparent;
|
|
955
|
+
}
|
|
956
|
+
.secret-copy-row {
|
|
957
|
+
grid-template-columns: 1fr;
|
|
958
|
+
}
|
|
959
|
+
.secret-copy-row .button {
|
|
960
|
+
width: 100%;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
.nav-menu {
|
|
964
|
+
background: transparent;
|
|
965
|
+
}
|
|
966
|
+
@media (max-width: 760px) {
|
|
967
|
+
.brand {
|
|
968
|
+
padding-left: 0;
|
|
969
|
+
font-size: 14px;
|
|
970
|
+
height: 40px;
|
|
971
|
+
align-items: center;
|
|
972
|
+
}
|
|
973
|
+
.nav-menu {
|
|
974
|
+
justify-self: end;
|
|
975
|
+
width: 38px;
|
|
976
|
+
min-width: 38px;
|
|
977
|
+
height: 38px;
|
|
978
|
+
background: transparent;
|
|
979
|
+
}
|
|
980
|
+
.nav.open .nav-links {
|
|
981
|
+
gap: 8px;
|
|
982
|
+
}
|
|
983
|
+
.nav.open .nav-menu {
|
|
984
|
+
background: transparent;
|
|
985
|
+
}
|
|
986
|
+
.nav.open .nav-links .button,
|
|
987
|
+
.nav.open .nav-links > a:not(.button) {
|
|
988
|
+
align-items: center;
|
|
989
|
+
justify-content: center;
|
|
990
|
+
padding-left: 0;
|
|
991
|
+
padding-right: 0;
|
|
992
|
+
font-size: 14px;
|
|
993
|
+
line-height: 1;
|
|
994
|
+
text-align: center;
|
|
995
|
+
}
|
|
996
|
+
.landing-page .hero,
|
|
997
|
+
.setup-page .hero {
|
|
998
|
+
justify-items: center;
|
|
999
|
+
text-align: center;
|
|
1000
|
+
}
|
|
1001
|
+
.landing-page .hero {
|
|
1002
|
+
padding-top: 64px;
|
|
842
1003
|
}
|
|
843
1004
|
}
|
|
844
1005
|
</style>`;
|
|
@@ -851,6 +1012,7 @@ export function renderLandingUi(env) {
|
|
|
851
1012
|
<meta charset="utf-8">
|
|
852
1013
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
853
1014
|
<title>${appName}</title>
|
|
1015
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
854
1016
|
${themeBootScript()}
|
|
855
1017
|
${sharedCss()}
|
|
856
1018
|
<style>
|
|
@@ -1017,6 +1179,10 @@ ${sharedCss()}
|
|
|
1017
1179
|
}
|
|
1018
1180
|
.footer {
|
|
1019
1181
|
flex-direction: column;
|
|
1182
|
+
align-items: center;
|
|
1183
|
+
justify-content: center;
|
|
1184
|
+
text-align: center;
|
|
1185
|
+
width: 100%;
|
|
1020
1186
|
}
|
|
1021
1187
|
}
|
|
1022
1188
|
</style>
|
|
@@ -1025,10 +1191,12 @@ ${sharedCss()}
|
|
|
1025
1191
|
<div class="shell narrow">
|
|
1026
1192
|
<nav class="nav">
|
|
1027
1193
|
<a href="/" class="brand">ErrorAtlas</a>
|
|
1028
|
-
<
|
|
1194
|
+
<div class="nav-actions">
|
|
1195
|
+
<button class="theme-toggle" id="themeToggle" type="button" aria-label="Switch theme" title="Switch theme"><span class="theme-toggle-icon" aria-hidden="true"></span></button>
|
|
1196
|
+
<button class="nav-menu" id="navMenu" type="button" aria-expanded="false" aria-controls="navLinks" aria-label="Open navigation"><span class="nav-menu-lines" aria-hidden="true"><span class="nav-menu-line"></span><span class="nav-menu-line"></span><span class="nav-menu-line"></span></span></button>
|
|
1197
|
+
</div>
|
|
1029
1198
|
<div class="nav-links" id="navLinks">
|
|
1030
1199
|
<a href="/setup">Setup</a>
|
|
1031
|
-
<button class="theme-toggle" id="themeToggle" type="button" aria-label="Switch theme" title="Switch theme"><span class="theme-toggle-icon" aria-hidden="true"></span></button>
|
|
1032
1200
|
<a class="button primary" href="/dashboard">Dashboard</a>
|
|
1033
1201
|
</div>
|
|
1034
1202
|
</nav>
|
|
@@ -1087,6 +1255,7 @@ export function renderSetupGuideUi(env, requestOrigin) {
|
|
|
1087
1255
|
<meta charset="utf-8">
|
|
1088
1256
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1089
1257
|
<title>${appName} Setup</title>
|
|
1258
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
1090
1259
|
${themeBootScript()}
|
|
1091
1260
|
${sharedCss()}
|
|
1092
1261
|
<style>
|
|
@@ -1195,9 +1364,6 @@ ${sharedCss()}
|
|
|
1195
1364
|
font-weight: 600;
|
|
1196
1365
|
}
|
|
1197
1366
|
@media (max-width: 760px) {
|
|
1198
|
-
.setup-page .nav {
|
|
1199
|
-
border-radius: 999px;
|
|
1200
|
-
}
|
|
1201
1367
|
.setup-page .nav-links {
|
|
1202
1368
|
border-top: 1px solid var(--line);
|
|
1203
1369
|
}
|
|
@@ -1227,10 +1393,12 @@ ${sharedCss()}
|
|
|
1227
1393
|
<div class="shell narrow">
|
|
1228
1394
|
<nav class="nav">
|
|
1229
1395
|
<a href="/" class="brand">ErrorAtlas</a>
|
|
1230
|
-
<
|
|
1396
|
+
<div class="nav-actions">
|
|
1397
|
+
<button class="theme-toggle" id="themeToggle" type="button" aria-label="Switch theme" title="Switch theme"><span class="theme-toggle-icon" aria-hidden="true"></span></button>
|
|
1398
|
+
<button class="nav-menu" id="navMenu" type="button" aria-expanded="false" aria-controls="navLinks" aria-label="Open navigation"><span class="nav-menu-lines" aria-hidden="true"><span class="nav-menu-line"></span><span class="nav-menu-line"></span><span class="nav-menu-line"></span></span></button>
|
|
1399
|
+
</div>
|
|
1231
1400
|
<div class="nav-links" id="navLinks">
|
|
1232
1401
|
<a href="/dashboard">Dashboard</a>
|
|
1233
|
-
<button class="theme-toggle" id="themeToggle" type="button" aria-label="Switch theme" title="Switch theme"><span class="theme-toggle-icon" aria-hidden="true"></span></button>
|
|
1234
1402
|
<a class="button primary" href="/">Home</a>
|
|
1235
1403
|
</div>
|
|
1236
1404
|
</nav>
|
|
@@ -1372,6 +1540,7 @@ export function renderDashboardUi(env) {
|
|
|
1372
1540
|
<meta charset="utf-8">
|
|
1373
1541
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1374
1542
|
<title>${appName} Dashboard</title>
|
|
1543
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
1375
1544
|
${themeBootScript()}
|
|
1376
1545
|
${sharedCss()}
|
|
1377
1546
|
<style>
|
|
@@ -1720,10 +1889,12 @@ ${sharedCss()}
|
|
|
1720
1889
|
<div class="shell">
|
|
1721
1890
|
<nav class="nav">
|
|
1722
1891
|
<a href="/" class="brand">ErrorAtlas</a>
|
|
1723
|
-
<
|
|
1892
|
+
<div class="nav-actions">
|
|
1893
|
+
<button class="theme-toggle" id="themeToggle" type="button" aria-label="Switch theme" title="Switch theme"><span class="theme-toggle-icon" aria-hidden="true"></span></button>
|
|
1894
|
+
<button class="nav-menu" id="navMenu" type="button" aria-expanded="false" aria-controls="navLinks" aria-label="Open navigation"><span class="nav-menu-lines" aria-hidden="true"><span class="nav-menu-line"></span><span class="nav-menu-line"></span><span class="nav-menu-line"></span></span></button>
|
|
1895
|
+
</div>
|
|
1724
1896
|
<div class="nav-links" id="navLinks">
|
|
1725
1897
|
<a href="/setup">Setup</a>
|
|
1726
|
-
<button class="theme-toggle" id="themeToggle" type="button" aria-label="Switch theme" title="Switch theme"><span class="theme-toggle-icon" aria-hidden="true"></span></button>
|
|
1727
1898
|
<a class="button primary" href="/">Home</a>
|
|
1728
1899
|
</div>
|
|
1729
1900
|
</nav>
|
|
@@ -1985,7 +2156,7 @@ ${sharedCss()}
|
|
|
1985
2156
|
function renderAuth() {
|
|
1986
2157
|
title.textContent = "Sign in";
|
|
1987
2158
|
status.textContent = "Protected console";
|
|
1988
|
-
content.innerHTML = '<div class="panel auth-open-card"><span class="muted">Sign in to mint MCP keys</span><h3>Console access</h3><p>Use GitHub or a one-time email link.
|
|
2159
|
+
content.innerHTML = '<div class="panel auth-open-card"><span class="muted">Sign in to mint MCP keys</span><h3>Console access</h3><p>Use GitHub or a one-time email link. Your workspace is prepared automatically.</p></div><div class="auth-modal" id="authModal" hidden><div class="auth-dialog" role="dialog" aria-modal="true" aria-labelledby="authTitle"><div class="auth-dialog-head"><div><h3 id="authTitle">Enter ErrorAtlas</h3><p>Choose how you want to sign in.</p></div><button class="icon-button" id="closeAuthModal" type="button" aria-label="Close sign in dialog">x</button></div><div class="auth-methods"><button class="button primary" id="githubSignIn" type="button">Continue with GitHub</button><div class="auth-divider">or</div><input id="authEmail" type="email" placeholder="you@example.com" aria-label="Email address"><button class="button" id="sendEmail" type="button">Send magic link</button></div></div></div>';
|
|
1989
2160
|
}
|
|
1990
2161
|
|
|
1991
2162
|
function renderSetupMissing() {
|
|
@@ -1996,7 +2167,7 @@ ${sharedCss()}
|
|
|
1996
2167
|
}
|
|
1997
2168
|
|
|
1998
2169
|
async function signInWithGitHub() {
|
|
1999
|
-
if (!state.config || !state.config.supabaseUrl) return setMessage("
|
|
2170
|
+
if (!state.config || !state.config.supabaseUrl) return setMessage("Sign-in is not configured yet.");
|
|
2000
2171
|
const redirectTo = location.origin + "/dashboard";
|
|
2001
2172
|
location.assign(supabaseBase() + "/auth/v1/authorize?provider=github&redirect_to=" + encodeURIComponent(redirectTo));
|
|
2002
2173
|
}
|
|
@@ -2010,6 +2181,7 @@ ${sharedCss()}
|
|
|
2010
2181
|
body: JSON.stringify({ email: email, create_user: true })
|
|
2011
2182
|
});
|
|
2012
2183
|
if (!response.ok) throw new Error(await response.text());
|
|
2184
|
+
closeAuthModal();
|
|
2013
2185
|
setMessage("Magic link sent. Check your inbox for " + email + ".", "success");
|
|
2014
2186
|
}
|
|
2015
2187
|
|
|
@@ -2186,10 +2358,8 @@ ${sharedCss()}
|
|
|
2186
2358
|
renderTopBar();
|
|
2187
2359
|
render();
|
|
2188
2360
|
} catch (error) {
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
state.data = null;
|
|
2192
|
-
setMessage(error instanceof Error ? error.message : "Dashboard failed to load.");
|
|
2361
|
+
state.data = { agentKeys: [], setupWarning: "Your workspace is still being prepared. Refresh once; if it continues, sign out and sign in again." };
|
|
2362
|
+
setMessage(error instanceof Error ? error.message : "Workspace could not finish loading.");
|
|
2193
2363
|
renderTopBar();
|
|
2194
2364
|
render();
|
|
2195
2365
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sansynx/erroratlas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Hosted ErrorAtlas MCP client and local debugging memory tools for coding agents.",
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
"deploy": "wrangler deploy",
|
|
28
28
|
"build": "npm run sync:assets && tsc -p tsconfig.json",
|
|
29
29
|
"typecheck": "tsc --noEmit",
|
|
30
|
-
"test:e2e": "npm run build && node scripts/e2e-smoke.mjs",
|
|
31
30
|
"prepack": "npm run build",
|
|
32
31
|
"mcp": "tsx src/mcp/server.ts",
|
|
33
32
|
"cli": "tsx src/cli/index.ts"
|