@sansynx/erroratlas 0.1.1 → 0.1.2

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 CHANGED
@@ -89,70 +89,3 @@ erroratlas.get_playbook
89
89
  - ErrorAtlas remote stores sanitized searchable incidents and playbooks.
90
90
  - Agent keys are shown once and stored remotely only as hashes.
91
91
  - 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
- ```
@@ -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 org = await supabaseRest(env, "organizations", {
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 supabaseRest(env, "projects", {
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
@@ -841,6 +841,129 @@ ul, ol { margin: 0; padding-left: 20px; color: var(--body); }
841
841
  border-bottom: 0;
842
842
  }
843
843
  }
844
+
845
+ .nav {
846
+ min-height: 64px;
847
+ margin-top: 18px;
848
+ padding: 10px 12px;
849
+ border: 1px solid var(--line);
850
+ border-radius: 20px;
851
+ background:
852
+ linear-gradient(180deg, color-mix(in srgb, var(--surface) 92%, transparent), color-mix(in srgb, var(--surface-2) 78%, transparent));
853
+ box-shadow: 0 14px 48px rgba(0, 0, 0, 0.08);
854
+ }
855
+ .brand {
856
+ gap: 9px;
857
+ padding-left: 4px;
858
+ }
859
+ .brand::before {
860
+ content: "";
861
+ width: 9px;
862
+ height: 9px;
863
+ border-radius: 999px;
864
+ background: var(--fg);
865
+ box-shadow: 0 0 0 4px var(--surface-2);
866
+ }
867
+ .nav-links {
868
+ gap: 6px;
869
+ }
870
+ .nav-links > a:not(.button),
871
+ .nav-links .button,
872
+ .nav-links .theme-toggle {
873
+ height: 38px;
874
+ min-height: 38px;
875
+ border-radius: 12px;
876
+ }
877
+ .nav-links > a:not(.button) {
878
+ padding: 0 12px;
879
+ color: var(--body);
880
+ }
881
+ .nav-links > a:not(.button):hover {
882
+ color: var(--fg);
883
+ background: var(--surface-2);
884
+ }
885
+ .theme-toggle {
886
+ border-color: transparent;
887
+ background: var(--surface-2);
888
+ }
889
+ .secret-strip {
890
+ display: grid;
891
+ gap: 14px;
892
+ margin: 18px 0 20px;
893
+ padding: 16px;
894
+ border: 1px solid var(--line);
895
+ border-radius: 16px;
896
+ background: var(--surface-2);
897
+ }
898
+ .secret-strip.is-ready {
899
+ border-color: color-mix(in srgb, var(--good) 45%, var(--line));
900
+ background: color-mix(in srgb, var(--green-100) 48%, var(--surface));
901
+ }
902
+ .secret-strip strong {
903
+ display: block;
904
+ margin-bottom: 4px;
905
+ color: var(--fg);
906
+ }
907
+ .secret-strip span {
908
+ color: var(--muted);
909
+ }
910
+ .secret-copy-row {
911
+ display: grid;
912
+ grid-template-columns: minmax(0, 1fr) auto;
913
+ align-items: center;
914
+ gap: 10px;
915
+ }
916
+ .secret-token {
917
+ display: block;
918
+ min-width: 0;
919
+ padding: 10px 12px;
920
+ overflow: hidden;
921
+ border: 1px solid var(--line);
922
+ border-radius: 10px;
923
+ background: var(--surface);
924
+ color: var(--fg) !important;
925
+ font-family: var(--font-mono);
926
+ font-size: 13px;
927
+ text-overflow: ellipsis;
928
+ white-space: nowrap;
929
+ }
930
+ .secret-copy-row .button {
931
+ height: 40px;
932
+ min-height: 40px;
933
+ border-radius: 10px;
934
+ }
935
+ @media (max-width: 760px) {
936
+ .nav {
937
+ margin-top: 12px;
938
+ border-radius: 18px;
939
+ background: var(--surface);
940
+ box-shadow: 0 16px 52px rgba(0, 0, 0, 0.12);
941
+ }
942
+ .nav.open {
943
+ border-radius: 20px;
944
+ padding: 10px;
945
+ }
946
+ .nav.open .nav-links {
947
+ padding-top: 10px;
948
+ }
949
+ .nav.open .nav-links .button,
950
+ .nav.open .nav-links > a:not(.button),
951
+ .nav.open .theme-toggle {
952
+ justify-content: flex-start;
953
+ height: 44px;
954
+ min-height: 44px;
955
+ padding: 0 10px;
956
+ border-radius: 12px;
957
+ border: 0;
958
+ border-bottom: 0;
959
+ }
960
+ .secret-copy-row {
961
+ grid-template-columns: 1fr;
962
+ }
963
+ .secret-copy-row .button {
964
+ width: 100%;
965
+ }
966
+ }
844
967
  </style>`;
845
968
  }
846
969
  export function renderLandingUi(env) {
@@ -1985,7 +2108,7 @@ ${sharedCss()}
1985
2108
  function renderAuth() {
1986
2109
  title.textContent = "Sign in";
1987
2110
  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. New operators are created 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>Pick an auth method.</p></div><button class="icon-button" id="closeAuthModal" type="button" aria-label="Close sign in dialog">×</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>';
2111
+ 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
2112
  }
1990
2113
 
1991
2114
  function renderSetupMissing() {
@@ -1996,7 +2119,7 @@ ${sharedCss()}
1996
2119
  }
1997
2120
 
1998
2121
  async function signInWithGitHub() {
1999
- if (!state.config || !state.config.supabaseUrl) return setMessage("Supabase URL is missing.");
2122
+ if (!state.config || !state.config.supabaseUrl) return setMessage("Sign-in is not configured yet.");
2000
2123
  const redirectTo = location.origin + "/dashboard";
2001
2124
  location.assign(supabaseBase() + "/auth/v1/authorize?provider=github&redirect_to=" + encodeURIComponent(redirectTo));
2002
2125
  }
@@ -2010,6 +2133,7 @@ ${sharedCss()}
2010
2133
  body: JSON.stringify({ email: email, create_user: true })
2011
2134
  });
2012
2135
  if (!response.ok) throw new Error(await response.text());
2136
+ closeAuthModal();
2013
2137
  setMessage("Magic link sent. Check your inbox for " + email + ".", "success");
2014
2138
  }
2015
2139
 
@@ -2186,10 +2310,8 @@ ${sharedCss()}
2186
2310
  renderTopBar();
2187
2311
  render();
2188
2312
  } catch (error) {
2189
- saveSession(null);
2190
- state.session = null;
2191
- state.data = null;
2192
- setMessage(error instanceof Error ? error.message : "Dashboard failed to load.");
2313
+ state.data = { agentKeys: [], setupWarning: "Your workspace is still being prepared. Refresh once; if it continues, sign out and sign in again." };
2314
+ setMessage(error instanceof Error ? error.message : "Workspace could not finish loading.");
2193
2315
  renderTopBar();
2194
2316
  render();
2195
2317
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sansynx/erroratlas",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Hosted ErrorAtlas MCP client and local debugging memory tools for coding agents.",