bitcompass 0.2.0 → 0.2.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/dist/commands/login.js +158 -6
- package/dist/commands/mcp.js +2 -4
- package/package.json +1 -1
package/dist/commands/login.js
CHANGED
|
@@ -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, '&')
|
|
104
|
+
.replace(/</g, '<')
|
|
105
|
+
.replace(/>/g, '>')
|
|
106
|
+
.replace(/"/g, '"')
|
|
107
|
+
.replace(/'/g, ''');
|
|
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(
|
|
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('
|
|
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(
|
|
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('
|
|
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(
|
|
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(
|
|
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/dist/commands/mcp.js
CHANGED
|
@@ -2,10 +2,8 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { isLoggedIn } from '../auth/config.js';
|
|
3
3
|
import { startMcpServer } from '../mcp/server.js';
|
|
4
4
|
export const runMcpStart = async () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
process.exit(1);
|
|
8
|
-
}
|
|
5
|
+
// Do not exit when not logged in: Cursor needs the process to stay alive to complete
|
|
6
|
+
// the MCP handshake. Tools return an auth message (run bitcompass login, then restart MCP) when needed.
|
|
9
7
|
await startMcpServer();
|
|
10
8
|
};
|
|
11
9
|
export const runMcpStatus = () => {
|