horizon-code 0.3.1 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "horizon-code",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "AI-powered trading strategy terminal for Polymarket",
5
5
  "type": "module",
6
6
  "bin": {
package/src/app.ts CHANGED
@@ -907,7 +907,8 @@ export class App {
907
907
  this.authenticated = loggedIn;
908
908
 
909
909
  const cfg = loadConfig();
910
- const email = cfg.user_email || getUser()?.email || undefined;
910
+ const rawEmail = cfg.user_email || getUser()?.email;
911
+ const email = typeof rawEmail === "string" ? rawEmail : undefined;
911
912
 
912
913
  if (!loggedIn) {
913
914
  this.splash.setAuthStatus(false);
@@ -939,11 +940,6 @@ export class App {
939
940
 
940
941
  if (!live) {
941
942
  this.splash.setAuthStatus(true, email);
942
- // Only warn about expired session if they actually had one before
943
- // Don't warn if they just have an api_key (chat still works fine)
944
- if (config.supabase_session) {
945
- this.showSystemMsg("Supabase session expired — chat sync disabled. Type /login to reconnect.");
946
- }
947
943
  } else {
948
944
  this.splash.setAuthStatus(true, email, firstTime);
949
945
  }
@@ -85,7 +85,7 @@ export class ChatRenderer {
85
85
  id: `msg-${message.id}`,
86
86
  flexDirection: "column",
87
87
  width: "100%",
88
- backgroundColor: isUser ? COLORS.selection : undefined,
88
+ backgroundColor: undefined,
89
89
  paddingLeft: 1,
90
90
  paddingRight: 1,
91
91
  marginTop: 1,
@@ -17,7 +17,6 @@ export class Footer {
17
17
  id: "footer",
18
18
  height: 1,
19
19
  width: "100%",
20
- backgroundColor: COLORS.bgDarker,
21
20
  flexDirection: "row",
22
21
  alignItems: "center",
23
22
  paddingLeft: 2,
@@ -31,7 +31,6 @@ export class InputBar {
31
31
  width: "100%",
32
32
  flexDirection: "column",
33
33
  paddingLeft: 4,
34
- backgroundColor: COLORS.bgSecondary,
35
34
  });
36
35
  this.acBox.visible = false;
37
36
  this.container.add(this.acBox);
@@ -52,7 +51,6 @@ export class InputBar {
52
51
  width: "100%",
53
52
  flexDirection: "row",
54
53
  alignItems: "center",
55
- backgroundColor: COLORS.bgDarker,
56
54
  paddingLeft: 2,
57
55
  });
58
56
  this.container.add(inputRow);
@@ -27,7 +27,6 @@ export class Splash {
27
27
  top: 0,
28
28
  width: "100%",
29
29
  height: "100%",
30
- backgroundColor: COLORS.bg,
31
30
  flexDirection: "column",
32
31
  alignItems: "center",
33
32
  justifyContent: "center",
@@ -34,7 +34,6 @@ export class TabBar {
34
34
  width: "100%",
35
35
  flexDirection: "row",
36
36
  alignItems: "center",
37
- backgroundColor: COLORS.bgDarker,
38
37
  flexShrink: 0,
39
38
  paddingLeft: 1,
40
39
  });
@@ -115,34 +115,37 @@ export async function restoreSession(): Promise<boolean> {
115
115
  }
116
116
 
117
117
  // Try setSession first (works if access token still valid)
118
- const { data, error } = await sb.auth.setSession({
119
- access_token: accessToken,
120
- refresh_token: refreshToken,
121
- });
122
-
123
- if (!error && data.session) {
124
- if (config.api_key) platform.setApiKey(config.api_key);
125
- return true;
126
- }
118
+ try {
119
+ const { data, error } = await sb.auth.setSession({
120
+ access_token: accessToken,
121
+ refresh_token: refreshToken,
122
+ });
123
+
124
+ if (!error && data.session) {
125
+ if (config.api_key) platform.setApiKey(config.api_key);
126
+ return true;
127
+ }
128
+ } catch {}
127
129
 
128
130
  // Access token expired — try refreshing with just the refresh token
129
- const { data: refreshData, error: refreshError } = await sb.auth.refreshSession({
130
- refresh_token: refreshToken,
131
- });
132
-
133
- if (!refreshError && refreshData.session) {
134
- // Save the new tokens
135
- saveSessionTokens(config, refreshData.session.access_token, refreshData.session.refresh_token);
136
- config.user_email = refreshData.session.user.email ?? config.user_email;
137
- config.user_id = refreshData.session.user.id;
138
- saveConfig(config);
139
- if (config.api_key) platform.setApiKey(config.api_key);
140
- return true;
141
- }
131
+ try {
132
+ const { data: refreshData, error: refreshError } = await sb.auth.refreshSession({
133
+ refresh_token: refreshToken,
134
+ });
135
+
136
+ if (!refreshError && refreshData.session) {
137
+ // Save the new tokens
138
+ saveSessionTokens(config, refreshData.session.access_token, refreshData.session.refresh_token);
139
+ config.user_email = refreshData.session.user.email ?? config.user_email;
140
+ config.user_id = refreshData.session.user.id;
141
+ saveConfig(config);
142
+ if (config.api_key) platform.setApiKey(config.api_key);
143
+ return true;
144
+ }
145
+ } catch {}
142
146
 
143
- // Both failed — refresh token fully expired
144
- delete config.supabase_session;
145
- saveConfig(config);
147
+ // Both failed — don't delete the session, it might work next time
148
+ // (network issue, Supabase outage, etc.). The API key still works for chat.
146
149
  }
147
150
 
148
151
  // API key still works for the LLM proxy even without a Supabase session
@@ -211,7 +214,10 @@ export async function loginWithBrowser(): Promise<{ success: boolean; error?: st
211
214
  // Save everything
212
215
  const config = loadConfig();
213
216
  saveSessionTokens(config, sd.session.access_token, sd.session.refresh_token);
214
- config.user_email = data.email || sd.session.user.email;
217
+ // Defensively extract email server might return string or object
218
+ const rawEmail = data.email ?? sd.session.user.email ?? sd.session.user?.user_metadata?.email;
219
+ const email = typeof rawEmail === "string" ? rawEmail : typeof rawEmail === "object" && rawEmail !== null ? (rawEmail as any).address ?? (rawEmail as any).email ?? String(rawEmail) : "";
220
+ config.user_email = email;
215
221
  config.user_id = sd.session.user.id;
216
222
 
217
223
  if (!config.api_key) {
@@ -224,7 +230,7 @@ export async function loginWithBrowser(): Promise<{ success: boolean; error?: st
224
230
  }
225
231
 
226
232
  saveConfig(config);
227
- return { success: true, email: data.email };
233
+ return { success: true, email };
228
234
  }
229
235
  }
230
236
  if (res.status === 410) return { success: false, error: "Session expired. Type /login to sign in again." };
@@ -246,7 +252,8 @@ export async function loginWithPassword(email: string, password: string): Promis
246
252
 
247
253
  const config = loadConfig();
248
254
  saveSessionTokens(config, data.session.access_token, data.session.refresh_token);
249
- config.user_email = data.session.user.email ?? email;
255
+ const userEmail = data.session.user.email;
256
+ config.user_email = typeof userEmail === "string" ? userEmail : email;
250
257
  config.user_id = data.session.user.id;
251
258
 
252
259
  if (!config.api_key) {
@@ -19,18 +19,18 @@ interface Colors {
19
19
 
20
20
  const THEMES: Record<ThemeName, Colors> = {
21
21
  dark: {
22
- bg: "#212121", bgSecondary: "#252525", bgDarker: "#1a1a1a", selection: "#303030",
23
- text: "#e0e0e0", textMuted: "#808080", textEmphasized: "#ffffff",
24
- border: "#4b4c5c", borderDim: "#444444", borderFocus: "#fab283",
25
- primary: "#fab283", primaryDim: "#c48a62", secondary: "#5c9cf5", accent: "#9d7cd8",
26
- success: "#7fd88f", error: "#e06c75", warning: "#f5a742", info: "#56b6c2",
22
+ bg: "#1a1a1a", bgSecondary: "#1e1e1e", bgDarker: "#141414", selection: "#252525",
23
+ text: "#d4d4d4", textMuted: "#808080", textEmphasized: "#ffffff",
24
+ border: "#4b4c5c", borderDim: "#3a3a3a", borderFocus: "#fab283",
25
+ primary: "#fab283", primaryDim: "#c48a62", secondary: "#4d8ef7", accent: "#9d7cd8",
26
+ success: "#7fd88f", error: "#e06c75", warning: "#f5a742", info: "#5bb8d0",
27
27
  },
28
28
  midnight: {
29
- bg: "#0d1117", bgSecondary: "#161b22", bgDarker: "#080c12", selection: "#1c2333",
29
+ bg: "#0d1117", bgSecondary: "#131920", bgDarker: "#080c12", selection: "#182030",
30
30
  text: "#c9d1d9", textMuted: "#636e7b", textEmphasized: "#f0f6fc",
31
31
  border: "#30363d", borderDim: "#2d333b", borderFocus: "#8be9fd",
32
- primary: "#ff79c6", primaryDim: "#cc5f9e", secondary: "#8be9fd", accent: "#bd93f9",
33
- success: "#50fa7b", error: "#ff5555", warning: "#f1fa8c", info: "#8be9fd",
32
+ primary: "#ff79c6", primaryDim: "#cc5f9e", secondary: "#79b8ff", accent: "#bd93f9",
33
+ success: "#50fa7b", error: "#ff5555", warning: "#f1fa8c", info: "#79b8ff",
34
34
  },
35
35
  nord: {
36
36
  bg: "#2e3440", bgSecondary: "#3b4252", bgDarker: "#272c36", selection: "#434c5e",