@kylewadegrove/cutline-mcp-cli 0.4.2 → 0.5.0
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/Dockerfile +11 -0
- package/README.md +177 -107
- package/dist/auth/callback.js +30 -32
- package/dist/auth/keychain.js +7 -15
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +246 -0
- package/dist/commands/login.js +39 -45
- package/dist/commands/logout.js +13 -19
- package/dist/commands/serve.d.ts +1 -0
- package/dist/commands/serve.js +38 -0
- package/dist/commands/setup.d.ts +5 -0
- package/dist/commands/setup.js +255 -0
- package/dist/commands/status.js +29 -35
- package/dist/commands/upgrade.js +44 -38
- package/dist/index.js +38 -14
- package/dist/servers/chunk-7FHM2GD3.js +5836 -0
- package/dist/servers/chunk-IVWF7VYZ.js +10086 -0
- package/dist/servers/chunk-JBJYSV4P.js +139 -0
- package/dist/servers/chunk-KMUSQOTJ.js +47 -0
- package/dist/servers/chunk-PD2HN2R5.js +908 -0
- package/dist/servers/chunk-PU7TL6S3.js +91 -0
- package/dist/servers/chunk-TGSEURMN.js +46 -0
- package/dist/servers/chunk-UBBAYTW3.js +946 -0
- package/dist/servers/cutline-server.js +11512 -0
- package/dist/servers/exploration-server.js +1030 -0
- package/dist/servers/graph-metrics-DCNR7JZN.js +12 -0
- package/dist/servers/integrations-server.js +121 -0
- package/dist/servers/output-server.js +120 -0
- package/dist/servers/pipeline-O5GJPNR4.js +20 -0
- package/dist/servers/premortem-handoff-XT4K3YDJ.js +10 -0
- package/dist/servers/premortem-server.js +958 -0
- package/dist/servers/score-history-HO5KRVGC.js +6 -0
- package/dist/servers/tools-server.js +291 -0
- package/dist/utils/config-store.js +13 -21
- package/dist/utils/config.js +2 -6
- package/mcpb/manifest.json +77 -0
- package/package.json +55 -9
- package/server.json +42 -0
- package/smithery.yaml +10 -0
- package/src/auth/callback.ts +0 -102
- package/src/auth/keychain.ts +0 -16
- package/src/commands/login.ts +0 -202
- package/src/commands/logout.ts +0 -30
- package/src/commands/status.ts +0 -153
- package/src/commands/upgrade.ts +0 -121
- package/src/index.ts +0 -40
- package/src/utils/config-store.ts +0 -46
- package/src/utils/config.ts +0 -65
- package/tsconfig.json +0 -22
package/src/auth/callback.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import type { Server } from 'http';
|
|
3
|
-
|
|
4
|
-
const CALLBACK_PORT = 8765;
|
|
5
|
-
const TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
6
|
-
|
|
7
|
-
export interface CallbackResult {
|
|
8
|
-
token: string;
|
|
9
|
-
email?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function startCallbackServer(): Promise<CallbackResult> {
|
|
13
|
-
return new Promise((resolve, reject) => {
|
|
14
|
-
const app = express();
|
|
15
|
-
let server: Server;
|
|
16
|
-
|
|
17
|
-
// Timeout handler
|
|
18
|
-
const timeout = setTimeout(() => {
|
|
19
|
-
server?.close();
|
|
20
|
-
reject(new Error('Authentication timeout - no callback received'));
|
|
21
|
-
}, TIMEOUT_MS);
|
|
22
|
-
|
|
23
|
-
// Callback endpoint
|
|
24
|
-
app.get('/', (req, res) => {
|
|
25
|
-
const token = req.query.token as string;
|
|
26
|
-
const email = req.query.email as string;
|
|
27
|
-
|
|
28
|
-
if (!token) {
|
|
29
|
-
res.status(400).send('Missing token parameter');
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Send success page
|
|
34
|
-
res.send(`
|
|
35
|
-
<!DOCTYPE html>
|
|
36
|
-
<html>
|
|
37
|
-
<head>
|
|
38
|
-
<title>Cutline MCP - Authentication Successful</title>
|
|
39
|
-
<style>
|
|
40
|
-
body {
|
|
41
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
42
|
-
display: flex;
|
|
43
|
-
justify-content: center;
|
|
44
|
-
align-items: center;
|
|
45
|
-
height: 100vh;
|
|
46
|
-
margin: 0;
|
|
47
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
48
|
-
}
|
|
49
|
-
.container {
|
|
50
|
-
background: white;
|
|
51
|
-
padding: 3rem;
|
|
52
|
-
border-radius: 1rem;
|
|
53
|
-
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
54
|
-
text-align: center;
|
|
55
|
-
max-width: 400px;
|
|
56
|
-
}
|
|
57
|
-
h1 {
|
|
58
|
-
color: #667eea;
|
|
59
|
-
margin-bottom: 1rem;
|
|
60
|
-
}
|
|
61
|
-
p {
|
|
62
|
-
color: #666;
|
|
63
|
-
line-height: 1.6;
|
|
64
|
-
}
|
|
65
|
-
.checkmark {
|
|
66
|
-
font-size: 4rem;
|
|
67
|
-
color: #4CAF50;
|
|
68
|
-
}
|
|
69
|
-
</style>
|
|
70
|
-
</head>
|
|
71
|
-
<body>
|
|
72
|
-
<div class="container">
|
|
73
|
-
<div class="checkmark">✓</div>
|
|
74
|
-
<h1>Authentication Successful!</h1>
|
|
75
|
-
<p>You can now close this window and return to your terminal.</p>
|
|
76
|
-
${email ? `<p>Logged in as: <strong>${email}</strong></p>` : ''}
|
|
77
|
-
</div>
|
|
78
|
-
</body>
|
|
79
|
-
</html>
|
|
80
|
-
`);
|
|
81
|
-
|
|
82
|
-
// Clean up and resolve
|
|
83
|
-
clearTimeout(timeout);
|
|
84
|
-
server.close();
|
|
85
|
-
resolve({ token, email });
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// Start server
|
|
89
|
-
server = app.listen(CALLBACK_PORT, () => {
|
|
90
|
-
console.log(`Callback server listening on http://localhost:${CALLBACK_PORT}`);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
server.on('error', (err: any) => {
|
|
94
|
-
clearTimeout(timeout);
|
|
95
|
-
if (err.code === 'EADDRINUSE') {
|
|
96
|
-
reject(new Error(`Port ${CALLBACK_PORT} is already in use. Please close other applications and try again.`));
|
|
97
|
-
} else {
|
|
98
|
-
reject(err);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
}
|
package/src/auth/keychain.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import keytar from 'keytar';
|
|
2
|
-
|
|
3
|
-
const SERVICE_NAME = 'cutline-mcp';
|
|
4
|
-
const ACCOUNT_NAME = 'refresh-token';
|
|
5
|
-
|
|
6
|
-
export async function storeRefreshToken(token: string): Promise<void> {
|
|
7
|
-
await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, token);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export async function getRefreshToken(): Promise<string | null> {
|
|
11
|
-
return await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export async function deleteRefreshToken(): Promise<boolean> {
|
|
15
|
-
return await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
16
|
-
}
|
package/src/commands/login.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import open from 'open';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import ora from 'ora';
|
|
4
|
-
import { startCallbackServer } from '../auth/callback.js';
|
|
5
|
-
import { storeRefreshToken } from '../auth/keychain.js';
|
|
6
|
-
import { saveConfig } from '../utils/config-store.js';
|
|
7
|
-
import { getConfig, fetchFirebaseApiKey } from '../utils/config.js';
|
|
8
|
-
|
|
9
|
-
interface SubscriptionInfo {
|
|
10
|
-
status: 'free' | 'active' | 'trialing' | 'past_due' | 'canceled' | 'unknown';
|
|
11
|
-
planName?: string;
|
|
12
|
-
periodEnd?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async function getSubscriptionStatus(idToken: string, isStaging: boolean): Promise<SubscriptionInfo> {
|
|
16
|
-
try {
|
|
17
|
-
const baseUrl = isStaging
|
|
18
|
-
? 'https://us-central1-cutline-staging.cloudfunctions.net'
|
|
19
|
-
: 'https://us-central1-cutline-prod.cloudfunctions.net';
|
|
20
|
-
|
|
21
|
-
const response = await fetch(`${baseUrl}/mcpSubscriptionStatus`, {
|
|
22
|
-
method: 'GET',
|
|
23
|
-
headers: {
|
|
24
|
-
'Authorization': `Bearer ${idToken}`,
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
if (!response.ok) {
|
|
29
|
-
return { status: 'unknown' };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return await response.json();
|
|
33
|
-
} catch (error) {
|
|
34
|
-
// Silently fail - subscription check is optional during login
|
|
35
|
-
return { status: 'unknown' };
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function exchangeRefreshForIdToken(refreshToken: string, apiKey: string): Promise<string> {
|
|
40
|
-
const response = await fetch(
|
|
41
|
-
`https://securetoken.googleapis.com/v1/token?key=${apiKey}`,
|
|
42
|
-
{
|
|
43
|
-
method: 'POST',
|
|
44
|
-
headers: { 'Content-Type': 'application/json' },
|
|
45
|
-
body: JSON.stringify({
|
|
46
|
-
grant_type: 'refresh_token',
|
|
47
|
-
refresh_token: refreshToken,
|
|
48
|
-
}),
|
|
49
|
-
}
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
if (!response.ok) {
|
|
53
|
-
throw new Error('Failed to get ID token');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const data = await response.json();
|
|
57
|
-
return data.id_token;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function exchangeCustomToken(customToken: string, apiKey: string): Promise<{ refreshToken: string; email?: string }> {
|
|
61
|
-
const response = await fetch(
|
|
62
|
-
`https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apiKey}`,
|
|
63
|
-
{
|
|
64
|
-
method: 'POST',
|
|
65
|
-
headers: { 'Content-Type': 'application/json' },
|
|
66
|
-
body: JSON.stringify({
|
|
67
|
-
token: customToken,
|
|
68
|
-
returnSecureToken: true,
|
|
69
|
-
}),
|
|
70
|
-
}
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
if (!response.ok) {
|
|
74
|
-
const error = await response.text();
|
|
75
|
-
throw new Error(`Failed to exchange custom token: ${error}`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const data = await response.json();
|
|
79
|
-
return {
|
|
80
|
-
refreshToken: data.refreshToken,
|
|
81
|
-
email: data.email,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export async function loginCommand(options: { staging?: boolean; signup?: boolean; email?: string }) {
|
|
86
|
-
const config = getConfig(options);
|
|
87
|
-
|
|
88
|
-
if (options.signup) {
|
|
89
|
-
console.log(chalk.bold('\n🚀 Cutline MCP - Create Account\n'));
|
|
90
|
-
} else {
|
|
91
|
-
console.log(chalk.bold('\n🔐 Cutline MCP Authentication\n'));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (options.staging) {
|
|
95
|
-
console.log(chalk.yellow(' ⚠️ Using STAGING environment\n'));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (options.email) {
|
|
99
|
-
console.log(chalk.gray(` Requesting sign-in as: ${options.email}\n`));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const spinner = ora('Starting authentication flow...').start();
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
// Fetch Firebase API key from web app endpoint
|
|
107
|
-
spinner.text = 'Fetching configuration...';
|
|
108
|
-
let firebaseApiKey: string;
|
|
109
|
-
try {
|
|
110
|
-
firebaseApiKey = await fetchFirebaseApiKey(options);
|
|
111
|
-
} catch (error) {
|
|
112
|
-
spinner.fail(chalk.red('Failed to fetch Firebase configuration'));
|
|
113
|
-
if (error instanceof Error) {
|
|
114
|
-
console.error(chalk.red(` ${error.message}`));
|
|
115
|
-
}
|
|
116
|
-
console.error(chalk.gray('\n You can also set the FIREBASE_API_KEY environment variable manually.\n'));
|
|
117
|
-
process.exit(1);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Start callback server
|
|
121
|
-
spinner.text = 'Waiting for authentication...';
|
|
122
|
-
const serverPromise = startCallbackServer();
|
|
123
|
-
|
|
124
|
-
// Open browser
|
|
125
|
-
let authUrl = `${config.AUTH_URL}?callback=${encodeURIComponent(config.CALLBACK_URL)}`;
|
|
126
|
-
if (options.signup) {
|
|
127
|
-
authUrl += '&mode=signup';
|
|
128
|
-
}
|
|
129
|
-
if (options.email) {
|
|
130
|
-
authUrl += `&email=${encodeURIComponent(options.email)}`;
|
|
131
|
-
}
|
|
132
|
-
await open(authUrl);
|
|
133
|
-
spinner.text = options.signup
|
|
134
|
-
? 'Browser opened - please create your account'
|
|
135
|
-
: 'Browser opened - please sign in or create an account';
|
|
136
|
-
|
|
137
|
-
// Wait for callback with custom token
|
|
138
|
-
const result = await serverPromise;
|
|
139
|
-
|
|
140
|
-
// Exchange custom token for refresh token
|
|
141
|
-
spinner.text = 'Exchanging token...';
|
|
142
|
-
const { refreshToken, email } = await exchangeCustomToken(result.token, firebaseApiKey);
|
|
143
|
-
|
|
144
|
-
// Store refresh token
|
|
145
|
-
try {
|
|
146
|
-
await storeRefreshToken(refreshToken);
|
|
147
|
-
} catch (error) {
|
|
148
|
-
console.warn(chalk.yellow(' ⚠️ Could not save to Keychain (skipping)'));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Save to file config (cross-platform)
|
|
152
|
-
try {
|
|
153
|
-
saveConfig({
|
|
154
|
-
refreshToken,
|
|
155
|
-
environment: options.staging ? 'staging' : 'production',
|
|
156
|
-
});
|
|
157
|
-
} catch (error) {
|
|
158
|
-
console.error(chalk.red(' ✗ Failed to save config file:'), error);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
spinner.succeed(chalk.green('Successfully authenticated!'));
|
|
162
|
-
|
|
163
|
-
// Show environment indicator
|
|
164
|
-
const envLabel = options.staging ? chalk.yellow('STAGING') : chalk.green('PRODUCTION');
|
|
165
|
-
console.log(chalk.gray(` Environment: ${envLabel}`));
|
|
166
|
-
|
|
167
|
-
if (email || result.email) {
|
|
168
|
-
console.log(chalk.gray(` Logged in as: ${email || result.email}`));
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Check subscription status
|
|
172
|
-
try {
|
|
173
|
-
spinner.start('Checking subscription...');
|
|
174
|
-
const idToken = await exchangeRefreshForIdToken(refreshToken, firebaseApiKey);
|
|
175
|
-
const subscription = await getSubscriptionStatus(idToken, !!options.staging);
|
|
176
|
-
spinner.stop();
|
|
177
|
-
|
|
178
|
-
if (subscription.status === 'active' || subscription.status === 'trialing') {
|
|
179
|
-
const statusLabel = subscription.status === 'trialing' ? ' (trial)' : '';
|
|
180
|
-
console.log(chalk.gray(' Plan:'), chalk.green(`✓ ${subscription.planName || 'Premium'}${statusLabel}`));
|
|
181
|
-
} else {
|
|
182
|
-
console.log(chalk.gray(' Plan:'), chalk.white('Free'));
|
|
183
|
-
console.log(chalk.dim(' Upgrade at'), chalk.cyan('https://thecutline.ai/pricing'));
|
|
184
|
-
}
|
|
185
|
-
} catch {
|
|
186
|
-
spinner.stop();
|
|
187
|
-
// Silently skip subscription check on error
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
console.log(chalk.gray('\n MCP servers can now access your account\n'));
|
|
191
|
-
console.log(chalk.dim(' Run'), chalk.cyan('cutline-mcp status'), chalk.dim('to verify\n'));
|
|
192
|
-
|
|
193
|
-
} catch (error) {
|
|
194
|
-
spinner.fail(chalk.red('Authentication failed'));
|
|
195
|
-
|
|
196
|
-
if (error instanceof Error) {
|
|
197
|
-
console.error(chalk.red(` ${error.message}\n`));
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
process.exit(1);
|
|
201
|
-
}
|
|
202
|
-
}
|
package/src/commands/logout.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import ora from 'ora';
|
|
3
|
-
import { deleteRefreshToken } from '../auth/keychain.js';
|
|
4
|
-
|
|
5
|
-
export async function logoutCommand() {
|
|
6
|
-
console.log(chalk.bold('\n👋 Logging out of Cutline MCP\n'));
|
|
7
|
-
|
|
8
|
-
const spinner = ora('Removing stored credentials...').start();
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
const deleted = await deleteRefreshToken();
|
|
12
|
-
|
|
13
|
-
if (deleted) {
|
|
14
|
-
spinner.succeed(chalk.green('Successfully logged out'));
|
|
15
|
-
console.log(chalk.gray(' Credentials removed from keychain\n'));
|
|
16
|
-
} else {
|
|
17
|
-
spinner.info(chalk.yellow('No credentials found'));
|
|
18
|
-
console.log(chalk.gray(' You were not logged in\n'));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
} catch (error) {
|
|
22
|
-
spinner.fail(chalk.red('Logout failed'));
|
|
23
|
-
|
|
24
|
-
if (error instanceof Error) {
|
|
25
|
-
console.error(chalk.red(` ${error.message}\n`));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
}
|
package/src/commands/status.ts
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import ora from 'ora';
|
|
3
|
-
import { getRefreshToken } from '../auth/keychain.js';
|
|
4
|
-
import { fetchFirebaseApiKey } from '../utils/config.js';
|
|
5
|
-
|
|
6
|
-
interface SubscriptionInfo {
|
|
7
|
-
status: 'free' | 'active' | 'trialing' | 'past_due' | 'canceled' | 'unknown';
|
|
8
|
-
planName?: string;
|
|
9
|
-
periodEnd?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async function getSubscriptionStatus(idToken: string, isStaging: boolean): Promise<SubscriptionInfo> {
|
|
13
|
-
try {
|
|
14
|
-
const baseUrl = isStaging
|
|
15
|
-
? 'https://us-central1-cutline-staging.cloudfunctions.net'
|
|
16
|
-
: 'https://us-central1-cutline-prod.cloudfunctions.net';
|
|
17
|
-
|
|
18
|
-
const response = await fetch(`${baseUrl}/mcpSubscriptionStatus`, {
|
|
19
|
-
method: 'GET',
|
|
20
|
-
headers: {
|
|
21
|
-
'Authorization': `Bearer ${idToken}`,
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
if (!response.ok) {
|
|
26
|
-
return { status: 'unknown' };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return await response.json();
|
|
30
|
-
} catch (error) {
|
|
31
|
-
console.error('Error fetching subscription:', error);
|
|
32
|
-
return { status: 'unknown' };
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function exchangeRefreshToken(refreshToken: string, apiKey: string): Promise<string> {
|
|
37
|
-
const response = await fetch(
|
|
38
|
-
`https://securetoken.googleapis.com/v1/token?key=${apiKey}`,
|
|
39
|
-
{
|
|
40
|
-
method: 'POST',
|
|
41
|
-
headers: { 'Content-Type': 'application/json' },
|
|
42
|
-
body: JSON.stringify({
|
|
43
|
-
grant_type: 'refresh_token',
|
|
44
|
-
refresh_token: refreshToken,
|
|
45
|
-
}),
|
|
46
|
-
}
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
if (!response.ok) {
|
|
50
|
-
const errorText = await response.text();
|
|
51
|
-
let errorMessage = 'Failed to refresh token';
|
|
52
|
-
try {
|
|
53
|
-
const errorData = JSON.parse(errorText);
|
|
54
|
-
errorMessage = errorData.error?.message || errorText;
|
|
55
|
-
} catch {
|
|
56
|
-
errorMessage = errorText;
|
|
57
|
-
}
|
|
58
|
-
throw new Error(errorMessage);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const data = await response.json();
|
|
62
|
-
return data.id_token;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export async function statusCommand(options: { staging?: boolean }) {
|
|
66
|
-
console.log(chalk.bold('\n📊 Cutline MCP Status\n'));
|
|
67
|
-
|
|
68
|
-
const spinner = ora('Checking authentication...').start();
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
// Check for stored refresh token
|
|
72
|
-
const refreshToken = await getRefreshToken();
|
|
73
|
-
|
|
74
|
-
if (!refreshToken) {
|
|
75
|
-
spinner.info(chalk.yellow('Not authenticated'));
|
|
76
|
-
console.log(chalk.gray(' Run'), chalk.cyan('cutline-mcp login'), chalk.gray('to authenticate\n'));
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Get Firebase API key
|
|
81
|
-
spinner.text = 'Fetching configuration...';
|
|
82
|
-
let firebaseApiKey: string;
|
|
83
|
-
try {
|
|
84
|
-
firebaseApiKey = await fetchFirebaseApiKey(options);
|
|
85
|
-
} catch (error) {
|
|
86
|
-
spinner.fail(chalk.red('Configuration error'));
|
|
87
|
-
console.error(chalk.red(` ${error instanceof Error ? error.message : 'Failed to get Firebase API key'}`));
|
|
88
|
-
process.exit(1);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Exchange refresh token for ID token
|
|
92
|
-
spinner.text = 'Verifying credentials...';
|
|
93
|
-
const idToken = await exchangeRefreshToken(refreshToken, firebaseApiKey);
|
|
94
|
-
|
|
95
|
-
// Decode JWT payload (base64) to get user info - no verification needed, just display
|
|
96
|
-
const payloadBase64 = idToken.split('.')[1];
|
|
97
|
-
const decoded = JSON.parse(Buffer.from(payloadBase64, 'base64').toString());
|
|
98
|
-
|
|
99
|
-
spinner.succeed(chalk.green('Authenticated'));
|
|
100
|
-
|
|
101
|
-
console.log(chalk.gray(' User:'), chalk.white(decoded.email || decoded.user_id || decoded.sub));
|
|
102
|
-
console.log(chalk.gray(' UID:'), chalk.dim(decoded.user_id || decoded.sub));
|
|
103
|
-
|
|
104
|
-
// Calculate token expiry
|
|
105
|
-
const expiresIn = Math.floor((decoded.exp * 1000 - Date.now()) / 1000 / 60);
|
|
106
|
-
console.log(chalk.gray(' Token expires in:'), chalk.white(`${expiresIn} minutes`));
|
|
107
|
-
|
|
108
|
-
// Show custom claims if present
|
|
109
|
-
if (decoded.mcp) {
|
|
110
|
-
console.log(chalk.gray(' MCP enabled:'), chalk.green('✓'));
|
|
111
|
-
}
|
|
112
|
-
if (decoded.deviceId) {
|
|
113
|
-
console.log(chalk.gray(' Device ID:'), chalk.dim(decoded.deviceId));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Check subscription status via Cloud Function
|
|
117
|
-
spinner.start('Checking subscription...');
|
|
118
|
-
const subscription = await getSubscriptionStatus(idToken, !!options.staging);
|
|
119
|
-
spinner.stop();
|
|
120
|
-
|
|
121
|
-
if (subscription.status === 'active' || subscription.status === 'trialing') {
|
|
122
|
-
const statusLabel = subscription.status === 'trialing' ? ' (trial)' : '';
|
|
123
|
-
console.log(chalk.gray(' Plan:'), chalk.green(`✓ ${subscription.planName || 'Premium'}${statusLabel}`));
|
|
124
|
-
if (subscription.periodEnd) {
|
|
125
|
-
const periodEndDate = new Date(subscription.periodEnd);
|
|
126
|
-
const daysLeft = Math.ceil((periodEndDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
|
|
127
|
-
console.log(chalk.gray(' Renews:'), chalk.white(`${periodEndDate.toLocaleDateString()} (${daysLeft} days)`));
|
|
128
|
-
}
|
|
129
|
-
} else if (subscription.status === 'past_due') {
|
|
130
|
-
console.log(chalk.gray(' Plan:'), chalk.yellow('⚠ Premium (payment past due)'));
|
|
131
|
-
} else if (subscription.status === 'canceled') {
|
|
132
|
-
console.log(chalk.gray(' Plan:'), chalk.yellow('Premium (canceled)'));
|
|
133
|
-
if (subscription.periodEnd) {
|
|
134
|
-
console.log(chalk.gray(' Access until:'), chalk.white(new Date(subscription.periodEnd).toLocaleDateString()));
|
|
135
|
-
}
|
|
136
|
-
} else {
|
|
137
|
-
console.log(chalk.gray(' Plan:'), chalk.white('Free'));
|
|
138
|
-
console.log(chalk.dim(' Upgrade at'), chalk.cyan('https://thecutline.ai/pricing'));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
console.log();
|
|
142
|
-
|
|
143
|
-
} catch (error) {
|
|
144
|
-
spinner.fail(chalk.red('Status check failed'));
|
|
145
|
-
|
|
146
|
-
if (error instanceof Error) {
|
|
147
|
-
console.error(chalk.red(` ${error.message}`));
|
|
148
|
-
console.log(chalk.gray(' Try running'), chalk.cyan('cutline-mcp login'), chalk.gray('again\n'));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
process.exit(1);
|
|
152
|
-
}
|
|
153
|
-
}
|
package/src/commands/upgrade.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import open from 'open';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import ora from 'ora';
|
|
4
|
-
import { startCallbackServer } from '../auth/callback.js';
|
|
5
|
-
import { storeRefreshToken } from '../auth/keychain.js';
|
|
6
|
-
import { saveConfig } from '../utils/config-store.js';
|
|
7
|
-
import { getConfig, fetchFirebaseApiKey } from '../utils/config.js';
|
|
8
|
-
|
|
9
|
-
async function exchangeCustomToken(customToken: string, apiKey: string): Promise<{ refreshToken: string; email?: string }> {
|
|
10
|
-
const response = await fetch(
|
|
11
|
-
`https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apiKey}`,
|
|
12
|
-
{
|
|
13
|
-
method: 'POST',
|
|
14
|
-
headers: { 'Content-Type': 'application/json' },
|
|
15
|
-
body: JSON.stringify({
|
|
16
|
-
token: customToken,
|
|
17
|
-
returnSecureToken: true,
|
|
18
|
-
}),
|
|
19
|
-
}
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
if (!response.ok) {
|
|
23
|
-
const error = await response.text();
|
|
24
|
-
throw new Error(`Failed to exchange custom token: ${error}`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const data = await response.json();
|
|
28
|
-
return {
|
|
29
|
-
refreshToken: data.refreshToken,
|
|
30
|
-
email: data.email,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function upgradeCommand(options: { staging?: boolean }) {
|
|
35
|
-
const config = getConfig(options);
|
|
36
|
-
|
|
37
|
-
console.log(chalk.bold('\n⬆️ Cutline MCP - Upgrade to Premium\n'));
|
|
38
|
-
|
|
39
|
-
if (options.staging) {
|
|
40
|
-
console.log(chalk.yellow(' ⚠️ Using STAGING environment\n'));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Fetch Firebase API key
|
|
44
|
-
let firebaseApiKey: string;
|
|
45
|
-
try {
|
|
46
|
-
firebaseApiKey = await fetchFirebaseApiKey(options);
|
|
47
|
-
} catch (error) {
|
|
48
|
-
console.error(chalk.red(`Error: ${error instanceof Error ? error.message : 'Failed to get Firebase API key'}`));
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Determine upgrade URL based on environment
|
|
53
|
-
const baseUrl = options.staging
|
|
54
|
-
? 'https://cutline-staging.web.app'
|
|
55
|
-
: 'https://thecutline.ai';
|
|
56
|
-
|
|
57
|
-
console.log(chalk.gray(' Opening upgrade page in your browser...\n'));
|
|
58
|
-
console.log(chalk.dim(' After upgrading, your MCP session will be refreshed automatically.\n'));
|
|
59
|
-
|
|
60
|
-
const spinner = ora('Waiting for upgrade and re-authentication...').start();
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
// Start callback server for re-auth after upgrade
|
|
64
|
-
const serverPromise = startCallbackServer();
|
|
65
|
-
|
|
66
|
-
// Open upgrade page with callback for re-auth
|
|
67
|
-
// The upgrade page will redirect to mcp-auth after successful upgrade
|
|
68
|
-
const upgradeUrl = `${baseUrl}/upgrade?mcp_callback=${encodeURIComponent(config.CALLBACK_URL)}`;
|
|
69
|
-
await open(upgradeUrl);
|
|
70
|
-
|
|
71
|
-
spinner.text = 'Browser opened - complete your upgrade, then re-authenticate';
|
|
72
|
-
|
|
73
|
-
// Wait for callback with new token (after upgrade + re-auth)
|
|
74
|
-
const result = await serverPromise;
|
|
75
|
-
|
|
76
|
-
// Exchange custom token for refresh token
|
|
77
|
-
spinner.text = 'Refreshing your session...';
|
|
78
|
-
const { refreshToken, email } = await exchangeCustomToken(result.token, firebaseApiKey);
|
|
79
|
-
|
|
80
|
-
// Store refresh token
|
|
81
|
-
try {
|
|
82
|
-
await storeRefreshToken(refreshToken);
|
|
83
|
-
} catch (error) {
|
|
84
|
-
console.warn(chalk.yellow(' ⚠️ Could not save to Keychain (skipping)'));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Save to file config (API key is fetched at runtime, not stored)
|
|
88
|
-
try {
|
|
89
|
-
saveConfig({
|
|
90
|
-
refreshToken,
|
|
91
|
-
environment: options.staging ? 'staging' : 'production',
|
|
92
|
-
});
|
|
93
|
-
} catch (error) {
|
|
94
|
-
console.error(chalk.red(' ✗ Failed to save config file:'), error);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
spinner.succeed(chalk.green('Upgrade complete! Session refreshed.'));
|
|
98
|
-
|
|
99
|
-
const envLabel = options.staging ? chalk.yellow('STAGING') : chalk.green('PRODUCTION');
|
|
100
|
-
console.log(chalk.gray(` Environment: ${envLabel}`));
|
|
101
|
-
|
|
102
|
-
if (email || result.email) {
|
|
103
|
-
console.log(chalk.gray(` Account: ${email || result.email}`));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
console.log(chalk.green('\n ✨ Premium features are now available!\n'));
|
|
107
|
-
console.log(chalk.dim(' Try:'), chalk.cyan('exploration_graduate'), chalk.dim('to run a full Deep Dive\n'));
|
|
108
|
-
|
|
109
|
-
} catch (error) {
|
|
110
|
-
spinner.fail(chalk.red('Upgrade flow failed'));
|
|
111
|
-
|
|
112
|
-
if (error instanceof Error) {
|
|
113
|
-
console.error(chalk.red(` ${error.message}`));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
console.log(chalk.gray('\n You can also upgrade at:'), chalk.cyan(`${baseUrl}/upgrade`));
|
|
117
|
-
console.log(chalk.gray(' Then run:'), chalk.cyan('cutline-mcp login'), chalk.gray('to refresh your session\n'));
|
|
118
|
-
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import { loginCommand } from './commands/login.js';
|
|
4
|
-
import { logoutCommand } from './commands/logout.js';
|
|
5
|
-
import { statusCommand } from './commands/status.js';
|
|
6
|
-
import { upgradeCommand } from './commands/upgrade.js';
|
|
7
|
-
|
|
8
|
-
const program = new Command();
|
|
9
|
-
|
|
10
|
-
program
|
|
11
|
-
.name('cutline-mcp')
|
|
12
|
-
.description('CLI tool for authenticating with Cutline MCP servers')
|
|
13
|
-
.version('0.1.0');
|
|
14
|
-
|
|
15
|
-
program
|
|
16
|
-
.command('login')
|
|
17
|
-
.description('Authenticate with Cutline and store credentials')
|
|
18
|
-
.option('--staging', 'Use staging environment')
|
|
19
|
-
.option('--signup', 'Open sign-up page instead of sign-in')
|
|
20
|
-
.option('--email <address>', 'Request sign-in with specific email address')
|
|
21
|
-
.action(loginCommand);
|
|
22
|
-
|
|
23
|
-
program
|
|
24
|
-
.command('logout')
|
|
25
|
-
.description('Remove stored credentials')
|
|
26
|
-
.action(logoutCommand);
|
|
27
|
-
|
|
28
|
-
program
|
|
29
|
-
.command('status')
|
|
30
|
-
.description('Show current authentication status')
|
|
31
|
-
.option('--staging', 'Use staging environment')
|
|
32
|
-
.action(statusCommand);
|
|
33
|
-
|
|
34
|
-
program
|
|
35
|
-
.command('upgrade')
|
|
36
|
-
.description('Upgrade to Premium and refresh your session')
|
|
37
|
-
.option('--staging', 'Use staging environment')
|
|
38
|
-
.action(upgradeCommand);
|
|
39
|
-
|
|
40
|
-
program.parse();
|