@kylewadegrove/cutline-mcp-cli 0.2.0 → 0.3.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.d.ts +1 -0
- package/dist/commands/login.js +21 -9
- package/dist/commands/status.js +9 -5
- package/dist/commands/upgrade.js +9 -5
- package/dist/index.js +1 -0
- package/dist/utils/config-store.d.ts +0 -1
- package/dist/utils/config.d.ts +14 -0
- package/dist/utils/config.js +37 -6
- package/package.json +1 -1
- package/src/commands/login.ts +24 -11
- package/src/commands/status.ts +9 -7
- package/src/commands/upgrade.ts +9 -6
- package/src/index.ts +1 -0
- package/src/utils/config-store.ts +1 -1
- package/src/utils/config.ts +46 -6
package/dist/commands/login.d.ts
CHANGED
package/dist/commands/login.js
CHANGED
|
@@ -41,15 +41,25 @@ async function loginCommand(options) {
|
|
|
41
41
|
if (options.staging) {
|
|
42
42
|
console.log(chalk_1.default.yellow(' ⚠️ Using STAGING environment\n'));
|
|
43
43
|
}
|
|
44
|
-
if (
|
|
45
|
-
console.
|
|
46
|
-
console.error(chalk_1.default.gray('Please set it before running this command:'));
|
|
47
|
-
console.error(chalk_1.default.cyan(` export FIREBASE_API_KEY=AIzaSy...`));
|
|
48
|
-
console.error(chalk_1.default.gray(' (or use NEXT_PUBLIC_FIREBASE_API_KEY for consistency with web app config)'));
|
|
49
|
-
process.exit(1);
|
|
44
|
+
if (options.email) {
|
|
45
|
+
console.log(chalk_1.default.gray(` Requesting sign-in as: ${options.email}\n`));
|
|
50
46
|
}
|
|
51
47
|
const spinner = (0, ora_1.default)('Starting authentication flow...').start();
|
|
52
48
|
try {
|
|
49
|
+
// Fetch Firebase API key from web app endpoint
|
|
50
|
+
spinner.text = 'Fetching configuration...';
|
|
51
|
+
let firebaseApiKey;
|
|
52
|
+
try {
|
|
53
|
+
firebaseApiKey = await (0, config_js_1.fetchFirebaseApiKey)(options);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
spinner.fail(chalk_1.default.red('Failed to fetch Firebase configuration'));
|
|
57
|
+
if (error instanceof Error) {
|
|
58
|
+
console.error(chalk_1.default.red(` ${error.message}`));
|
|
59
|
+
}
|
|
60
|
+
console.error(chalk_1.default.gray('\n You can also set the FIREBASE_API_KEY environment variable manually.\n'));
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
53
63
|
// Start callback server
|
|
54
64
|
spinner.text = 'Waiting for authentication...';
|
|
55
65
|
const serverPromise = (0, callback_js_1.startCallbackServer)();
|
|
@@ -58,6 +68,9 @@ async function loginCommand(options) {
|
|
|
58
68
|
if (options.signup) {
|
|
59
69
|
authUrl += '&mode=signup';
|
|
60
70
|
}
|
|
71
|
+
if (options.email) {
|
|
72
|
+
authUrl += `&email=${encodeURIComponent(options.email)}`;
|
|
73
|
+
}
|
|
61
74
|
await (0, open_1.default)(authUrl);
|
|
62
75
|
spinner.text = options.signup
|
|
63
76
|
? 'Browser opened - please create your account'
|
|
@@ -66,7 +79,7 @@ async function loginCommand(options) {
|
|
|
66
79
|
const result = await serverPromise;
|
|
67
80
|
// Exchange custom token for refresh token
|
|
68
81
|
spinner.text = 'Exchanging token...';
|
|
69
|
-
const { refreshToken, email } = await exchangeCustomToken(result.token,
|
|
82
|
+
const { refreshToken, email } = await exchangeCustomToken(result.token, firebaseApiKey);
|
|
70
83
|
// Store refresh token
|
|
71
84
|
try {
|
|
72
85
|
await (0, keychain_js_1.storeRefreshToken)(refreshToken);
|
|
@@ -74,12 +87,11 @@ async function loginCommand(options) {
|
|
|
74
87
|
catch (error) {
|
|
75
88
|
console.warn(chalk_1.default.yellow(' ⚠️ Could not save to Keychain (skipping)'));
|
|
76
89
|
}
|
|
77
|
-
//
|
|
90
|
+
// Save to file config (cross-platform) - don't store API key anymore
|
|
78
91
|
try {
|
|
79
92
|
(0, config_store_js_1.saveConfig)({
|
|
80
93
|
refreshToken,
|
|
81
94
|
environment: options.staging ? 'staging' : 'production',
|
|
82
|
-
firebaseApiKey: config.FIREBASE_API_KEY
|
|
83
95
|
});
|
|
84
96
|
}
|
|
85
97
|
catch (error) {
|
package/dist/commands/status.js
CHANGED
|
@@ -44,16 +44,20 @@ async function statusCommand(options) {
|
|
|
44
44
|
console.log(chalk_1.default.gray(' Run'), chalk_1.default.cyan('cutline-mcp login'), chalk_1.default.gray('to authenticate\n'));
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
|
-
// Get
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
// Get Firebase API key
|
|
48
|
+
spinner.text = 'Fetching configuration...';
|
|
49
|
+
let firebaseApiKey;
|
|
50
|
+
try {
|
|
51
|
+
firebaseApiKey = await (0, config_js_1.fetchFirebaseApiKey)(options);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
50
54
|
spinner.fail(chalk_1.default.red('Configuration error'));
|
|
51
|
-
console.error(chalk_1.default.red(`
|
|
55
|
+
console.error(chalk_1.default.red(` ${error instanceof Error ? error.message : 'Failed to get Firebase API key'}`));
|
|
52
56
|
process.exit(1);
|
|
53
57
|
}
|
|
54
58
|
// Exchange refresh token for ID token
|
|
55
59
|
spinner.text = 'Verifying credentials...';
|
|
56
|
-
const idToken = await exchangeRefreshToken(refreshToken,
|
|
60
|
+
const idToken = await exchangeRefreshToken(refreshToken, firebaseApiKey);
|
|
57
61
|
// Initialize Firebase Admin with correct project ID
|
|
58
62
|
if (firebase_admin_1.default.apps.length === 0) {
|
|
59
63
|
const projectId = options.staging ? 'cutline-staging' : 'cutline-prod';
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -36,8 +36,13 @@ async function upgradeCommand(options) {
|
|
|
36
36
|
if (options.staging) {
|
|
37
37
|
console.log(chalk_1.default.yellow(' ⚠️ Using STAGING environment\n'));
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
// Fetch Firebase API key
|
|
40
|
+
let firebaseApiKey;
|
|
41
|
+
try {
|
|
42
|
+
firebaseApiKey = await (0, config_js_1.fetchFirebaseApiKey)(options);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : 'Failed to get Firebase API key'}`));
|
|
41
46
|
process.exit(1);
|
|
42
47
|
}
|
|
43
48
|
// Determine upgrade URL based on environment
|
|
@@ -59,7 +64,7 @@ async function upgradeCommand(options) {
|
|
|
59
64
|
const result = await serverPromise;
|
|
60
65
|
// Exchange custom token for refresh token
|
|
61
66
|
spinner.text = 'Refreshing your session...';
|
|
62
|
-
const { refreshToken, email } = await exchangeCustomToken(result.token,
|
|
67
|
+
const { refreshToken, email } = await exchangeCustomToken(result.token, firebaseApiKey);
|
|
63
68
|
// Store refresh token
|
|
64
69
|
try {
|
|
65
70
|
await (0, keychain_js_1.storeRefreshToken)(refreshToken);
|
|
@@ -67,12 +72,11 @@ async function upgradeCommand(options) {
|
|
|
67
72
|
catch (error) {
|
|
68
73
|
console.warn(chalk_1.default.yellow(' ⚠️ Could not save to Keychain (skipping)'));
|
|
69
74
|
}
|
|
70
|
-
// Save to file config
|
|
75
|
+
// Save to file config (API key is fetched at runtime, not stored)
|
|
71
76
|
try {
|
|
72
77
|
(0, config_store_js_1.saveConfig)({
|
|
73
78
|
refreshToken,
|
|
74
79
|
environment: options.staging ? 'staging' : 'production',
|
|
75
|
-
firebaseApiKey: config.FIREBASE_API_KEY
|
|
76
80
|
});
|
|
77
81
|
}
|
|
78
82
|
catch (error) {
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ program
|
|
|
16
16
|
.description('Authenticate with Cutline and store credentials')
|
|
17
17
|
.option('--staging', 'Use staging environment')
|
|
18
18
|
.option('--signup', 'Open sign-up page instead of sign-in')
|
|
19
|
+
.option('--email <address>', 'Request sign-in with specific email address')
|
|
19
20
|
.action(login_js_1.loginCommand);
|
|
20
21
|
program
|
|
21
22
|
.command('logout')
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
export interface Config {
|
|
2
2
|
AUTH_URL: string;
|
|
3
3
|
CALLBACK_URL: string;
|
|
4
|
+
BASE_URL: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ConfigWithApiKey extends Config {
|
|
4
7
|
FIREBASE_API_KEY: string;
|
|
5
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Get static config (URLs only, no API key)
|
|
11
|
+
*/
|
|
6
12
|
export declare function getConfig(options?: {
|
|
7
13
|
staging?: boolean;
|
|
8
14
|
}): Config;
|
|
15
|
+
/**
|
|
16
|
+
* Fetch Firebase API key from the web app's public endpoint.
|
|
17
|
+
* This avoids hardcoding API keys in source code.
|
|
18
|
+
* Falls back to environment variables if fetch fails.
|
|
19
|
+
*/
|
|
20
|
+
export declare function fetchFirebaseApiKey(options?: {
|
|
21
|
+
staging?: boolean;
|
|
22
|
+
}): Promise<string>;
|
package/dist/utils/config.js
CHANGED
|
@@ -1,21 +1,52 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getConfig = getConfig;
|
|
4
|
+
exports.fetchFirebaseApiKey = fetchFirebaseApiKey;
|
|
5
|
+
/**
|
|
6
|
+
* Get static config (URLs only, no API key)
|
|
7
|
+
*/
|
|
4
8
|
function getConfig(options = {}) {
|
|
5
9
|
if (options.staging) {
|
|
6
10
|
return {
|
|
7
11
|
AUTH_URL: process.env.CUTLINE_AUTH_URL || 'https://cutline-staging.web.app/mcp-auth',
|
|
8
12
|
CALLBACK_URL: 'http://localhost:8765',
|
|
9
|
-
|
|
10
|
-
// Also support NEXT_PUBLIC_FIREBASE_API_KEY for consistency with web app config
|
|
11
|
-
FIREBASE_API_KEY: process.env.FIREBASE_API_KEY || process.env.NEXT_PUBLIC_FIREBASE_API_KEY || 'AIzaSyAAqU_euGAMtJoXp0sECblAIndifCp0pmE',
|
|
13
|
+
BASE_URL: 'https://cutline-staging.web.app',
|
|
12
14
|
};
|
|
13
15
|
}
|
|
14
16
|
return {
|
|
15
17
|
AUTH_URL: process.env.CUTLINE_AUTH_URL || 'https://thecutline.ai/mcp-auth',
|
|
16
18
|
CALLBACK_URL: 'http://localhost:8765',
|
|
17
|
-
|
|
18
|
-
// Also support NEXT_PUBLIC_FIREBASE_API_KEY for consistency with web app config
|
|
19
|
-
FIREBASE_API_KEY: process.env.FIREBASE_API_KEY || process.env.NEXT_PUBLIC_FIREBASE_API_KEY || 'AIzaSyAYBQt7A-a_mxn5SuXPHUpyIlm0eCn42N8',
|
|
19
|
+
BASE_URL: 'https://thecutline.ai',
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Fetch Firebase API key from the web app's public endpoint.
|
|
24
|
+
* This avoids hardcoding API keys in source code.
|
|
25
|
+
* Falls back to environment variables if fetch fails.
|
|
26
|
+
*/
|
|
27
|
+
async function fetchFirebaseApiKey(options = {}) {
|
|
28
|
+
// First check environment variables
|
|
29
|
+
const envKey = process.env.FIREBASE_API_KEY || process.env.NEXT_PUBLIC_FIREBASE_API_KEY;
|
|
30
|
+
if (envKey) {
|
|
31
|
+
return envKey;
|
|
32
|
+
}
|
|
33
|
+
// Fetch from web app endpoint
|
|
34
|
+
const config = getConfig(options);
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch(`${config.BASE_URL}/api/firebase-config`, {
|
|
37
|
+
headers: { 'Accept': 'application/json' },
|
|
38
|
+
});
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(`HTTP ${response.status}`);
|
|
41
|
+
}
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
if (!data.apiKey) {
|
|
44
|
+
throw new Error('No apiKey in response');
|
|
45
|
+
}
|
|
46
|
+
return data.apiKey;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
throw new Error(`Failed to fetch Firebase config from ${config.BASE_URL}/api/firebase-config. ` +
|
|
50
|
+
`Please set FIREBASE_API_KEY environment variable or ensure network connectivity.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
package/package.json
CHANGED
package/src/commands/login.ts
CHANGED
|
@@ -4,7 +4,7 @@ import ora from 'ora';
|
|
|
4
4
|
import { startCallbackServer } from '../auth/callback.js';
|
|
5
5
|
import { storeRefreshToken } from '../auth/keychain.js';
|
|
6
6
|
import { saveConfig } from '../utils/config-store.js';
|
|
7
|
-
import { getConfig } from '../utils/config.js';
|
|
7
|
+
import { getConfig, fetchFirebaseApiKey } from '../utils/config.js';
|
|
8
8
|
|
|
9
9
|
async function exchangeCustomToken(customToken: string, apiKey: string): Promise<{ refreshToken: string; email?: string }> {
|
|
10
10
|
const response = await fetch(
|
|
@@ -31,7 +31,7 @@ async function exchangeCustomToken(customToken: string, apiKey: string): Promise
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export async function loginCommand(options: { staging?: boolean; signup?: boolean }) {
|
|
34
|
+
export async function loginCommand(options: { staging?: boolean; signup?: boolean; email?: string }) {
|
|
35
35
|
const config = getConfig(options);
|
|
36
36
|
|
|
37
37
|
if (options.signup) {
|
|
@@ -44,17 +44,28 @@ export async function loginCommand(options: { staging?: boolean; signup?: boolea
|
|
|
44
44
|
console.log(chalk.yellow(' ⚠️ Using STAGING environment\n'));
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
if (
|
|
48
|
-
console.
|
|
49
|
-
console.error(chalk.gray('Please set it before running this command:'));
|
|
50
|
-
console.error(chalk.cyan(` export FIREBASE_API_KEY=AIzaSy...`));
|
|
51
|
-
console.error(chalk.gray(' (or use NEXT_PUBLIC_FIREBASE_API_KEY for consistency with web app config)'));
|
|
52
|
-
process.exit(1);
|
|
47
|
+
if (options.email) {
|
|
48
|
+
console.log(chalk.gray(` Requesting sign-in as: ${options.email}\n`));
|
|
53
49
|
}
|
|
54
50
|
|
|
51
|
+
|
|
55
52
|
const spinner = ora('Starting authentication flow...').start();
|
|
56
53
|
|
|
57
54
|
try {
|
|
55
|
+
// Fetch Firebase API key from web app endpoint
|
|
56
|
+
spinner.text = 'Fetching configuration...';
|
|
57
|
+
let firebaseApiKey: string;
|
|
58
|
+
try {
|
|
59
|
+
firebaseApiKey = await fetchFirebaseApiKey(options);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
spinner.fail(chalk.red('Failed to fetch Firebase configuration'));
|
|
62
|
+
if (error instanceof Error) {
|
|
63
|
+
console.error(chalk.red(` ${error.message}`));
|
|
64
|
+
}
|
|
65
|
+
console.error(chalk.gray('\n You can also set the FIREBASE_API_KEY environment variable manually.\n'));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
58
69
|
// Start callback server
|
|
59
70
|
spinner.text = 'Waiting for authentication...';
|
|
60
71
|
const serverPromise = startCallbackServer();
|
|
@@ -64,6 +75,9 @@ export async function loginCommand(options: { staging?: boolean; signup?: boolea
|
|
|
64
75
|
if (options.signup) {
|
|
65
76
|
authUrl += '&mode=signup';
|
|
66
77
|
}
|
|
78
|
+
if (options.email) {
|
|
79
|
+
authUrl += `&email=${encodeURIComponent(options.email)}`;
|
|
80
|
+
}
|
|
67
81
|
await open(authUrl);
|
|
68
82
|
spinner.text = options.signup
|
|
69
83
|
? 'Browser opened - please create your account'
|
|
@@ -74,7 +88,7 @@ export async function loginCommand(options: { staging?: boolean; signup?: boolea
|
|
|
74
88
|
|
|
75
89
|
// Exchange custom token for refresh token
|
|
76
90
|
spinner.text = 'Exchanging token...';
|
|
77
|
-
const { refreshToken, email } = await exchangeCustomToken(result.token,
|
|
91
|
+
const { refreshToken, email } = await exchangeCustomToken(result.token, firebaseApiKey);
|
|
78
92
|
|
|
79
93
|
// Store refresh token
|
|
80
94
|
try {
|
|
@@ -83,12 +97,11 @@ export async function loginCommand(options: { staging?: boolean; signup?: boolea
|
|
|
83
97
|
console.warn(chalk.yellow(' ⚠️ Could not save to Keychain (skipping)'));
|
|
84
98
|
}
|
|
85
99
|
|
|
86
|
-
//
|
|
100
|
+
// Save to file config (cross-platform) - don't store API key anymore
|
|
87
101
|
try {
|
|
88
102
|
saveConfig({
|
|
89
103
|
refreshToken,
|
|
90
104
|
environment: options.staging ? 'staging' : 'production',
|
|
91
|
-
firebaseApiKey: config.FIREBASE_API_KEY
|
|
92
105
|
});
|
|
93
106
|
} catch (error) {
|
|
94
107
|
console.error(chalk.red(' ✗ Failed to save config file:'), error);
|
package/src/commands/status.ts
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import admin from 'firebase-admin';
|
|
4
4
|
import { getRefreshToken } from '../auth/keychain.js';
|
|
5
|
-
import {
|
|
5
|
+
import { fetchFirebaseApiKey } from '../utils/config.js';
|
|
6
6
|
|
|
7
7
|
async function exchangeRefreshToken(refreshToken: string, apiKey: string): Promise<string> {
|
|
8
8
|
const response = await fetch(
|
|
@@ -48,18 +48,20 @@ export async function statusCommand(options: { staging?: boolean }) {
|
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
// Get
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
// Get Firebase API key
|
|
52
|
+
spinner.text = 'Fetching configuration...';
|
|
53
|
+
let firebaseApiKey: string;
|
|
54
|
+
try {
|
|
55
|
+
firebaseApiKey = await fetchFirebaseApiKey(options);
|
|
56
|
+
} catch (error) {
|
|
55
57
|
spinner.fail(chalk.red('Configuration error'));
|
|
56
|
-
console.error(chalk.red(`
|
|
58
|
+
console.error(chalk.red(` ${error instanceof Error ? error.message : 'Failed to get Firebase API key'}`));
|
|
57
59
|
process.exit(1);
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
// Exchange refresh token for ID token
|
|
61
63
|
spinner.text = 'Verifying credentials...';
|
|
62
|
-
const idToken = await exchangeRefreshToken(refreshToken,
|
|
64
|
+
const idToken = await exchangeRefreshToken(refreshToken, firebaseApiKey);
|
|
63
65
|
|
|
64
66
|
// Initialize Firebase Admin with correct project ID
|
|
65
67
|
if (admin.apps.length === 0) {
|
package/src/commands/upgrade.ts
CHANGED
|
@@ -4,7 +4,7 @@ import ora from 'ora';
|
|
|
4
4
|
import { startCallbackServer } from '../auth/callback.js';
|
|
5
5
|
import { storeRefreshToken } from '../auth/keychain.js';
|
|
6
6
|
import { saveConfig } from '../utils/config-store.js';
|
|
7
|
-
import { getConfig } from '../utils/config.js';
|
|
7
|
+
import { getConfig, fetchFirebaseApiKey } from '../utils/config.js';
|
|
8
8
|
|
|
9
9
|
async function exchangeCustomToken(customToken: string, apiKey: string): Promise<{ refreshToken: string; email?: string }> {
|
|
10
10
|
const response = await fetch(
|
|
@@ -40,8 +40,12 @@ export async function upgradeCommand(options: { staging?: boolean }) {
|
|
|
40
40
|
console.log(chalk.yellow(' ⚠️ Using STAGING environment\n'));
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
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'}`));
|
|
45
49
|
process.exit(1);
|
|
46
50
|
}
|
|
47
51
|
|
|
@@ -71,7 +75,7 @@ export async function upgradeCommand(options: { staging?: boolean }) {
|
|
|
71
75
|
|
|
72
76
|
// Exchange custom token for refresh token
|
|
73
77
|
spinner.text = 'Refreshing your session...';
|
|
74
|
-
const { refreshToken, email } = await exchangeCustomToken(result.token,
|
|
78
|
+
const { refreshToken, email } = await exchangeCustomToken(result.token, firebaseApiKey);
|
|
75
79
|
|
|
76
80
|
// Store refresh token
|
|
77
81
|
try {
|
|
@@ -80,12 +84,11 @@ export async function upgradeCommand(options: { staging?: boolean }) {
|
|
|
80
84
|
console.warn(chalk.yellow(' ⚠️ Could not save to Keychain (skipping)'));
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
// Save to file config
|
|
87
|
+
// Save to file config (API key is fetched at runtime, not stored)
|
|
84
88
|
try {
|
|
85
89
|
saveConfig({
|
|
86
90
|
refreshToken,
|
|
87
91
|
environment: options.staging ? 'staging' : 'production',
|
|
88
|
-
firebaseApiKey: config.FIREBASE_API_KEY
|
|
89
92
|
});
|
|
90
93
|
} catch (error) {
|
|
91
94
|
console.error(chalk.red(' ✗ Failed to save config file:'), error);
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ program
|
|
|
17
17
|
.description('Authenticate with Cutline and store credentials')
|
|
18
18
|
.option('--staging', 'Use staging environment')
|
|
19
19
|
.option('--signup', 'Open sign-up page instead of sign-in')
|
|
20
|
+
.option('--email <address>', 'Request sign-in with specific email address')
|
|
20
21
|
.action(loginCommand);
|
|
21
22
|
|
|
22
23
|
program
|
|
@@ -9,7 +9,7 @@ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
|
9
9
|
export interface McpConfig {
|
|
10
10
|
refreshToken?: string;
|
|
11
11
|
environment?: 'production' | 'staging';
|
|
12
|
-
firebaseApiKey
|
|
12
|
+
// Note: firebaseApiKey is no longer stored - it's fetched from the web app at runtime
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function ensureConfigDir() {
|
package/src/utils/config.ts
CHANGED
|
@@ -1,25 +1,65 @@
|
|
|
1
1
|
export interface Config {
|
|
2
2
|
AUTH_URL: string;
|
|
3
3
|
CALLBACK_URL: string;
|
|
4
|
+
BASE_URL: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ConfigWithApiKey extends Config {
|
|
4
8
|
FIREBASE_API_KEY: string;
|
|
5
9
|
}
|
|
6
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Get static config (URLs only, no API key)
|
|
13
|
+
*/
|
|
7
14
|
export function getConfig(options: { staging?: boolean } = {}): Config {
|
|
8
15
|
if (options.staging) {
|
|
9
16
|
return {
|
|
10
17
|
AUTH_URL: process.env.CUTLINE_AUTH_URL || 'https://cutline-staging.web.app/mcp-auth',
|
|
11
18
|
CALLBACK_URL: 'http://localhost:8765',
|
|
12
|
-
|
|
13
|
-
// Also support NEXT_PUBLIC_FIREBASE_API_KEY for consistency with web app config
|
|
14
|
-
FIREBASE_API_KEY: process.env.FIREBASE_API_KEY || process.env.NEXT_PUBLIC_FIREBASE_API_KEY || 'AIzaSyAAqU_euGAMtJoXp0sECblAIndifCp0pmE',
|
|
19
|
+
BASE_URL: 'https://cutline-staging.web.app',
|
|
15
20
|
};
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
return {
|
|
19
24
|
AUTH_URL: process.env.CUTLINE_AUTH_URL || 'https://thecutline.ai/mcp-auth',
|
|
20
25
|
CALLBACK_URL: 'http://localhost:8765',
|
|
21
|
-
|
|
22
|
-
// Also support NEXT_PUBLIC_FIREBASE_API_KEY for consistency with web app config
|
|
23
|
-
FIREBASE_API_KEY: process.env.FIREBASE_API_KEY || process.env.NEXT_PUBLIC_FIREBASE_API_KEY || 'AIzaSyAYBQt7A-a_mxn5SuXPHUpyIlm0eCn42N8',
|
|
26
|
+
BASE_URL: 'https://thecutline.ai',
|
|
24
27
|
};
|
|
25
28
|
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Fetch Firebase API key from the web app's public endpoint.
|
|
32
|
+
* This avoids hardcoding API keys in source code.
|
|
33
|
+
* Falls back to environment variables if fetch fails.
|
|
34
|
+
*/
|
|
35
|
+
export async function fetchFirebaseApiKey(options: { staging?: boolean } = {}): Promise<string> {
|
|
36
|
+
// First check environment variables
|
|
37
|
+
const envKey = process.env.FIREBASE_API_KEY || process.env.NEXT_PUBLIC_FIREBASE_API_KEY;
|
|
38
|
+
if (envKey) {
|
|
39
|
+
return envKey;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Fetch from web app endpoint
|
|
43
|
+
const config = getConfig(options);
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(`${config.BASE_URL}/api/firebase-config`, {
|
|
46
|
+
headers: { 'Accept': 'application/json' },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
throw new Error(`HTTP ${response.status}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
if (!data.apiKey) {
|
|
55
|
+
throw new Error('No apiKey in response');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return data.apiKey;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Failed to fetch Firebase config from ${config.BASE_URL}/api/firebase-config. ` +
|
|
62
|
+
`Please set FIREBASE_API_KEY environment variable or ensure network connectivity.`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|