@kylewadegrove/cutline-mcp-cli 0.4.2 → 0.5.1

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 +267 -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
@@ -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
+ }
@@ -1,16 +1,10 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.loginCommand = loginCommand;
7
- const open_1 = __importDefault(require("open"));
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 (error) {
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 = (0, config_js_1.getConfig)(options);
62
+ export async function loginCommand(options) {
63
+ const config = getConfig(options);
71
64
  if (options.signup) {
72
- console.log(chalk_1.default.bold('\n🚀 Cutline MCP - Create Account\n'));
65
+ console.log(chalk.bold('\n🚀 Cutline MCP - Create Account\n'));
73
66
  }
74
67
  else {
75
- console.log(chalk_1.default.bold('\n🔐 Cutline MCP Authentication\n'));
68
+ console.log(chalk.bold('\n🔐 Cutline MCP Authentication\n'));
76
69
  }
77
70
  if (options.staging) {
78
- console.log(chalk_1.default.yellow(' ⚠️ Using STAGING environment\n'));
71
+ console.log(chalk.yellow(' ⚠️ Using STAGING environment\n'));
79
72
  }
80
73
  if (options.email) {
81
- console.log(chalk_1.default.gray(` Requesting sign-in as: ${options.email}\n`));
74
+ console.log(chalk.gray(` Requesting sign-in as: ${options.email}\n`));
82
75
  }
83
- const spinner = (0, ora_1.default)('Starting authentication flow...').start();
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 (0, config_js_1.fetchFirebaseApiKey)(options);
82
+ firebaseApiKey = await fetchFirebaseApiKey(options);
90
83
  }
91
84
  catch (error) {
92
- spinner.fail(chalk_1.default.red('Failed to fetch Firebase configuration'));
85
+ spinner.fail(chalk.red('Failed to fetch Firebase configuration'));
93
86
  if (error instanceof Error) {
94
- console.error(chalk_1.default.red(` ${error.message}`));
87
+ console.error(chalk.red(` ${error.message}`));
95
88
  }
96
- console.error(chalk_1.default.gray('\n You can also set the FIREBASE_API_KEY environment variable manually.\n'));
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 = (0, callback_js_1.startCallbackServer)();
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 (0, open_1.default)(authUrl);
103
+ await open(authUrl);
111
104
  spinner.text = options.signup
112
105
  ? 'Browser opened - please create your account'
113
- : 'Browser opened - please sign in or create an account';
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 (0, keychain_js_1.storeRefreshToken)(refreshToken);
114
+ await storeRefreshToken(refreshToken);
122
115
  }
123
116
  catch (error) {
124
- console.warn(chalk_1.default.yellow(' ⚠️ Could not save to Keychain (skipping)'));
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
- (0, config_store_js_1.saveConfig)({
121
+ saveConfig({
129
122
  refreshToken,
130
123
  environment: options.staging ? 'staging' : 'production',
131
124
  });
132
125
  }
133
126
  catch (error) {
134
- console.error(chalk_1.default.red(' ✗ Failed to save config file:'), error);
127
+ console.error(chalk.red(' ✗ Failed to save config file:'), error);
135
128
  }
136
- spinner.succeed(chalk_1.default.green('Successfully authenticated!'));
129
+ spinner.succeed(chalk.green('Successfully authenticated!'));
137
130
  // Show environment indicator
138
- const envLabel = options.staging ? chalk_1.default.yellow('STAGING') : chalk_1.default.green('PRODUCTION');
139
- console.log(chalk_1.default.gray(` Environment: ${envLabel}`));
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(chalk_1.default.gray(` Logged in as: ${email || result.email}`));
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(chalk_1.default.gray(' Plan:'), chalk_1.default.green(`✓ ${subscription.planName || 'Premium'}${statusLabel}`));
144
+ console.log(chalk.gray(' Plan:'), chalk.green(`✓ ${subscription.planName || 'Premium'}${statusLabel}`));
152
145
  }
153
146
  else {
154
- console.log(chalk_1.default.gray(' Plan:'), chalk_1.default.white('Free'));
155
- console.log(chalk_1.default.dim(' Upgrade at'), chalk_1.default.cyan('https://thecutline.ai/pricing'));
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(chalk_1.default.gray('\n MCP servers can now access your account\n'));
163
- console.log(chalk_1.default.dim(' Run'), chalk_1.default.cyan('cutline-mcp status'), chalk_1.default.dim('to verify\n'));
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(chalk_1.default.red('Authentication failed'));
160
+ spinner.fail(chalk.red('Authentication failed'));
167
161
  if (error instanceof Error) {
168
- console.error(chalk_1.default.red(` ${error.message}\n`));
162
+ console.error(chalk.red(` ${error.message}\n`));
169
163
  }
170
164
  process.exit(1);
171
165
  }
@@ -1,30 +1,24 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.logoutCommand = logoutCommand;
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 (0, keychain_js_1.deleteRefreshToken)();
8
+ const deleted = await deleteRefreshToken();
15
9
  if (deleted) {
16
- spinner.succeed(chalk_1.default.green('Successfully logged out'));
17
- console.log(chalk_1.default.gray(' Credentials removed from keychain\n'));
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(chalk_1.default.yellow('No credentials found'));
21
- console.log(chalk_1.default.gray(' You were not logged in\n'));
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(chalk_1.default.red('Logout failed'));
19
+ spinner.fail(chalk.red('Logout failed'));
26
20
  if (error instanceof Error) {
27
- console.error(chalk_1.default.red(` ${error.message}\n`));
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
+ }
@@ -0,0 +1,5 @@
1
+ export declare function setupCommand(options: {
2
+ staging?: boolean;
3
+ skipLogin?: boolean;
4
+ projectRoot?: string;
5
+ }): Promise<void>;