@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
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { resolve, join } from 'node:path';
|
|
5
|
+
import { getRefreshToken } from '../auth/keychain.js';
|
|
6
|
+
import { fetchFirebaseApiKey } from '../utils/config.js';
|
|
7
|
+
const CUTLINE_CONFIG = '.cutline/config.json';
|
|
8
|
+
async function authenticate(options) {
|
|
9
|
+
const refreshToken = await getRefreshToken();
|
|
10
|
+
if (!refreshToken)
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
const apiKey = await fetchFirebaseApiKey(options);
|
|
14
|
+
const response = await fetch(`https://securetoken.googleapis.com/v1/token?key=${apiKey}`, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: { 'Content-Type': 'application/json' },
|
|
17
|
+
body: JSON.stringify({ grant_type: 'refresh_token', refresh_token: refreshToken }),
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok)
|
|
20
|
+
return null;
|
|
21
|
+
const data = await response.json();
|
|
22
|
+
const idToken = data.id_token;
|
|
23
|
+
const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
|
|
24
|
+
const baseUrl = options.staging
|
|
25
|
+
? 'https://us-central1-cutline-staging.cloudfunctions.net'
|
|
26
|
+
: 'https://us-central1-cutline-prod.cloudfunctions.net';
|
|
27
|
+
const subRes = await fetch(`${baseUrl}/mcpSubscriptionStatus`, {
|
|
28
|
+
headers: { Authorization: `Bearer ${idToken}` },
|
|
29
|
+
});
|
|
30
|
+
const sub = subRes.ok ? await subRes.json() : { status: 'free' };
|
|
31
|
+
const isPremium = sub.status === 'active' || sub.status === 'trialing';
|
|
32
|
+
return {
|
|
33
|
+
tier: isPremium ? 'premium' : 'free',
|
|
34
|
+
email: payload.email,
|
|
35
|
+
uid: payload.user_id || payload.sub,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function readCutlineConfig(projectRoot) {
|
|
43
|
+
const configPath = join(projectRoot, CUTLINE_CONFIG);
|
|
44
|
+
if (!existsSync(configPath))
|
|
45
|
+
return null;
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function cursorRgrRule(config, tier) {
|
|
54
|
+
const productId = config?.product_id ?? '<from .cutline/config.json>';
|
|
55
|
+
const productName = config?.product_name ?? 'your product';
|
|
56
|
+
const verifyTool = tier === 'premium'
|
|
57
|
+
? `\`code_audit(product_id: "${productId}", project_root: "<workspace>")\``
|
|
58
|
+
: `\`engineering_audit(project_root: "<workspace>")\``;
|
|
59
|
+
const planStep = tier === 'premium'
|
|
60
|
+
? `1. **Plan**: \`rgr_plan(product_id: "${productId}", file_path: "<file>")\``
|
|
61
|
+
: `1. **Plan**: \`engineering_audit(project_root: "<workspace>")\` to identify top active issue`;
|
|
62
|
+
return `---
|
|
63
|
+
description: RGR development workflow using Cutline MCP tools
|
|
64
|
+
globs:
|
|
65
|
+
alwaysApply: true
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
# RGR-Driven Development
|
|
69
|
+
|
|
70
|
+
Product: **${productName}**
|
|
71
|
+
|
|
72
|
+
When Cutline MCP servers are connected, follow this workflow for feature implementations, bug fixes, or refactoring.
|
|
73
|
+
|
|
74
|
+
## The RGR Cycle
|
|
75
|
+
|
|
76
|
+
${planStep}
|
|
77
|
+
2. **Implement**: Address constraints/findings from the plan
|
|
78
|
+
3. **Verify**: ${verifyTool}
|
|
79
|
+
4. **Complete**: ${tier === 'premium' ? `\`rgr_complete_phase(product_id: "${productId}", phase: "<phase>")\`` : 'Re-scan to confirm scores improved'}
|
|
80
|
+
|
|
81
|
+
## When to use RGR
|
|
82
|
+
|
|
83
|
+
Always: New features, security/auth, billing, API endpoints, DB schema, infra.
|
|
84
|
+
Skip: Pure styling, docs, formatting, non-security dep bumps.
|
|
85
|
+
|
|
86
|
+
## Active vs Latent Issues
|
|
87
|
+
|
|
88
|
+
- **Active**: Problems in existing code. Fix now. (from \`code_audit\`, \`engineering_audit\`)
|
|
89
|
+
- **Latent**: Risks in planned features. Address as you build. (from deep dives, \`rgr_plan\`)
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
function cursorConstraintsRule(config, tier) {
|
|
93
|
+
if (tier === 'free') {
|
|
94
|
+
return `---
|
|
95
|
+
description: Cutline engineering audit integration
|
|
96
|
+
globs:
|
|
97
|
+
alwaysApply: true
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
# Cutline Constraints
|
|
101
|
+
|
|
102
|
+
Run \`engineering_audit(project_root)\` before major implementations to check constraint coverage.
|
|
103
|
+
|
|
104
|
+
Severity levels:
|
|
105
|
+
- **CRITICAL**: Must address before proceeding
|
|
106
|
+
- **WARNING**: Consider in your approach
|
|
107
|
+
- **INFO**: Context for UX copy, error messages
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
const productId = config?.product_id ?? '<from .cutline/config.json>';
|
|
111
|
+
return `---
|
|
112
|
+
description: Proactive constraint checking from Cutline constraint graph
|
|
113
|
+
globs:
|
|
114
|
+
alwaysApply: true
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
# Cutline Constraints
|
|
118
|
+
|
|
119
|
+
Read \`.cutline/config.json\` to get product_id. Call \`constraints_auto\` when modifying sensitive paths:
|
|
120
|
+
|
|
121
|
+
\`\`\`
|
|
122
|
+
constraints_auto(product_id: "${productId}", file_paths: [<files>], task_description: "<task>", mode: "advisory")
|
|
123
|
+
\`\`\`
|
|
124
|
+
|
|
125
|
+
Check constraints when touching: auth, billing, security, AI/LLM, integrations, user-facing flows.
|
|
126
|
+
|
|
127
|
+
Severity levels:
|
|
128
|
+
- **CRITICAL**: Must address before proceeding
|
|
129
|
+
- **WARNING**: Consider in your approach
|
|
130
|
+
- **INFO**: Context for UX copy, error messages
|
|
131
|
+
`;
|
|
132
|
+
}
|
|
133
|
+
function cursorCutlinePointer() {
|
|
134
|
+
return `---
|
|
135
|
+
description: Cutline constraint integration
|
|
136
|
+
globs:
|
|
137
|
+
alwaysApply: true
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
# Cutline Integration
|
|
141
|
+
|
|
142
|
+
Read \`.cutline.md\` before planning or executing ANY code in this repository.
|
|
143
|
+
If a rule in \`.cutline.md\` conflicts with a stylistic rule below, \`.cutline.md\` takes precedence.
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
function claudeLocalContent(config, tier) {
|
|
147
|
+
const productId = config?.product_id ?? '<product_id>';
|
|
148
|
+
const productName = config?.product_name ?? 'your product';
|
|
149
|
+
const verifyCmd = tier === 'premium'
|
|
150
|
+
? `code_audit(product_id: "${productId}", project_root)`
|
|
151
|
+
: `engineering_audit(project_root)`;
|
|
152
|
+
return `# Cutline Integration
|
|
153
|
+
|
|
154
|
+
This file is auto-generated by \`cutline-mcp init\`. Do not commit to version control.
|
|
155
|
+
|
|
156
|
+
Product: ${productName}${config?.product_id ? ` (ID: \`${config.product_id}\`)` : ''}
|
|
157
|
+
Tier: ${tier}
|
|
158
|
+
|
|
159
|
+
${tier === 'premium' ? `Read \`.cutline.md\` before planning or executing code in this repository.\n` : ''}## RGR Workflow
|
|
160
|
+
|
|
161
|
+
1. **Plan**: ${tier === 'premium' ? `\`rgr_plan(product_id: "${productId}", file_path)\`` : `\`engineering_audit(project_root)\``} before writing code
|
|
162
|
+
2. **Implement**: Address constraints/findings from the plan
|
|
163
|
+
3. **Verify**: \`${verifyCmd}\`
|
|
164
|
+
4. **Complete**: ${tier === 'premium' ? `\`rgr_complete_phase(product_id: "${productId}", phase)\`` : 'Re-scan to confirm scores improved'}
|
|
165
|
+
|
|
166
|
+
Use RGR for: new features, security/auth, billing, API endpoints, DB schema, infra.
|
|
167
|
+
Skip for: pure styling, docs, formatting, non-security dep bumps.
|
|
168
|
+
|
|
169
|
+
## Active vs Latent Issues
|
|
170
|
+
|
|
171
|
+
- **Active**: Problems in existing code. Fix now.
|
|
172
|
+
- **Latent**: Risks in planned features. Address as you build.
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
175
|
+
function ensureGitignore(projectRoot, patterns) {
|
|
176
|
+
const gitignorePath = join(projectRoot, '.gitignore');
|
|
177
|
+
if (!existsSync(gitignorePath))
|
|
178
|
+
return false;
|
|
179
|
+
let content = readFileSync(gitignorePath, 'utf-8');
|
|
180
|
+
const additions = patterns.filter((p) => !content.includes(p));
|
|
181
|
+
if (additions.length === 0)
|
|
182
|
+
return false;
|
|
183
|
+
content = content.trimEnd() + '\n\n# Cutline generated (re-run cutline-mcp init to update)\n' + additions.join('\n') + '\n';
|
|
184
|
+
writeFileSync(gitignorePath, content);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
export async function initCommand(options) {
|
|
188
|
+
const projectRoot = resolve(options.projectRoot ?? process.cwd());
|
|
189
|
+
const config = readCutlineConfig(projectRoot);
|
|
190
|
+
console.log(chalk.bold('\n🔧 Cutline Init\n'));
|
|
191
|
+
// Authenticate and determine tier
|
|
192
|
+
const spinner = ora('Checking authentication...').start();
|
|
193
|
+
const auth = await authenticate({ staging: options.staging });
|
|
194
|
+
let tier = 'free';
|
|
195
|
+
if (!auth) {
|
|
196
|
+
spinner.warn(chalk.yellow('Not authenticated — generating free-tier rules'));
|
|
197
|
+
console.log(chalk.dim(' Run `cutline-mcp login` to authenticate for richer config.\n'));
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
tier = auth.tier;
|
|
201
|
+
spinner.succeed(chalk.green(`Authenticated as ${auth.email} (${tier})`));
|
|
202
|
+
}
|
|
203
|
+
if (config) {
|
|
204
|
+
console.log(chalk.dim(` Product: ${config.product_name ?? config.product_id}`));
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
console.log(chalk.yellow(' No .cutline/config.json found — generating generic rules.'));
|
|
208
|
+
}
|
|
209
|
+
console.log();
|
|
210
|
+
const filesWritten = [];
|
|
211
|
+
// 1. Cursor rules
|
|
212
|
+
const cursorDir = join(projectRoot, '.cursor', 'rules');
|
|
213
|
+
mkdirSync(cursorDir, { recursive: true });
|
|
214
|
+
writeFileSync(join(cursorDir, 'rgr-workflow.mdc'), cursorRgrRule(config, tier));
|
|
215
|
+
filesWritten.push('.cursor/rules/rgr-workflow.mdc');
|
|
216
|
+
writeFileSync(join(cursorDir, 'ambient-constraints.mdc'), cursorConstraintsRule(config, tier));
|
|
217
|
+
filesWritten.push('.cursor/rules/ambient-constraints.mdc');
|
|
218
|
+
if (tier === 'premium') {
|
|
219
|
+
writeFileSync(join(cursorDir, 'cutline.mdc'), cursorCutlinePointer());
|
|
220
|
+
filesWritten.push('.cursor/rules/cutline.mdc');
|
|
221
|
+
}
|
|
222
|
+
// 2. Claude Code config
|
|
223
|
+
writeFileSync(join(projectRoot, 'CLAUDE.local.md'), claudeLocalContent(config, tier));
|
|
224
|
+
filesWritten.push('CLAUDE.local.md');
|
|
225
|
+
// 3. Update .gitignore
|
|
226
|
+
const gitPatterns = ['CLAUDE.local.md', '.cursor/rules/', '.cutline.md'];
|
|
227
|
+
const gitignoreUpdated = ensureGitignore(projectRoot, gitPatterns);
|
|
228
|
+
for (const f of filesWritten) {
|
|
229
|
+
console.log(chalk.green(` ✓ ${f}`));
|
|
230
|
+
}
|
|
231
|
+
if (gitignoreUpdated) {
|
|
232
|
+
console.log(chalk.dim(' Updated .gitignore'));
|
|
233
|
+
}
|
|
234
|
+
console.log(chalk.bold(`\n ${filesWritten.length} files generated.`));
|
|
235
|
+
if (tier === 'premium' && config?.product_id) {
|
|
236
|
+
console.log(chalk.dim('\n For graph-enhanced .cutline.md, ask your AI agent:'));
|
|
237
|
+
console.log(chalk.cyan(` generate_cutline_md(product_id: "${config.product_id}", project_root: "${projectRoot}")`));
|
|
238
|
+
}
|
|
239
|
+
else if (tier === 'free') {
|
|
240
|
+
console.log(chalk.dim('\n Upgrade to Premium for product-specific constraint graphs and .cutline.md'));
|
|
241
|
+
console.log(chalk.dim(' →'), chalk.cyan('cutline-mcp upgrade'), chalk.dim('or https://thecutline.ai/upgrade'));
|
|
242
|
+
}
|
|
243
|
+
console.log();
|
|
244
|
+
console.log(chalk.bold(' Next step:'));
|
|
245
|
+
console.log(chalk.dim(' Run'), chalk.cyan('cutline-mcp setup'), chalk.dim('to get the MCP server config for your IDE.\n'));
|
|
246
|
+
}
|
package/dist/commands/login.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
-
const ora_1 = __importDefault(require("ora"));
|
|
10
|
-
const callback_js_1 = require("../auth/callback.js");
|
|
11
|
-
const keychain_js_1 = require("../auth/keychain.js");
|
|
12
|
-
const config_store_js_1 = require("../utils/config-store.js");
|
|
13
|
-
const config_js_1 = require("../utils/config.js");
|
|
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';
|
|
14
8
|
async function getSubscriptionStatus(idToken, isStaging) {
|
|
15
9
|
try {
|
|
16
10
|
const baseUrl = isStaging
|
|
@@ -27,8 +21,7 @@ async function getSubscriptionStatus(idToken, isStaging) {
|
|
|
27
21
|
}
|
|
28
22
|
return await response.json();
|
|
29
23
|
}
|
|
30
|
-
catch
|
|
31
|
-
// Silently fail - subscription check is optional during login
|
|
24
|
+
catch {
|
|
32
25
|
return { status: 'unknown' };
|
|
33
26
|
}
|
|
34
27
|
}
|
|
@@ -66,40 +59,40 @@ async function exchangeCustomToken(customToken, apiKey) {
|
|
|
66
59
|
email: data.email,
|
|
67
60
|
};
|
|
68
61
|
}
|
|
69
|
-
async function loginCommand(options) {
|
|
70
|
-
const config =
|
|
62
|
+
export async function loginCommand(options) {
|
|
63
|
+
const config = getConfig(options);
|
|
71
64
|
if (options.signup) {
|
|
72
|
-
console.log(
|
|
65
|
+
console.log(chalk.bold('\n🚀 Cutline MCP - Create Account\n'));
|
|
73
66
|
}
|
|
74
67
|
else {
|
|
75
|
-
console.log(
|
|
68
|
+
console.log(chalk.bold('\n🔐 Cutline MCP Authentication\n'));
|
|
76
69
|
}
|
|
77
70
|
if (options.staging) {
|
|
78
|
-
console.log(
|
|
71
|
+
console.log(chalk.yellow(' ⚠️ Using STAGING environment\n'));
|
|
79
72
|
}
|
|
80
73
|
if (options.email) {
|
|
81
|
-
console.log(
|
|
74
|
+
console.log(chalk.gray(` Requesting sign-in as: ${options.email}\n`));
|
|
82
75
|
}
|
|
83
|
-
const spinner = (
|
|
76
|
+
const spinner = ora('Starting authentication flow...').start();
|
|
84
77
|
try {
|
|
85
78
|
// Fetch Firebase API key from web app endpoint
|
|
86
79
|
spinner.text = 'Fetching configuration...';
|
|
87
80
|
let firebaseApiKey;
|
|
88
81
|
try {
|
|
89
|
-
firebaseApiKey = await
|
|
82
|
+
firebaseApiKey = await fetchFirebaseApiKey(options);
|
|
90
83
|
}
|
|
91
84
|
catch (error) {
|
|
92
|
-
spinner.fail(
|
|
85
|
+
spinner.fail(chalk.red('Failed to fetch Firebase configuration'));
|
|
93
86
|
if (error instanceof Error) {
|
|
94
|
-
console.error(
|
|
87
|
+
console.error(chalk.red(` ${error.message}`));
|
|
95
88
|
}
|
|
96
|
-
console.error(
|
|
89
|
+
console.error(chalk.gray('\n You can also set the FIREBASE_API_KEY environment variable manually.\n'));
|
|
97
90
|
process.exit(1);
|
|
98
91
|
}
|
|
99
92
|
// Start callback server
|
|
100
93
|
spinner.text = 'Waiting for authentication...';
|
|
101
|
-
const serverPromise =
|
|
102
|
-
// Open browser
|
|
94
|
+
const serverPromise = startCallbackServer();
|
|
95
|
+
// Open browser — default is the quick email-only flow
|
|
103
96
|
let authUrl = `${config.AUTH_URL}?callback=${encodeURIComponent(config.CALLBACK_URL)}`;
|
|
104
97
|
if (options.signup) {
|
|
105
98
|
authUrl += '&mode=signup';
|
|
@@ -107,10 +100,10 @@ async function loginCommand(options) {
|
|
|
107
100
|
if (options.email) {
|
|
108
101
|
authUrl += `&email=${encodeURIComponent(options.email)}`;
|
|
109
102
|
}
|
|
110
|
-
await (
|
|
103
|
+
await open(authUrl);
|
|
111
104
|
spinner.text = options.signup
|
|
112
105
|
? 'Browser opened - please create your account'
|
|
113
|
-
: 'Browser opened -
|
|
106
|
+
: 'Browser opened - enter your email to get started (check email for sign-in link)';
|
|
114
107
|
// Wait for callback with custom token
|
|
115
108
|
const result = await serverPromise;
|
|
116
109
|
// Exchange custom token for refresh token
|
|
@@ -118,27 +111,27 @@ async function loginCommand(options) {
|
|
|
118
111
|
const { refreshToken, email } = await exchangeCustomToken(result.token, firebaseApiKey);
|
|
119
112
|
// Store refresh token
|
|
120
113
|
try {
|
|
121
|
-
await
|
|
114
|
+
await storeRefreshToken(refreshToken);
|
|
122
115
|
}
|
|
123
116
|
catch (error) {
|
|
124
|
-
console.warn(
|
|
117
|
+
console.warn(chalk.yellow(' ⚠️ Could not save to Keychain (skipping)'));
|
|
125
118
|
}
|
|
126
119
|
// Save to file config (cross-platform)
|
|
127
120
|
try {
|
|
128
|
-
|
|
121
|
+
saveConfig({
|
|
129
122
|
refreshToken,
|
|
130
123
|
environment: options.staging ? 'staging' : 'production',
|
|
131
124
|
});
|
|
132
125
|
}
|
|
133
126
|
catch (error) {
|
|
134
|
-
console.error(
|
|
127
|
+
console.error(chalk.red(' ✗ Failed to save config file:'), error);
|
|
135
128
|
}
|
|
136
|
-
spinner.succeed(
|
|
129
|
+
spinner.succeed(chalk.green('Successfully authenticated!'));
|
|
137
130
|
// Show environment indicator
|
|
138
|
-
const envLabel = options.staging ?
|
|
139
|
-
console.log(
|
|
131
|
+
const envLabel = options.staging ? chalk.yellow('STAGING') : chalk.green('PRODUCTION');
|
|
132
|
+
console.log(chalk.gray(` Environment: ${envLabel}`));
|
|
140
133
|
if (email || result.email) {
|
|
141
|
-
console.log(
|
|
134
|
+
console.log(chalk.gray(` Logged in as: ${email || result.email}`));
|
|
142
135
|
}
|
|
143
136
|
// Check subscription status
|
|
144
137
|
try {
|
|
@@ -148,24 +141,25 @@ async function loginCommand(options) {
|
|
|
148
141
|
spinner.stop();
|
|
149
142
|
if (subscription.status === 'active' || subscription.status === 'trialing') {
|
|
150
143
|
const statusLabel = subscription.status === 'trialing' ? ' (trial)' : '';
|
|
151
|
-
console.log(
|
|
144
|
+
console.log(chalk.gray(' Plan:'), chalk.green(`✓ ${subscription.planName || 'Premium'}${statusLabel}`));
|
|
152
145
|
}
|
|
153
146
|
else {
|
|
154
|
-
console.log(
|
|
155
|
-
console.log(
|
|
147
|
+
console.log(chalk.gray(' Plan:'), chalk.white('Free'));
|
|
148
|
+
console.log(chalk.dim(' Upgrade at'), chalk.cyan('https://thecutline.ai/pricing'));
|
|
156
149
|
}
|
|
157
150
|
}
|
|
158
151
|
catch {
|
|
159
152
|
spinner.stop();
|
|
160
153
|
// Silently skip subscription check on error
|
|
161
154
|
}
|
|
162
|
-
console.log(
|
|
163
|
-
console.log(
|
|
155
|
+
console.log();
|
|
156
|
+
console.log(chalk.bold(' Next step:'));
|
|
157
|
+
console.log(chalk.dim(' Run'), chalk.cyan('cutline-mcp init'), chalk.dim('in your project directory to generate IDE rules.\n'));
|
|
164
158
|
}
|
|
165
159
|
catch (error) {
|
|
166
|
-
spinner.fail(
|
|
160
|
+
spinner.fail(chalk.red('Authentication failed'));
|
|
167
161
|
if (error instanceof Error) {
|
|
168
|
-
console.error(
|
|
162
|
+
console.error(chalk.red(` ${error.message}\n`));
|
|
169
163
|
}
|
|
170
164
|
process.exit(1);
|
|
171
165
|
}
|
package/dist/commands/logout.js
CHANGED
|
@@ -1,30 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
-
const ora_1 = __importDefault(require("ora"));
|
|
9
|
-
const keychain_js_1 = require("../auth/keychain.js");
|
|
10
|
-
async function logoutCommand() {
|
|
11
|
-
console.log(chalk_1.default.bold('\n👋 Logging out of Cutline MCP\n'));
|
|
12
|
-
const spinner = (0, ora_1.default)('Removing stored credentials...').start();
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { deleteRefreshToken } from '../auth/keychain.js';
|
|
4
|
+
export async function logoutCommand() {
|
|
5
|
+
console.log(chalk.bold('\n👋 Logging out of Cutline MCP\n'));
|
|
6
|
+
const spinner = ora('Removing stored credentials...').start();
|
|
13
7
|
try {
|
|
14
|
-
const deleted = await
|
|
8
|
+
const deleted = await deleteRefreshToken();
|
|
15
9
|
if (deleted) {
|
|
16
|
-
spinner.succeed(
|
|
17
|
-
console.log(
|
|
10
|
+
spinner.succeed(chalk.green('Successfully logged out'));
|
|
11
|
+
console.log(chalk.gray(' Credentials removed from keychain\n'));
|
|
18
12
|
}
|
|
19
13
|
else {
|
|
20
|
-
spinner.info(
|
|
21
|
-
console.log(
|
|
14
|
+
spinner.info(chalk.yellow('No credentials found'));
|
|
15
|
+
console.log(chalk.gray(' You were not logged in\n'));
|
|
22
16
|
}
|
|
23
17
|
}
|
|
24
18
|
catch (error) {
|
|
25
|
-
spinner.fail(
|
|
19
|
+
spinner.fail(chalk.red('Logout failed'));
|
|
26
20
|
if (error instanceof Error) {
|
|
27
|
-
console.error(
|
|
21
|
+
console.error(chalk.red(` ${error.message}\n`));
|
|
28
22
|
}
|
|
29
23
|
process.exit(1);
|
|
30
24
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function serveCommand(serverName: string): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { resolve, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const SERVER_MAP = {
|
|
7
|
+
constraints: 'cutline-server.js',
|
|
8
|
+
premortem: 'premortem-server.js',
|
|
9
|
+
exploration: 'exploration-server.js',
|
|
10
|
+
tools: 'tools-server.js',
|
|
11
|
+
output: 'output-server.js',
|
|
12
|
+
integrations: 'integrations-server.js',
|
|
13
|
+
};
|
|
14
|
+
export function serveCommand(serverName) {
|
|
15
|
+
const fileName = SERVER_MAP[serverName];
|
|
16
|
+
if (!fileName) {
|
|
17
|
+
const valid = Object.keys(SERVER_MAP).join(', ');
|
|
18
|
+
console.error(`Unknown server: "${serverName}". Valid names: ${valid}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const serverPath = resolve(__dirname, '../servers', fileName);
|
|
22
|
+
if (!existsSync(serverPath)) {
|
|
23
|
+
console.error(`Server bundle not found at ${serverPath}`);
|
|
24
|
+
console.error('The package may not have been built correctly.');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
// Replace this process with the MCP server.
|
|
28
|
+
// MCP servers use stdio transport, so we need to keep stdin/stdout connected.
|
|
29
|
+
try {
|
|
30
|
+
execFileSync(process.execPath, [serverPath], {
|
|
31
|
+
stdio: 'inherit',
|
|
32
|
+
env: process.env,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
process.exit(err.status ?? 1);
|
|
37
|
+
}
|
|
38
|
+
}
|