@kylewadegrove/cutline-mcp-cli 0.3.2 → 0.4.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/dist/commands/login.js +74 -1
- package/dist/commands/status.js +54 -2
- package/package.json +1 -1
- package/src/commands/login.ts +93 -1
- package/src/commands/status.ts +63 -2
package/dist/commands/login.js
CHANGED
|
@@ -7,10 +7,58 @@ exports.loginCommand = loginCommand;
|
|
|
7
7
|
const open_1 = __importDefault(require("open"));
|
|
8
8
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
9
|
const ora_1 = __importDefault(require("ora"));
|
|
10
|
+
const firebase_admin_1 = __importDefault(require("firebase-admin"));
|
|
10
11
|
const callback_js_1 = require("../auth/callback.js");
|
|
11
12
|
const keychain_js_1 = require("../auth/keychain.js");
|
|
12
13
|
const config_store_js_1 = require("../utils/config-store.js");
|
|
13
14
|
const config_js_1 = require("../utils/config.js");
|
|
15
|
+
async function getSubscriptionStatus(uid, projectId) {
|
|
16
|
+
try {
|
|
17
|
+
// Initialize Firebase Admin if not already
|
|
18
|
+
if (firebase_admin_1.default.apps.length === 0) {
|
|
19
|
+
firebase_admin_1.default.initializeApp({ projectId });
|
|
20
|
+
}
|
|
21
|
+
const db = firebase_admin_1.default.firestore();
|
|
22
|
+
const userDoc = await db.collection('users').doc(uid).get();
|
|
23
|
+
if (!userDoc.exists) {
|
|
24
|
+
return { status: 'free' };
|
|
25
|
+
}
|
|
26
|
+
const userData = userDoc.data();
|
|
27
|
+
const subscription = userData?.subscription;
|
|
28
|
+
if (!subscription) {
|
|
29
|
+
return { status: 'free' };
|
|
30
|
+
}
|
|
31
|
+
const status = subscription.status || 'unknown';
|
|
32
|
+
// Get plan name from price ID or default
|
|
33
|
+
let planName = 'Premium';
|
|
34
|
+
if (subscription.priceId?.includes('yearly') || subscription.interval === 'year') {
|
|
35
|
+
planName = 'Premium (Yearly)';
|
|
36
|
+
}
|
|
37
|
+
else if (subscription.priceId?.includes('monthly') || subscription.interval === 'month') {
|
|
38
|
+
planName = 'Premium (Monthly)';
|
|
39
|
+
}
|
|
40
|
+
return { status, planName };
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
// Silently fail - subscription check is optional during login
|
|
44
|
+
return { status: 'unknown' };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function exchangeRefreshForIdToken(refreshToken, apiKey) {
|
|
48
|
+
const response = await fetch(`https://securetoken.googleapis.com/v1/token?key=${apiKey}`, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: { 'Content-Type': 'application/json' },
|
|
51
|
+
body: JSON.stringify({
|
|
52
|
+
grant_type: 'refresh_token',
|
|
53
|
+
refresh_token: refreshToken,
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
throw new Error('Failed to get ID token');
|
|
58
|
+
}
|
|
59
|
+
const data = await response.json();
|
|
60
|
+
return data.id_token;
|
|
61
|
+
}
|
|
14
62
|
async function exchangeCustomToken(customToken, apiKey) {
|
|
15
63
|
const response = await fetch(`https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apiKey}`, {
|
|
16
64
|
method: 'POST',
|
|
@@ -104,7 +152,32 @@ async function loginCommand(options) {
|
|
|
104
152
|
if (email || result.email) {
|
|
105
153
|
console.log(chalk_1.default.gray(` Logged in as: ${email || result.email}`));
|
|
106
154
|
}
|
|
107
|
-
|
|
155
|
+
// Check subscription status
|
|
156
|
+
try {
|
|
157
|
+
spinner.start('Checking subscription...');
|
|
158
|
+
const projectId = options.staging ? 'cutline-staging' : 'cutline-prod';
|
|
159
|
+
const idToken = await exchangeRefreshForIdToken(refreshToken, firebaseApiKey);
|
|
160
|
+
// Initialize Firebase Admin if needed
|
|
161
|
+
if (firebase_admin_1.default.apps.length === 0) {
|
|
162
|
+
firebase_admin_1.default.initializeApp({ projectId });
|
|
163
|
+
}
|
|
164
|
+
const decoded = await firebase_admin_1.default.auth().verifyIdToken(idToken);
|
|
165
|
+
const subscription = await getSubscriptionStatus(decoded.uid, projectId);
|
|
166
|
+
spinner.stop();
|
|
167
|
+
if (subscription.status === 'active' || subscription.status === 'trialing') {
|
|
168
|
+
const statusLabel = subscription.status === 'trialing' ? ' (trial)' : '';
|
|
169
|
+
console.log(chalk_1.default.gray(' Plan:'), chalk_1.default.green(`✓ ${subscription.planName || 'Premium'}${statusLabel}`));
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
console.log(chalk_1.default.gray(' Plan:'), chalk_1.default.white('Free'));
|
|
173
|
+
console.log(chalk_1.default.dim(' Upgrade at'), chalk_1.default.cyan('https://thecutline.ai/pricing'));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
spinner.stop();
|
|
178
|
+
// Silently skip subscription check on error
|
|
179
|
+
}
|
|
180
|
+
console.log(chalk_1.default.gray('\n MCP servers can now access your account\n'));
|
|
108
181
|
console.log(chalk_1.default.dim(' Run'), chalk_1.default.cyan('cutline-mcp status'), chalk_1.default.dim('to verify\n'));
|
|
109
182
|
}
|
|
110
183
|
catch (error) {
|
package/dist/commands/status.js
CHANGED
|
@@ -9,6 +9,37 @@ const ora_1 = __importDefault(require("ora"));
|
|
|
9
9
|
const firebase_admin_1 = __importDefault(require("firebase-admin"));
|
|
10
10
|
const keychain_js_1 = require("../auth/keychain.js");
|
|
11
11
|
const config_js_1 = require("../utils/config.js");
|
|
12
|
+
async function getSubscriptionStatus(uid) {
|
|
13
|
+
try {
|
|
14
|
+
const db = firebase_admin_1.default.firestore();
|
|
15
|
+
const userDoc = await db.collection('users').doc(uid).get();
|
|
16
|
+
if (!userDoc.exists) {
|
|
17
|
+
return { status: 'free' };
|
|
18
|
+
}
|
|
19
|
+
const userData = userDoc.data();
|
|
20
|
+
const subscription = userData?.subscription;
|
|
21
|
+
if (!subscription) {
|
|
22
|
+
return { status: 'free' };
|
|
23
|
+
}
|
|
24
|
+
const status = subscription.status || 'unknown';
|
|
25
|
+
const periodEnd = subscription.periodEndsAt
|
|
26
|
+
? new Date(subscription.periodEndsAt)
|
|
27
|
+
: undefined;
|
|
28
|
+
// Get plan name from price ID or default
|
|
29
|
+
let planName = 'Premium';
|
|
30
|
+
if (subscription.priceId?.includes('yearly') || subscription.interval === 'year') {
|
|
31
|
+
planName = 'Premium (Yearly)';
|
|
32
|
+
}
|
|
33
|
+
else if (subscription.priceId?.includes('monthly') || subscription.interval === 'month') {
|
|
34
|
+
planName = 'Premium (Monthly)';
|
|
35
|
+
}
|
|
36
|
+
return { status, planName, periodEnd };
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error('Error fetching subscription:', error);
|
|
40
|
+
return { status: 'unknown' };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
12
43
|
async function exchangeRefreshToken(refreshToken, apiKey) {
|
|
13
44
|
const response = await fetch(`https://securetoken.googleapis.com/v1/token?key=${apiKey}`, {
|
|
14
45
|
method: 'POST',
|
|
@@ -78,8 +109,29 @@ async function statusCommand(options) {
|
|
|
78
109
|
if (decoded.deviceId) {
|
|
79
110
|
console.log(chalk_1.default.gray(' Device ID:'), chalk_1.default.dim(decoded.deviceId));
|
|
80
111
|
}
|
|
81
|
-
//
|
|
82
|
-
|
|
112
|
+
// Check subscription status from Firestore
|
|
113
|
+
const subscription = await getSubscriptionStatus(decoded.uid);
|
|
114
|
+
if (subscription.status === 'active' || subscription.status === 'trialing') {
|
|
115
|
+
const statusLabel = subscription.status === 'trialing' ? ' (trial)' : '';
|
|
116
|
+
console.log(chalk_1.default.gray(' Plan:'), chalk_1.default.green(`✓ ${subscription.planName || 'Premium'}${statusLabel}`));
|
|
117
|
+
if (subscription.periodEnd) {
|
|
118
|
+
const daysLeft = Math.ceil((subscription.periodEnd.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
|
|
119
|
+
console.log(chalk_1.default.gray(' Renews:'), chalk_1.default.white(`${subscription.periodEnd.toLocaleDateString()} (${daysLeft} days)`));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else if (subscription.status === 'past_due') {
|
|
123
|
+
console.log(chalk_1.default.gray(' Plan:'), chalk_1.default.yellow('⚠ Premium (payment past due)'));
|
|
124
|
+
}
|
|
125
|
+
else if (subscription.status === 'canceled') {
|
|
126
|
+
console.log(chalk_1.default.gray(' Plan:'), chalk_1.default.yellow('Premium (canceled)'));
|
|
127
|
+
if (subscription.periodEnd) {
|
|
128
|
+
console.log(chalk_1.default.gray(' Access until:'), chalk_1.default.white(subscription.periodEnd.toLocaleDateString()));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
console.log(chalk_1.default.gray(' Plan:'), chalk_1.default.white('Free'));
|
|
133
|
+
console.log(chalk_1.default.dim(' Upgrade at'), chalk_1.default.cyan('https://thecutline.ai/pricing'));
|
|
134
|
+
}
|
|
83
135
|
console.log();
|
|
84
136
|
}
|
|
85
137
|
catch (error) {
|
package/package.json
CHANGED
package/src/commands/login.ts
CHANGED
|
@@ -1,11 +1,76 @@
|
|
|
1
1
|
import open from 'open';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import ora from 'ora';
|
|
4
|
+
import admin from 'firebase-admin';
|
|
4
5
|
import { startCallbackServer } from '../auth/callback.js';
|
|
5
6
|
import { storeRefreshToken } from '../auth/keychain.js';
|
|
6
7
|
import { saveConfig } from '../utils/config-store.js';
|
|
7
8
|
import { getConfig, fetchFirebaseApiKey } from '../utils/config.js';
|
|
8
9
|
|
|
10
|
+
interface SubscriptionInfo {
|
|
11
|
+
status: 'free' | 'active' | 'trialing' | 'past_due' | 'canceled' | 'unknown';
|
|
12
|
+
planName?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function getSubscriptionStatus(uid: string, projectId: string): Promise<SubscriptionInfo> {
|
|
16
|
+
try {
|
|
17
|
+
// Initialize Firebase Admin if not already
|
|
18
|
+
if (admin.apps.length === 0) {
|
|
19
|
+
admin.initializeApp({ projectId });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const db = admin.firestore();
|
|
23
|
+
const userDoc = await db.collection('users').doc(uid).get();
|
|
24
|
+
|
|
25
|
+
if (!userDoc.exists) {
|
|
26
|
+
return { status: 'free' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const userData = userDoc.data();
|
|
30
|
+
const subscription = userData?.subscription;
|
|
31
|
+
|
|
32
|
+
if (!subscription) {
|
|
33
|
+
return { status: 'free' };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const status = subscription.status || 'unknown';
|
|
37
|
+
|
|
38
|
+
// Get plan name from price ID or default
|
|
39
|
+
let planName = 'Premium';
|
|
40
|
+
if (subscription.priceId?.includes('yearly') || subscription.interval === 'year') {
|
|
41
|
+
planName = 'Premium (Yearly)';
|
|
42
|
+
} else if (subscription.priceId?.includes('monthly') || subscription.interval === 'month') {
|
|
43
|
+
planName = 'Premium (Monthly)';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { status, planName };
|
|
47
|
+
} catch (error) {
|
|
48
|
+
// Silently fail - subscription check is optional during login
|
|
49
|
+
return { status: 'unknown' };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function exchangeRefreshForIdToken(refreshToken: string, apiKey: string): Promise<string> {
|
|
54
|
+
const response = await fetch(
|
|
55
|
+
`https://securetoken.googleapis.com/v1/token?key=${apiKey}`,
|
|
56
|
+
{
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: { 'Content-Type': 'application/json' },
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
grant_type: 'refresh_token',
|
|
61
|
+
refresh_token: refreshToken,
|
|
62
|
+
}),
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
throw new Error('Failed to get ID token');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const data = await response.json();
|
|
71
|
+
return data.id_token;
|
|
72
|
+
}
|
|
73
|
+
|
|
9
74
|
async function exchangeCustomToken(customToken: string, apiKey: string): Promise<{ refreshToken: string; email?: string }> {
|
|
10
75
|
const response = await fetch(
|
|
11
76
|
`https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apiKey}`,
|
|
@@ -117,7 +182,34 @@ export async function loginCommand(options: { staging?: boolean; signup?: boolea
|
|
|
117
182
|
console.log(chalk.gray(` Logged in as: ${email || result.email}`));
|
|
118
183
|
}
|
|
119
184
|
|
|
120
|
-
|
|
185
|
+
// Check subscription status
|
|
186
|
+
try {
|
|
187
|
+
spinner.start('Checking subscription...');
|
|
188
|
+
const projectId = options.staging ? 'cutline-staging' : 'cutline-prod';
|
|
189
|
+
const idToken = await exchangeRefreshForIdToken(refreshToken, firebaseApiKey);
|
|
190
|
+
|
|
191
|
+
// Initialize Firebase Admin if needed
|
|
192
|
+
if (admin.apps.length === 0) {
|
|
193
|
+
admin.initializeApp({ projectId });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const decoded = await admin.auth().verifyIdToken(idToken);
|
|
197
|
+
const subscription = await getSubscriptionStatus(decoded.uid, projectId);
|
|
198
|
+
spinner.stop();
|
|
199
|
+
|
|
200
|
+
if (subscription.status === 'active' || subscription.status === 'trialing') {
|
|
201
|
+
const statusLabel = subscription.status === 'trialing' ? ' (trial)' : '';
|
|
202
|
+
console.log(chalk.gray(' Plan:'), chalk.green(`✓ ${subscription.planName || 'Premium'}${statusLabel}`));
|
|
203
|
+
} else {
|
|
204
|
+
console.log(chalk.gray(' Plan:'), chalk.white('Free'));
|
|
205
|
+
console.log(chalk.dim(' Upgrade at'), chalk.cyan('https://thecutline.ai/pricing'));
|
|
206
|
+
}
|
|
207
|
+
} catch {
|
|
208
|
+
spinner.stop();
|
|
209
|
+
// Silently skip subscription check on error
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log(chalk.gray('\n MCP servers can now access your account\n'));
|
|
121
213
|
console.log(chalk.dim(' Run'), chalk.cyan('cutline-mcp status'), chalk.dim('to verify\n'));
|
|
122
214
|
|
|
123
215
|
} catch (error) {
|
package/src/commands/status.ts
CHANGED
|
@@ -4,6 +4,48 @@ import admin from 'firebase-admin';
|
|
|
4
4
|
import { getRefreshToken } from '../auth/keychain.js';
|
|
5
5
|
import { fetchFirebaseApiKey } from '../utils/config.js';
|
|
6
6
|
|
|
7
|
+
interface SubscriptionInfo {
|
|
8
|
+
status: 'free' | 'active' | 'trialing' | 'past_due' | 'canceled' | 'unknown';
|
|
9
|
+
planName?: string;
|
|
10
|
+
periodEnd?: Date;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function getSubscriptionStatus(uid: string): Promise<SubscriptionInfo> {
|
|
14
|
+
try {
|
|
15
|
+
const db = admin.firestore();
|
|
16
|
+
const userDoc = await db.collection('users').doc(uid).get();
|
|
17
|
+
|
|
18
|
+
if (!userDoc.exists) {
|
|
19
|
+
return { status: 'free' };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const userData = userDoc.data();
|
|
23
|
+
const subscription = userData?.subscription;
|
|
24
|
+
|
|
25
|
+
if (!subscription) {
|
|
26
|
+
return { status: 'free' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const status = subscription.status || 'unknown';
|
|
30
|
+
const periodEnd = subscription.periodEndsAt
|
|
31
|
+
? new Date(subscription.periodEndsAt)
|
|
32
|
+
: undefined;
|
|
33
|
+
|
|
34
|
+
// Get plan name from price ID or default
|
|
35
|
+
let planName = 'Premium';
|
|
36
|
+
if (subscription.priceId?.includes('yearly') || subscription.interval === 'year') {
|
|
37
|
+
planName = 'Premium (Yearly)';
|
|
38
|
+
} else if (subscription.priceId?.includes('monthly') || subscription.interval === 'month') {
|
|
39
|
+
planName = 'Premium (Monthly)';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { status, planName, periodEnd };
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('Error fetching subscription:', error);
|
|
45
|
+
return { status: 'unknown' };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
7
49
|
async function exchangeRefreshToken(refreshToken: string, apiKey: string): Promise<string> {
|
|
8
50
|
const response = await fetch(
|
|
9
51
|
`https://securetoken.googleapis.com/v1/token?key=${apiKey}`,
|
|
@@ -89,8 +131,27 @@ export async function statusCommand(options: { staging?: boolean }) {
|
|
|
89
131
|
console.log(chalk.gray(' Device ID:'), chalk.dim(decoded.deviceId));
|
|
90
132
|
}
|
|
91
133
|
|
|
92
|
-
//
|
|
93
|
-
|
|
134
|
+
// Check subscription status from Firestore
|
|
135
|
+
const subscription = await getSubscriptionStatus(decoded.uid);
|
|
136
|
+
|
|
137
|
+
if (subscription.status === 'active' || subscription.status === 'trialing') {
|
|
138
|
+
const statusLabel = subscription.status === 'trialing' ? ' (trial)' : '';
|
|
139
|
+
console.log(chalk.gray(' Plan:'), chalk.green(`✓ ${subscription.planName || 'Premium'}${statusLabel}`));
|
|
140
|
+
if (subscription.periodEnd) {
|
|
141
|
+
const daysLeft = Math.ceil((subscription.periodEnd.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
|
|
142
|
+
console.log(chalk.gray(' Renews:'), chalk.white(`${subscription.periodEnd.toLocaleDateString()} (${daysLeft} days)`));
|
|
143
|
+
}
|
|
144
|
+
} else if (subscription.status === 'past_due') {
|
|
145
|
+
console.log(chalk.gray(' Plan:'), chalk.yellow('⚠ Premium (payment past due)'));
|
|
146
|
+
} else if (subscription.status === 'canceled') {
|
|
147
|
+
console.log(chalk.gray(' Plan:'), chalk.yellow('Premium (canceled)'));
|
|
148
|
+
if (subscription.periodEnd) {
|
|
149
|
+
console.log(chalk.gray(' Access until:'), chalk.white(subscription.periodEnd.toLocaleDateString()));
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
console.log(chalk.gray(' Plan:'), chalk.white('Free'));
|
|
153
|
+
console.log(chalk.dim(' Upgrade at'), chalk.cyan('https://thecutline.ai/pricing'));
|
|
154
|
+
}
|
|
94
155
|
|
|
95
156
|
console.log();
|
|
96
157
|
|