bitcompass 0.2.0 → 0.2.1

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.
@@ -6,6 +6,158 @@ import ora from 'ora';
6
6
  import { getTokenFilePath, loadConfig, saveCredentials } from '../auth/config.js';
7
7
  import { DEFAULT_SUPABASE_ANON_KEY, DEFAULT_SUPABASE_URL } from '../auth/defaults.js';
8
8
  const CALLBACK_PORT = 38473;
9
+ /** Design tokens matching src/index.css (light theme) */
10
+ const STYLES = {
11
+ background: 'hsl(0, 0%, 98%)',
12
+ foreground: 'hsl(222, 47%, 11%)',
13
+ primary: 'hsl(221, 83%, 53%)',
14
+ primaryForeground: 'hsl(0, 0%, 100%)',
15
+ muted: 'hsl(220, 9%, 46%)',
16
+ card: 'hsl(0, 0%, 100%)',
17
+ border: 'hsl(220, 13%, 91%)',
18
+ radius: '0.625rem',
19
+ shadow: '0 10px 15px -3px hsl(220 13% 11% / 0.08), 0 4px 6px -4px hsl(220 13% 11% / 0.05)',
20
+ };
21
+ const CALLBACK_SUCCESS_HTML = `<!DOCTYPE html>
22
+ <html lang="en">
23
+ <head>
24
+ <meta charset="UTF-8" />
25
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
26
+ <title>Logged in — Bitcompass</title>
27
+ <style>
28
+ * { box-sizing: border-box; }
29
+ body {
30
+ margin: 0;
31
+ min-height: 100vh;
32
+ display: flex;
33
+ align-items: center;
34
+ justify-content: center;
35
+ background: ${STYLES.background};
36
+ color: ${STYLES.foreground};
37
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
38
+ -webkit-font-smoothing: antialiased;
39
+ padding: 1rem;
40
+ }
41
+ .card {
42
+ width: 100%;
43
+ max-width: 28rem;
44
+ background: ${STYLES.card};
45
+ border: 1px solid ${STYLES.border};
46
+ border-radius: ${STYLES.radius};
47
+ box-shadow: ${STYLES.shadow};
48
+ padding: 2rem;
49
+ text-align: center;
50
+ animation: fadeIn 0.35s ease-out;
51
+ }
52
+ @keyframes fadeIn {
53
+ from { opacity: 0; transform: translateY(8px); }
54
+ to { opacity: 1; transform: translateY(0); }
55
+ }
56
+ .icon-wrap {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ justify-content: center;
60
+ width: 4rem;
61
+ height: 4rem;
62
+ border-radius: 1rem;
63
+ background: ${STYLES.primary};
64
+ color: ${STYLES.primaryForeground};
65
+ margin-bottom: 1.25rem;
66
+ }
67
+ .icon-wrap svg { width: 2rem; height: 2rem; }
68
+ h1 {
69
+ margin: 0 0 0.5rem;
70
+ font-size: 1.5rem;
71
+ font-weight: 700;
72
+ }
73
+ .muted {
74
+ color: ${STYLES.muted};
75
+ font-size: 0.875rem;
76
+ line-height: 1.5;
77
+ margin: 0;
78
+ }
79
+ .hint {
80
+ margin-top: 1.25rem;
81
+ padding-top: 1.25rem;
82
+ border-top: 1px solid ${STYLES.border};
83
+ font-size: 0.8125rem;
84
+ color: ${STYLES.muted};
85
+ }
86
+ </style>
87
+ </head>
88
+ <body>
89
+ <div class="card">
90
+ <div class="icon-wrap" aria-hidden="true">
91
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
92
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
93
+ <polyline points="22 4 12 14.01 9 11.01"/>
94
+ </svg>
95
+ </div>
96
+ <h1>You're all set</h1>
97
+ <p class="muted">You're logged in successfully. You can close this window safely—your credentials are saved and the CLI is ready to use.</p>
98
+ <p class="hint">Return to your terminal to continue.</p>
99
+ </div>
100
+ </body>
101
+ </html>`;
102
+ const escapeHtml = (s) => s
103
+ .replace(/&/g, '&amp;')
104
+ .replace(/</g, '&lt;')
105
+ .replace(/>/g, '&gt;')
106
+ .replace(/"/g, '&quot;')
107
+ .replace(/'/g, '&#39;');
108
+ const callbackPage = (opts) => {
109
+ const iconColor = opts.isError ? 'hsl(0, 84%, 60%)' : STYLES.primary;
110
+ const safeMessage = escapeHtml(opts.message);
111
+ return `<!DOCTYPE html>
112
+ <html lang="en">
113
+ <head>
114
+ <meta charset="UTF-8" />
115
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
116
+ <title>${opts.title} — Bitcompass</title>
117
+ <style>
118
+ * { box-sizing: border-box; }
119
+ body {
120
+ margin: 0;
121
+ min-height: 100vh;
122
+ display: flex;
123
+ align-items: center;
124
+ justify-content: center;
125
+ background: ${STYLES.background};
126
+ color: ${STYLES.foreground};
127
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
128
+ -webkit-font-smoothing: antialiased;
129
+ padding: 1rem;
130
+ }
131
+ .card {
132
+ width: 100%;
133
+ max-width: 28rem;
134
+ background: ${STYLES.card};
135
+ border: 1px solid ${STYLES.border};
136
+ border-radius: ${STYLES.radius};
137
+ box-shadow: ${STYLES.shadow};
138
+ padding: 2rem;
139
+ text-align: center;
140
+ }
141
+ .icon-wrap { display: inline-flex; align-items: center; justify-content: center; width: 4rem; height: 4rem; border-radius: 1rem; margin-bottom: 1.25rem; }
142
+ .icon-wrap svg { width: 2rem; height: 2rem; }
143
+ .icon-wrap { background: ${iconColor}; color: ${STYLES.primaryForeground}; }
144
+ h1 { margin: 0 0 0.5rem; font-size: 1.25rem; font-weight: 700; }
145
+ .muted { color: ${STYLES.muted}; font-size: 0.875rem; line-height: 1.5; margin: 0; }
146
+ </style>
147
+ </head>
148
+ <body>
149
+ <div class="card">
150
+ <div class="icon-wrap" aria-hidden="true">
151
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
152
+ <circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>
153
+ </svg>
154
+ </div>
155
+ <h1>${opts.title}</h1>
156
+ <p class="muted">${safeMessage}</p>
157
+ </div>
158
+ </body>
159
+ </html>`;
160
+ };
9
161
  const createInMemoryStorage = () => {
10
162
  const store = new Map();
11
163
  return {
@@ -48,7 +200,7 @@ export const runLogin = async () => {
48
200
  const errorDesc = u.searchParams.get('error_description');
49
201
  if (errorDesc) {
50
202
  res.writeHead(200, { 'Content-Type': 'text/html' });
51
- res.end(`<!DOCTYPE html><html><body><p>Login failed: ${errorDesc}</p></body></html>`);
203
+ res.end(callbackPage({ title: 'Login failed', message: errorDesc, isError: true }));
52
204
  spinner.fail(chalk.red('Login failed: ' + errorDesc));
53
205
  server.close();
54
206
  reject(new Error(errorDesc));
@@ -56,7 +208,7 @@ export const runLogin = async () => {
56
208
  }
57
209
  if (!code) {
58
210
  res.writeHead(200, { 'Content-Type': 'text/html' });
59
- res.end('<!DOCTYPE html><html><body><p>No authorization code in URL. Try again.</p></body></html>');
211
+ res.end(callbackPage({ title: 'No authorization code', message: 'No authorization code was received. Please try logging in again.', isError: true }));
60
212
  spinner.fail(chalk.red('No code received. Try again.'));
61
213
  server.close();
62
214
  reject(new Error('No code'));
@@ -66,7 +218,7 @@ export const runLogin = async () => {
66
218
  const { data, error } = await supabase.auth.exchangeCodeForSession(code);
67
219
  if (error) {
68
220
  res.writeHead(200, { 'Content-Type': 'text/html' });
69
- res.end(`<!DOCTYPE html><html><body><p>Exchange failed: ${error.message}</p></body></html>`);
221
+ res.end(callbackPage({ title: 'Login failed', message: error.message, isError: true }));
70
222
  spinner.fail(chalk.red('Login failed: ' + error.message));
71
223
  server.close();
72
224
  reject(error);
@@ -75,7 +227,7 @@ export const runLogin = async () => {
75
227
  const session = data?.session;
76
228
  if (!session?.access_token) {
77
229
  res.writeHead(200, { 'Content-Type': 'text/html' });
78
- res.end('<!DOCTYPE html><html><body><p>No session received.</p></body></html>');
230
+ res.end(callbackPage({ title: 'No session', message: 'No session was received. Please try again.', isError: true }));
79
231
  spinner.fail(chalk.red('No session.'));
80
232
  server.close();
81
233
  reject(new Error('No session'));
@@ -89,7 +241,7 @@ export const runLogin = async () => {
89
241
  const tokenPath = getTokenFilePath();
90
242
  saveCredentials(creds);
91
243
  res.writeHead(200, { 'Content-Type': 'text/html' });
92
- res.end('<!DOCTYPE html><html><body><p>Logged in. You can close this window.</p></body></html>');
244
+ res.end(CALLBACK_SUCCESS_HTML);
93
245
  spinner.succeed(chalk.green('Logged in successfully.'));
94
246
  console.log(chalk.dim('Credentials saved to:'), tokenPath);
95
247
  server.close();
@@ -97,7 +249,7 @@ export const runLogin = async () => {
97
249
  }
98
250
  catch (e) {
99
251
  res.writeHead(200, { 'Content-Type': 'text/html' });
100
- res.end(`<!DOCTYPE html><html><body><p>Error: ${e instanceof Error ? e.message : String(e)}</p></body></html>`);
252
+ res.end(callbackPage({ title: 'Error', message: e instanceof Error ? e.message : String(e), isError: true }));
101
253
  spinner.fail('Login failed.');
102
254
  console.error(chalk.red(e instanceof Error ? e.message : String(e)));
103
255
  server.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitcompass",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "BitCompass CLI - rules, solutions, and MCP server",
5
5
  "type": "module",
6
6
  "bin": { "bitcompass": "./dist/index.js" },