@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.
Files changed (49) hide show
  1. package/Dockerfile +11 -0
  2. package/README.md +177 -107
  3. package/dist/auth/callback.js +30 -32
  4. package/dist/auth/keychain.js +7 -15
  5. package/dist/commands/init.d.ts +4 -0
  6. package/dist/commands/init.js +246 -0
  7. package/dist/commands/login.js +39 -45
  8. package/dist/commands/logout.js +13 -19
  9. package/dist/commands/serve.d.ts +1 -0
  10. package/dist/commands/serve.js +38 -0
  11. package/dist/commands/setup.d.ts +5 -0
  12. package/dist/commands/setup.js +255 -0
  13. package/dist/commands/status.js +29 -35
  14. package/dist/commands/upgrade.js +44 -38
  15. package/dist/index.js +38 -14
  16. package/dist/servers/chunk-7FHM2GD3.js +5836 -0
  17. package/dist/servers/chunk-IVWF7VYZ.js +10086 -0
  18. package/dist/servers/chunk-JBJYSV4P.js +139 -0
  19. package/dist/servers/chunk-KMUSQOTJ.js +47 -0
  20. package/dist/servers/chunk-PD2HN2R5.js +908 -0
  21. package/dist/servers/chunk-PU7TL6S3.js +91 -0
  22. package/dist/servers/chunk-TGSEURMN.js +46 -0
  23. package/dist/servers/chunk-UBBAYTW3.js +946 -0
  24. package/dist/servers/cutline-server.js +11512 -0
  25. package/dist/servers/exploration-server.js +1030 -0
  26. package/dist/servers/graph-metrics-DCNR7JZN.js +12 -0
  27. package/dist/servers/integrations-server.js +121 -0
  28. package/dist/servers/output-server.js +120 -0
  29. package/dist/servers/pipeline-O5GJPNR4.js +20 -0
  30. package/dist/servers/premortem-handoff-XT4K3YDJ.js +10 -0
  31. package/dist/servers/premortem-server.js +958 -0
  32. package/dist/servers/score-history-HO5KRVGC.js +6 -0
  33. package/dist/servers/tools-server.js +291 -0
  34. package/dist/utils/config-store.js +13 -21
  35. package/dist/utils/config.js +2 -6
  36. package/mcpb/manifest.json +77 -0
  37. package/package.json +55 -9
  38. package/server.json +42 -0
  39. package/smithery.yaml +10 -0
  40. package/src/auth/callback.ts +0 -102
  41. package/src/auth/keychain.ts +0 -16
  42. package/src/commands/login.ts +0 -202
  43. package/src/commands/logout.ts +0 -30
  44. package/src/commands/status.ts +0 -153
  45. package/src/commands/upgrade.ts +0 -121
  46. package/src/index.ts +0 -40
  47. package/src/utils/config-store.ts +0 -46
  48. package/src/utils/config.ts +0 -65
  49. package/tsconfig.json +0 -22
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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();