@vibekiln/cutline-mcp-cli 0.1.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 (43) hide show
  1. package/Dockerfile +11 -0
  2. package/README.md +248 -0
  3. package/dist/auth/callback.d.ts +6 -0
  4. package/dist/auth/callback.js +97 -0
  5. package/dist/auth/keychain.d.ts +3 -0
  6. package/dist/auth/keychain.js +16 -0
  7. package/dist/commands/init.d.ts +4 -0
  8. package/dist/commands/init.js +309 -0
  9. package/dist/commands/login.d.ts +7 -0
  10. package/dist/commands/login.js +166 -0
  11. package/dist/commands/logout.d.ts +1 -0
  12. package/dist/commands/logout.js +25 -0
  13. package/dist/commands/serve.d.ts +1 -0
  14. package/dist/commands/serve.js +38 -0
  15. package/dist/commands/setup.d.ts +5 -0
  16. package/dist/commands/setup.js +278 -0
  17. package/dist/commands/status.d.ts +3 -0
  18. package/dist/commands/status.js +127 -0
  19. package/dist/commands/upgrade.d.ts +3 -0
  20. package/dist/commands/upgrade.js +112 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +64 -0
  23. package/dist/servers/chunk-DE7R7WKY.js +331 -0
  24. package/dist/servers/chunk-KMUSQOTJ.js +47 -0
  25. package/dist/servers/chunk-OP4EO6FV.js +454 -0
  26. package/dist/servers/chunk-UBBAYTW3.js +946 -0
  27. package/dist/servers/chunk-ZVWDXO6M.js +1063 -0
  28. package/dist/servers/cutline-server.js +10448 -0
  29. package/dist/servers/data-client-FPUZBUO3.js +160 -0
  30. package/dist/servers/exploration-server.js +930 -0
  31. package/dist/servers/graph-metrics-DCNR7JZN.js +12 -0
  32. package/dist/servers/integrations-server.js +107 -0
  33. package/dist/servers/output-server.js +107 -0
  34. package/dist/servers/premortem-server.js +971 -0
  35. package/dist/servers/tools-server.js +287 -0
  36. package/dist/utils/config-store.d.ts +8 -0
  37. package/dist/utils/config-store.js +35 -0
  38. package/dist/utils/config.d.ts +22 -0
  39. package/dist/utils/config.js +48 -0
  40. package/mcpb/manifest.json +77 -0
  41. package/package.json +76 -0
  42. package/server.json +42 -0
  43. package/smithery.yaml +10 -0
@@ -0,0 +1,309 @@
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
+ import { saveConfig, loadConfig } from '../utils/config-store.js';
8
+ const CUTLINE_CONFIG = '.cutline/config.json';
9
+ async function authenticate(options) {
10
+ const refreshToken = await getRefreshToken();
11
+ if (!refreshToken)
12
+ return null;
13
+ try {
14
+ const apiKey = await fetchFirebaseApiKey(options);
15
+ const response = await fetch(`https://securetoken.googleapis.com/v1/token?key=${apiKey}`, {
16
+ method: 'POST',
17
+ headers: { 'Content-Type': 'application/json' },
18
+ body: JSON.stringify({ grant_type: 'refresh_token', refresh_token: refreshToken }),
19
+ });
20
+ if (!response.ok)
21
+ return null;
22
+ const data = await response.json();
23
+ const idToken = data.id_token;
24
+ const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
25
+ const baseUrl = options.staging
26
+ ? 'https://us-central1-cutline-staging.cloudfunctions.net'
27
+ : 'https://us-central1-cutline-prod.cloudfunctions.net';
28
+ const subRes = await fetch(`${baseUrl}/mcpSubscriptionStatus`, {
29
+ headers: { Authorization: `Bearer ${idToken}` },
30
+ });
31
+ const sub = subRes.ok ? await subRes.json() : { status: 'free' };
32
+ const isPremium = sub.status === 'active' || sub.status === 'trialing';
33
+ return {
34
+ tier: isPremium ? 'premium' : 'free',
35
+ email: payload.email,
36
+ uid: payload.user_id || payload.sub,
37
+ idToken,
38
+ baseUrl,
39
+ };
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ }
45
+ function readCutlineConfig(projectRoot) {
46
+ const configPath = join(projectRoot, CUTLINE_CONFIG);
47
+ if (!existsSync(configPath))
48
+ return null;
49
+ try {
50
+ return JSON.parse(readFileSync(configPath, 'utf-8'));
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ }
56
+ function cursorRgrRule(config, tier) {
57
+ const productId = config?.product_id ?? '<from .cutline/config.json>';
58
+ const productName = config?.product_name ?? 'your product';
59
+ const verifyTool = tier === 'premium'
60
+ ? `\`engineering_audit(product_id: "${productId}", project_root: "<workspace>")\``
61
+ : `\`code_audit(project_root: "<workspace>")\``;
62
+ const planStep = tier === 'premium'
63
+ ? `1. **Plan**: \`rgr_plan(product_id: "${productId}", file_path: "<file>")\``
64
+ : `1. **Plan**: \`code_audit(project_root: "<workspace>")\` to identify top active issue`;
65
+ return `---
66
+ description: RGR development workflow using Cutline MCP tools
67
+ globs:
68
+ alwaysApply: true
69
+ ---
70
+
71
+ # RGR-Driven Development
72
+
73
+ Product: **${productName}**
74
+
75
+ When Cutline MCP servers are connected, follow this workflow for feature implementations, bug fixes, or refactoring.
76
+
77
+ ## The RGR Cycle
78
+
79
+ ${planStep}
80
+ 2. **Implement**: Address constraints/findings from the plan
81
+ 3. **Verify**: ${verifyTool}
82
+ 4. **Complete**: ${tier === 'premium' ? `\`rgr_complete_phase(product_id: "${productId}", phase: "<phase>")\`` : 'Re-scan to confirm scores improved'}
83
+
84
+ ## When to use RGR
85
+
86
+ Always: New features, security/auth, billing, API endpoints, DB schema, infra.
87
+ Skip: Pure styling, docs, formatting, non-security dep bumps.
88
+
89
+ ## Active vs Latent Issues
90
+
91
+ - **Active**: Problems in existing code. Fix now. (from \`code_audit\`, \`engineering_audit\`)
92
+ - **Latent**: Risks in planned features. Address as you build. (from deep dives, \`rgr_plan\`)
93
+
94
+ ## Magic Phrase
95
+
96
+ If the user invokes Cutline naturally (for example: **"use cutline"**, **"use cutline to..."**, **"using cutline..."**, **"with cutline..."**), route automatically based on tier and intent:
97
+ - Free/default intent: run \`code_audit(project_root)\`
98
+ - Premium product-linked intent: run \`engineering_audit(product_id, project_root)\`
99
+ - Feature implementation intent: run \`rgr_plan(...)\` then \`constraints_auto(...)\`
100
+ `;
101
+ }
102
+ function cursorConstraintsRule(config, tier) {
103
+ if (tier === 'free') {
104
+ return `---
105
+ description: Cutline engineering audit integration
106
+ globs:
107
+ alwaysApply: true
108
+ ---
109
+
110
+ # Cutline Constraints
111
+
112
+ Run \`code_audit(project_root)\` before major implementations to check constraint coverage.
113
+
114
+ Severity levels:
115
+ - **CRITICAL**: Must address before proceeding
116
+ - **WARNING**: Consider in your approach
117
+ - **INFO**: Context for UX copy, error messages
118
+ `;
119
+ }
120
+ const productId = config?.product_id ?? '<from .cutline/config.json>';
121
+ return `---
122
+ description: Proactive constraint checking from Cutline constraint graph
123
+ globs:
124
+ alwaysApply: true
125
+ ---
126
+
127
+ # Cutline Constraints
128
+
129
+ Read \`.cutline/config.json\` to get product_id. Call \`constraints_auto\` when modifying sensitive paths:
130
+
131
+ \`\`\`
132
+ constraints_auto(product_id: "${productId}", file_paths: [<files>], task_description: "<task>", mode: "advisory")
133
+ \`\`\`
134
+
135
+ Check constraints when touching: auth, billing, security, AI/LLM, integrations, user-facing flows.
136
+
137
+ Severity levels:
138
+ - **CRITICAL**: Must address before proceeding
139
+ - **WARNING**: Consider in your approach
140
+ - **INFO**: Context for UX copy, error messages
141
+ `;
142
+ }
143
+ function cursorCutlinePointer() {
144
+ return `---
145
+ description: Cutline constraint integration
146
+ globs:
147
+ alwaysApply: true
148
+ ---
149
+
150
+ # Cutline Integration
151
+
152
+ Read \`.cutline.md\` before planning or executing ANY code in this repository.
153
+ If a rule in \`.cutline.md\` conflicts with a stylistic rule below, \`.cutline.md\` takes precedence.
154
+ `;
155
+ }
156
+ function claudeLocalContent(config, tier) {
157
+ const productId = config?.product_id ?? '<product_id>';
158
+ const productName = config?.product_name ?? 'your product';
159
+ const verifyCmd = tier === 'premium'
160
+ ? `engineering_audit(product_id: "${productId}", project_root)`
161
+ : `code_audit(project_root)`;
162
+ return `# Cutline Integration
163
+
164
+ This file is auto-generated by \`cutline-mcp init\`. Do not commit to version control.
165
+
166
+ Product: ${productName}${config?.product_id ? ` (ID: \`${config.product_id}\`)` : ''}
167
+ Tier: ${tier}
168
+
169
+ ${tier === 'premium' ? `Read \`.cutline.md\` before planning or executing code in this repository.\n` : ''}## RGR Workflow
170
+
171
+ 1. **Plan**: ${tier === 'premium' ? `\`rgr_plan(product_id: "${productId}", file_path)\`` : `\`code_audit(project_root)\``} before writing code
172
+ 2. **Implement**: Address constraints/findings from the plan
173
+ 3. **Verify**: \`${verifyCmd}\`
174
+ 4. **Complete**: ${tier === 'premium' ? `\`rgr_complete_phase(product_id: "${productId}", phase)\`` : 'Re-scan to confirm scores improved'}
175
+
176
+ Use RGR for: new features, security/auth, billing, API endpoints, DB schema, infra.
177
+ Skip for: pure styling, docs, formatting, non-security dep bumps.
178
+
179
+ ## Active vs Latent Issues
180
+
181
+ - **Active**: Problems in existing code. Fix now.
182
+ - **Latent**: Risks in planned features. Address as you build.
183
+
184
+ ## Magic Phrase
185
+
186
+ If the user invokes Cutline naturally (for example: **"use cutline"**, **"use cutline to..."**, **"using cutline..."**, **"with cutline..."**), route automatically based on tier and intent:
187
+ - Free/default intent: \`code_audit(project_root)\`
188
+ - Premium product-linked intent: \`engineering_audit(product_id, project_root)\`
189
+ - Feature implementation intent: \`rgr_plan(...)\` then \`constraints_auto(...)\`
190
+ `;
191
+ }
192
+ function ensureGitignore(projectRoot, patterns) {
193
+ const gitignorePath = join(projectRoot, '.gitignore');
194
+ if (!existsSync(gitignorePath))
195
+ return false;
196
+ let content = readFileSync(gitignorePath, 'utf-8');
197
+ const additions = patterns.filter((p) => !content.includes(p));
198
+ if (additions.length === 0)
199
+ return false;
200
+ content = content.trimEnd() + '\n\n# Cutline generated (re-run cutline-mcp init to update)\n' + additions.join('\n') + '\n';
201
+ writeFileSync(gitignorePath, content);
202
+ return true;
203
+ }
204
+ export async function initCommand(options) {
205
+ const projectRoot = resolve(options.projectRoot ?? process.cwd());
206
+ const config = readCutlineConfig(projectRoot);
207
+ console.log(chalk.bold('\nšŸ”§ Cutline Init\n'));
208
+ // Authenticate and determine tier
209
+ const spinner = ora('Checking authentication...').start();
210
+ const auth = await authenticate({ staging: options.staging });
211
+ let tier = 'free';
212
+ if (!auth) {
213
+ spinner.warn(chalk.yellow('Not authenticated — generating free-tier rules'));
214
+ console.log(chalk.dim(' Run `cutline-mcp login` to authenticate for richer config.\n'));
215
+ }
216
+ else {
217
+ tier = auth.tier;
218
+ spinner.succeed(chalk.green(`Authenticated as ${auth.email} (${tier})`));
219
+ }
220
+ if (config) {
221
+ console.log(chalk.dim(` Product: ${config.product_name ?? config.product_id}`));
222
+ }
223
+ else {
224
+ console.log(chalk.yellow(' No .cutline/config.json found — generating generic rules.'));
225
+ }
226
+ // Generate API key for authenticated users (skips if one already exists)
227
+ let generatedApiKey = null;
228
+ if (auth?.idToken && auth.baseUrl) {
229
+ const existing = loadConfig();
230
+ if (existing.apiKey) {
231
+ console.log(chalk.dim(' API key: already configured'));
232
+ }
233
+ else {
234
+ const keySpinner = ora('Generating API key...').start();
235
+ try {
236
+ const keyRes = await fetch(`${auth.baseUrl}/mcpDataProxy`, {
237
+ method: 'POST',
238
+ headers: {
239
+ 'Content-Type': 'application/json',
240
+ Authorization: `Bearer ${auth.idToken}`,
241
+ },
242
+ body: JSON.stringify({ action: 'apiKey.create', params: { name: 'cursor-ide' } }),
243
+ });
244
+ if (keyRes.ok) {
245
+ const keyData = await keyRes.json();
246
+ generatedApiKey = keyData.key;
247
+ saveConfig({ apiKey: generatedApiKey });
248
+ keySpinner.succeed(chalk.green('API key generated and saved'));
249
+ }
250
+ else {
251
+ const errBody = await keyRes.text().catch(() => '');
252
+ keySpinner.warn(chalk.yellow(`API key generation skipped (${keyRes.status})`));
253
+ if (errBody)
254
+ console.log(chalk.dim(` ${errBody.slice(0, 120)}`));
255
+ }
256
+ }
257
+ catch (e) {
258
+ keySpinner.warn(chalk.yellow('API key generation skipped (network error)'));
259
+ }
260
+ }
261
+ }
262
+ console.log();
263
+ const filesWritten = [];
264
+ // 1. Cursor rules
265
+ const cursorDir = join(projectRoot, '.cursor', 'rules');
266
+ mkdirSync(cursorDir, { recursive: true });
267
+ writeFileSync(join(cursorDir, 'rgr-workflow.mdc'), cursorRgrRule(config, tier));
268
+ filesWritten.push('.cursor/rules/rgr-workflow.mdc');
269
+ writeFileSync(join(cursorDir, 'ambient-constraints.mdc'), cursorConstraintsRule(config, tier));
270
+ filesWritten.push('.cursor/rules/ambient-constraints.mdc');
271
+ if (tier === 'premium') {
272
+ writeFileSync(join(cursorDir, 'cutline.mdc'), cursorCutlinePointer());
273
+ filesWritten.push('.cursor/rules/cutline.mdc');
274
+ }
275
+ // 2. Claude Code config
276
+ writeFileSync(join(projectRoot, 'CLAUDE.local.md'), claudeLocalContent(config, tier));
277
+ filesWritten.push('CLAUDE.local.md');
278
+ // 3. Update .gitignore
279
+ const gitPatterns = ['CLAUDE.local.md', '.cursor/rules/', '.cutline.md'];
280
+ const gitignoreUpdated = ensureGitignore(projectRoot, gitPatterns);
281
+ for (const f of filesWritten) {
282
+ console.log(chalk.green(` āœ“ ${f}`));
283
+ }
284
+ if (gitignoreUpdated) {
285
+ console.log(chalk.dim(' Updated .gitignore'));
286
+ }
287
+ console.log(chalk.bold(`\n ${filesWritten.length} files generated.`));
288
+ if (tier === 'premium' && config?.product_id) {
289
+ console.log(chalk.dim('\n For graph-enhanced .cutline.md, ask your AI agent:'));
290
+ console.log(chalk.cyan(` generate_cutline_md(product_id: "${config.product_id}", project_root: "${projectRoot}")`));
291
+ }
292
+ else if (tier === 'free') {
293
+ console.log(chalk.dim('\n Upgrade to Premium for product-specific constraint graphs and .cutline.md'));
294
+ console.log(chalk.dim(' →'), chalk.cyan('cutline-mcp upgrade'), chalk.dim('or https://thecutline.ai/upgrade'));
295
+ }
296
+ if (generatedApiKey) {
297
+ console.log();
298
+ console.log(chalk.bold(' API Key (shown once):'));
299
+ console.log(chalk.cyan(` ${generatedApiKey}`));
300
+ console.log();
301
+ console.log(chalk.dim(' Add to your Cursor MCP config for best performance:'));
302
+ console.log(chalk.dim(' "env": { "CUTLINE_API_KEY": "') + chalk.cyan(generatedApiKey) + chalk.dim('" }'));
303
+ console.log();
304
+ console.log(chalk.dim(' Or it will be used automatically from ~/.cutline-mcp/config.json'));
305
+ }
306
+ console.log();
307
+ console.log(chalk.bold(' Next step:'));
308
+ console.log(chalk.dim(' Run'), chalk.cyan('cutline-mcp setup'), chalk.dim('to get the MCP server config for your IDE.\n'));
309
+ }
@@ -0,0 +1,7 @@
1
+ import { type CallbackSource } from '../auth/callback.js';
2
+ export declare function loginCommand(options: {
3
+ staging?: boolean;
4
+ signup?: boolean;
5
+ email?: string;
6
+ source?: CallbackSource;
7
+ }): Promise<void>;
@@ -0,0 +1,166 @@
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
+ async function getSubscriptionStatus(idToken, isStaging) {
9
+ try {
10
+ const baseUrl = isStaging
11
+ ? 'https://us-central1-cutline-staging.cloudfunctions.net'
12
+ : 'https://us-central1-cutline-prod.cloudfunctions.net';
13
+ const response = await fetch(`${baseUrl}/mcpSubscriptionStatus`, {
14
+ method: 'GET',
15
+ headers: {
16
+ 'Authorization': `Bearer ${idToken}`,
17
+ },
18
+ });
19
+ if (!response.ok) {
20
+ return { status: 'unknown' };
21
+ }
22
+ return await response.json();
23
+ }
24
+ catch {
25
+ return { status: 'unknown' };
26
+ }
27
+ }
28
+ async function exchangeRefreshForIdToken(refreshToken, apiKey) {
29
+ const response = await fetch(`https://securetoken.googleapis.com/v1/token?key=${apiKey}`, {
30
+ method: 'POST',
31
+ headers: { 'Content-Type': 'application/json' },
32
+ body: JSON.stringify({
33
+ grant_type: 'refresh_token',
34
+ refresh_token: refreshToken,
35
+ }),
36
+ });
37
+ if (!response.ok) {
38
+ throw new Error('Failed to get ID token');
39
+ }
40
+ const data = await response.json();
41
+ return data.id_token;
42
+ }
43
+ async function exchangeCustomToken(customToken, apiKey) {
44
+ const response = await fetch(`https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apiKey}`, {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json' },
47
+ body: JSON.stringify({
48
+ token: customToken,
49
+ returnSecureToken: true,
50
+ }),
51
+ });
52
+ if (!response.ok) {
53
+ const error = await response.text();
54
+ throw new Error(`Failed to exchange custom token: ${error}`);
55
+ }
56
+ const data = await response.json();
57
+ return {
58
+ refreshToken: data.refreshToken,
59
+ email: data.email,
60
+ };
61
+ }
62
+ export async function loginCommand(options) {
63
+ const config = getConfig(options);
64
+ if (options.signup) {
65
+ console.log(chalk.bold('\nšŸš€ Cutline MCP - Create Account\n'));
66
+ }
67
+ else {
68
+ console.log(chalk.bold('\nšŸ” Cutline MCP Authentication\n'));
69
+ }
70
+ if (options.staging) {
71
+ console.log(chalk.yellow(' āš ļø Using STAGING environment\n'));
72
+ }
73
+ if (options.email) {
74
+ console.log(chalk.gray(` Requesting sign-in as: ${options.email}\n`));
75
+ }
76
+ const spinner = ora('Starting authentication flow...').start();
77
+ try {
78
+ // Fetch Firebase API key from web app endpoint
79
+ spinner.text = 'Fetching configuration...';
80
+ let firebaseApiKey;
81
+ try {
82
+ firebaseApiKey = await fetchFirebaseApiKey(options);
83
+ }
84
+ catch (error) {
85
+ spinner.fail(chalk.red('Failed to fetch Firebase configuration'));
86
+ if (error instanceof Error) {
87
+ console.error(chalk.red(` ${error.message}`));
88
+ }
89
+ console.error(chalk.gray('\n You can also set the FIREBASE_API_KEY environment variable manually.\n'));
90
+ process.exit(1);
91
+ }
92
+ // Start callback server
93
+ spinner.text = 'Waiting for authentication...';
94
+ const serverPromise = startCallbackServer(options.source ?? 'login');
95
+ // Open browser — default is the quick email-only flow
96
+ let authUrl = `${config.AUTH_URL}?callback=${encodeURIComponent(config.CALLBACK_URL)}`;
97
+ if (options.signup) {
98
+ authUrl += '&mode=signup';
99
+ }
100
+ if (options.email) {
101
+ authUrl += `&email=${encodeURIComponent(options.email)}`;
102
+ }
103
+ await open(authUrl);
104
+ spinner.text = options.signup
105
+ ? 'Browser opened - please create your account'
106
+ : 'Browser opened - enter your email to get started (check email for sign-in link)';
107
+ // Wait for callback with custom token
108
+ const result = await serverPromise;
109
+ // Exchange custom token for refresh token
110
+ spinner.text = 'Exchanging token...';
111
+ const { refreshToken, email } = await exchangeCustomToken(result.token, firebaseApiKey);
112
+ // Store refresh token
113
+ try {
114
+ await storeRefreshToken(refreshToken);
115
+ }
116
+ catch (error) {
117
+ console.warn(chalk.yellow(' āš ļø Could not save to Keychain (skipping)'));
118
+ }
119
+ // Save to file config (cross-platform)
120
+ try {
121
+ saveConfig({
122
+ refreshToken,
123
+ environment: options.staging ? 'staging' : 'production',
124
+ });
125
+ }
126
+ catch (error) {
127
+ console.error(chalk.red(' āœ— Failed to save config file:'), error);
128
+ }
129
+ spinner.succeed(chalk.green('Successfully authenticated!'));
130
+ // Show environment indicator
131
+ const envLabel = options.staging ? chalk.yellow('STAGING') : chalk.green('PRODUCTION');
132
+ console.log(chalk.gray(` Environment: ${envLabel}`));
133
+ if (email || result.email) {
134
+ console.log(chalk.gray(` Logged in as: ${email || result.email}`));
135
+ }
136
+ // Check subscription status
137
+ try {
138
+ spinner.start('Checking subscription...');
139
+ const idToken = await exchangeRefreshForIdToken(refreshToken, firebaseApiKey);
140
+ const subscription = await getSubscriptionStatus(idToken, !!options.staging);
141
+ spinner.stop();
142
+ if (subscription.status === 'active' || subscription.status === 'trialing') {
143
+ const statusLabel = subscription.status === 'trialing' ? ' (trial)' : '';
144
+ console.log(chalk.gray(' Plan:'), chalk.green(`āœ“ ${subscription.planName || 'Premium'}${statusLabel}`));
145
+ }
146
+ else {
147
+ console.log(chalk.gray(' Plan:'), chalk.white('Free'));
148
+ console.log(chalk.dim(' Upgrade at'), chalk.cyan('https://thecutline.ai/pricing'));
149
+ }
150
+ }
151
+ catch {
152
+ spinner.stop();
153
+ // Silently skip subscription check on error
154
+ }
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'));
158
+ }
159
+ catch (error) {
160
+ spinner.fail(chalk.red('Authentication failed'));
161
+ if (error instanceof Error) {
162
+ console.error(chalk.red(` ${error.message}\n`));
163
+ }
164
+ process.exit(1);
165
+ }
166
+ }
@@ -0,0 +1 @@
1
+ export declare function logoutCommand(): Promise<void>;
@@ -0,0 +1,25 @@
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();
7
+ try {
8
+ const deleted = await deleteRefreshToken();
9
+ if (deleted) {
10
+ spinner.succeed(chalk.green('Successfully logged out'));
11
+ console.log(chalk.gray(' Credentials removed from keychain\n'));
12
+ }
13
+ else {
14
+ spinner.info(chalk.yellow('No credentials found'));
15
+ console.log(chalk.gray(' You were not logged in\n'));
16
+ }
17
+ }
18
+ catch (error) {
19
+ spinner.fail(chalk.red('Logout failed'));
20
+ if (error instanceof Error) {
21
+ console.error(chalk.red(` ${error.message}\n`));
22
+ }
23
+ process.exit(1);
24
+ }
25
+ }
@@ -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
+ }
@@ -0,0 +1,5 @@
1
+ export declare function setupCommand(options: {
2
+ staging?: boolean;
3
+ skipLogin?: boolean;
4
+ projectRoot?: string;
5
+ }): Promise<void>;