@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.
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 +255 -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,255 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { createInterface } from 'node:readline';
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
5
+ import { homedir } from 'node:os';
6
+ import { join, dirname, resolve } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { getRefreshToken } from '../auth/keychain.js';
9
+ import { fetchFirebaseApiKey } from '../utils/config.js';
10
+ import { loginCommand } from './login.js';
11
+ import { initCommand } from './init.js';
12
+ function getCliVersion() {
13
+ try {
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const pkg = JSON.parse(readFileSync(join(dirname(__filename), '..', '..', 'package.json'), 'utf-8'));
16
+ return pkg.version ?? 'unknown';
17
+ }
18
+ catch {
19
+ return 'unknown';
20
+ }
21
+ }
22
+ const SERVER_NAMES = [
23
+ 'constraints',
24
+ 'premortem',
25
+ 'exploration',
26
+ 'tools',
27
+ 'output',
28
+ 'integrations',
29
+ ];
30
+ async function detectTier(options) {
31
+ const refreshToken = await getRefreshToken();
32
+ if (!refreshToken)
33
+ return { tier: 'free' };
34
+ try {
35
+ const apiKey = await fetchFirebaseApiKey(options);
36
+ const response = await fetch(`https://securetoken.googleapis.com/v1/token?key=${apiKey}`, {
37
+ method: 'POST',
38
+ headers: { 'Content-Type': 'application/json' },
39
+ body: JSON.stringify({ grant_type: 'refresh_token', refresh_token: refreshToken }),
40
+ });
41
+ if (!response.ok)
42
+ return { tier: 'free' };
43
+ const data = await response.json();
44
+ const idToken = data.id_token;
45
+ const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
46
+ const baseUrl = options.staging
47
+ ? 'https://us-central1-cutline-staging.cloudfunctions.net'
48
+ : 'https://us-central1-cutline-prod.cloudfunctions.net';
49
+ const subRes = await fetch(`${baseUrl}/mcpSubscriptionStatus`, {
50
+ headers: { Authorization: `Bearer ${idToken}` },
51
+ });
52
+ const sub = subRes.ok ? await subRes.json() : { status: 'free' };
53
+ const isPremium = sub.status === 'active' || sub.status === 'trialing';
54
+ return { tier: isPremium ? 'premium' : 'free', email: payload.email, idToken };
55
+ }
56
+ catch {
57
+ return { tier: 'free' };
58
+ }
59
+ }
60
+ async function fetchProducts(idToken, options) {
61
+ try {
62
+ const baseUrl = options.staging
63
+ ? 'https://us-central1-cutline-staging.cloudfunctions.net'
64
+ : 'https://us-central1-cutline-prod.cloudfunctions.net';
65
+ const res = await fetch(`${baseUrl}/mcpListProducts`, {
66
+ headers: { Authorization: `Bearer ${idToken}` },
67
+ });
68
+ if (!res.ok)
69
+ return [];
70
+ const data = await res.json();
71
+ return data.products ?? [];
72
+ }
73
+ catch {
74
+ return [];
75
+ }
76
+ }
77
+ function prompt(question) {
78
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
79
+ return new Promise((resolve) => {
80
+ rl.question(question, (answer) => {
81
+ rl.close();
82
+ resolve(answer.trim());
83
+ });
84
+ });
85
+ }
86
+ function buildServerConfig() {
87
+ const config = {};
88
+ for (const name of SERVER_NAMES) {
89
+ config[`cutline-${name}`] = {
90
+ command: 'npx',
91
+ args: ['-y', '@kylewadegrove/cutline-mcp-cli@latest', 'serve', name],
92
+ };
93
+ }
94
+ return config;
95
+ }
96
+ function mergeIdeConfig(filePath, serverConfig) {
97
+ let existing = {};
98
+ if (existsSync(filePath)) {
99
+ try {
100
+ existing = JSON.parse(readFileSync(filePath, 'utf-8'));
101
+ }
102
+ catch {
103
+ existing = {};
104
+ }
105
+ }
106
+ else {
107
+ const dir = join(filePath, '..');
108
+ mkdirSync(dir, { recursive: true });
109
+ }
110
+ const existingServers = (existing.mcpServers ?? {});
111
+ // Remove old cutline-* entries, then add fresh ones
112
+ const cleaned = {};
113
+ for (const [key, val] of Object.entries(existingServers)) {
114
+ if (!key.startsWith('cutline-')) {
115
+ cleaned[key] = val;
116
+ }
117
+ }
118
+ existing.mcpServers = { ...cleaned, ...serverConfig };
119
+ writeFileSync(filePath, JSON.stringify(existing, null, 2) + '\n');
120
+ return true;
121
+ }
122
+ export async function setupCommand(options) {
123
+ const version = getCliVersion();
124
+ console.log(chalk.bold(`\n🔌 Cutline MCP Setup`) + chalk.dim(` v${version}\n`));
125
+ // ── 1. Authenticate ──────────────────────────────────────────────────────
126
+ const hasToken = await getRefreshToken();
127
+ if (!hasToken && !options.skipLogin) {
128
+ console.log(chalk.dim(' No credentials found — starting login flow.\n'));
129
+ await loginCommand({ staging: options.staging });
130
+ console.log();
131
+ }
132
+ const spinner = ora('Detecting account tier...').start();
133
+ const { tier, email, idToken } = await detectTier({ staging: options.staging });
134
+ if (email) {
135
+ spinner.succeed(chalk.green(`Authenticated as ${email} (${tier})`));
136
+ }
137
+ else {
138
+ spinner.succeed(chalk.yellow(`Running as ${tier} tier`));
139
+ }
140
+ console.log();
141
+ // ── 2. Connect to a product graph ────────────────────────────────────────
142
+ const projectRoot = resolve(options.projectRoot ?? process.cwd());
143
+ const configPath = join(projectRoot, '.cutline', 'config.json');
144
+ const hasExistingConfig = existsSync(configPath);
145
+ if (tier === 'premium' && idToken && !hasExistingConfig) {
146
+ const productSpinner = ora('Fetching your product graphs...').start();
147
+ const products = await fetchProducts(idToken, { staging: options.staging });
148
+ productSpinner.stop();
149
+ if (products.length > 0) {
150
+ console.log(chalk.bold(' Connect to a product graph\n'));
151
+ products.forEach((p, i) => {
152
+ const date = p.createdAt ? chalk.dim(` (${new Date(p.createdAt).toLocaleDateString()})`) : '';
153
+ console.log(` ${chalk.cyan(`${i + 1}.`)} ${chalk.white(p.name)}${date}`);
154
+ if (p.brief)
155
+ console.log(` ${chalk.dim(p.brief)}`);
156
+ });
157
+ console.log(` ${chalk.dim(`${products.length + 1}.`)} ${chalk.dim('Skip — I\'ll connect later')}`);
158
+ console.log();
159
+ const answer = await prompt(chalk.cyan(' Select a product (number): '));
160
+ const choice = parseInt(answer, 10);
161
+ if (choice >= 1 && choice <= products.length) {
162
+ const selected = products[choice - 1];
163
+ mkdirSync(join(projectRoot, '.cutline'), { recursive: true });
164
+ writeFileSync(configPath, JSON.stringify({
165
+ product_id: selected.id,
166
+ product_name: selected.name,
167
+ }, null, 2) + '\n');
168
+ console.log(chalk.green(`\n ✓ Connected to "${selected.name}"`));
169
+ console.log(chalk.dim(` ${configPath}\n`));
170
+ }
171
+ else {
172
+ console.log(chalk.dim('\n Skipped. Run `cutline-mcp setup` again to connect later.\n'));
173
+ }
174
+ }
175
+ }
176
+ else if (hasExistingConfig) {
177
+ try {
178
+ const existing = JSON.parse(readFileSync(configPath, 'utf-8'));
179
+ console.log(chalk.green(` ✓ Connected to product graph:`), chalk.white(existing.product_name || existing.product_id));
180
+ console.log();
181
+ }
182
+ catch { /* ignore parse errors */ }
183
+ }
184
+ // ── 3. Write MCP server config to IDEs ───────────────────────────────────
185
+ const serverConfig = buildServerConfig();
186
+ const home = homedir();
187
+ const ideConfigs = [
188
+ { name: 'Cursor', path: join(home, '.cursor', 'mcp.json') },
189
+ { name: 'Claude Code', path: join(home, '.claude', 'settings.json') },
190
+ ];
191
+ let wroteAny = false;
192
+ for (const ide of ideConfigs) {
193
+ // Write to Cursor always (primary target); write to Claude if dir exists
194
+ const dirExists = existsSync(join(ide.path, '..'));
195
+ if (ide.name === 'Cursor' || dirExists) {
196
+ try {
197
+ mergeIdeConfig(ide.path, serverConfig);
198
+ console.log(chalk.green(` ✓ ${ide.name}`), chalk.dim(ide.path));
199
+ wroteAny = true;
200
+ }
201
+ catch (err) {
202
+ console.log(chalk.red(` ✗ ${ide.name}`), chalk.dim(err.message));
203
+ }
204
+ }
205
+ }
206
+ if (wroteAny) {
207
+ console.log(chalk.dim('\n MCP server entries merged into IDE config (existing servers preserved).\n'));
208
+ }
209
+ else {
210
+ console.log(chalk.yellow('\n No IDE config files found. Printing config for manual setup:\n'));
211
+ console.log(chalk.green(JSON.stringify({ mcpServers: serverConfig }, null, 2)));
212
+ console.log();
213
+ }
214
+ // ── 4. Generate IDE rules ────────────────────────────────────────────────
215
+ console.log(chalk.bold(' Generating IDE rules...\n'));
216
+ await initCommand({ projectRoot: options.projectRoot, staging: options.staging });
217
+ // ── 5. Claude Code one-liners ────────────────────────────────────────────
218
+ console.log(chalk.bold(' Claude Code one-liner alternative:\n'));
219
+ console.log(chalk.dim(' If you prefer `claude mcp add` instead of settings.json:\n'));
220
+ const coreServers = ['constraints', 'premortem', 'tools', 'exploration'];
221
+ for (const name of coreServers) {
222
+ console.log(chalk.cyan(` claude mcp add cutline-${name} -- npx -y @kylewadegrove/cutline-mcp-cli serve ${name}`));
223
+ }
224
+ console.log();
225
+ // ── 6. What you can do ───────────────────────────────────────────────────
226
+ console.log(chalk.bold(' Restart your IDE, then ask your AI agent:\n'));
227
+ if (tier === 'premium') {
228
+ const items = [
229
+ { cmd: 'Run a deep dive on my product idea', desc: 'Pre-mortem analysis — risks, assumptions, experiments' },
230
+ { cmd: 'Run a code audit for my product', desc: 'Security scan + RGR remediation plan' },
231
+ { cmd: 'Check constraints for src/api/upload.ts', desc: 'Get NFR boundaries for a specific file' },
232
+ { cmd: 'Generate .cutline.md for my product', desc: 'Write the constraint routing engine' },
233
+ { cmd: 'What does my persona think about X?', desc: 'AI persona feedback on features' },
234
+ ];
235
+ for (const item of items) {
236
+ console.log(` ${chalk.cyan('→')} ${chalk.white(`"${item.cmd}"`)}`);
237
+ console.log(` ${chalk.dim(item.desc)}`);
238
+ }
239
+ }
240
+ else {
241
+ const items = [
242
+ { cmd: 'Run an engineering audit on this codebase', desc: 'Security, reliability, and scalability scan (3/month free)' },
243
+ ];
244
+ for (const item of items) {
245
+ console.log(` ${chalk.cyan('→')} ${chalk.white(`"${item.cmd}"`)}`);
246
+ console.log(` ${chalk.dim(item.desc)}`);
247
+ }
248
+ console.log();
249
+ console.log(chalk.dim(' Upgrade to Premium for deep dives, code audits, constraint graphs, and personas'));
250
+ console.log(chalk.dim(' →'), chalk.cyan('cutline-mcp upgrade'), chalk.dim('or https://thecutline.ai/upgrade'));
251
+ }
252
+ console.log();
253
+ console.log(chalk.dim(` cutline-mcp v${version} · docs: https://thecutline.ai/docs/setup`));
254
+ console.log();
255
+ }
@@ -1,13 +1,7 @@
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.statusCommand = statusCommand;
7
- const chalk_1 = __importDefault(require("chalk"));
8
- const ora_1 = __importDefault(require("ora"));
9
- const keychain_js_1 = require("../auth/keychain.js");
10
- const config_js_1 = require("../utils/config.js");
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { getRefreshToken } from '../auth/keychain.js';
4
+ import { fetchFirebaseApiKey } from '../utils/config.js';
11
5
  async function getSubscriptionStatus(idToken, isStaging) {
12
6
  try {
13
7
  const baseUrl = isStaging
@@ -53,26 +47,26 @@ async function exchangeRefreshToken(refreshToken, apiKey) {
53
47
  const data = await response.json();
54
48
  return data.id_token;
55
49
  }
56
- async function statusCommand(options) {
57
- console.log(chalk_1.default.bold('\n📊 Cutline MCP Status\n'));
58
- const spinner = (0, ora_1.default)('Checking authentication...').start();
50
+ export async function statusCommand(options) {
51
+ console.log(chalk.bold('\n📊 Cutline MCP Status\n'));
52
+ const spinner = ora('Checking authentication...').start();
59
53
  try {
60
54
  // Check for stored refresh token
61
- const refreshToken = await (0, keychain_js_1.getRefreshToken)();
55
+ const refreshToken = await getRefreshToken();
62
56
  if (!refreshToken) {
63
- spinner.info(chalk_1.default.yellow('Not authenticated'));
64
- console.log(chalk_1.default.gray(' Run'), chalk_1.default.cyan('cutline-mcp login'), chalk_1.default.gray('to authenticate\n'));
57
+ spinner.info(chalk.yellow('Not authenticated'));
58
+ console.log(chalk.gray(' Run'), chalk.cyan('cutline-mcp login'), chalk.gray('to authenticate\n'));
65
59
  return;
66
60
  }
67
61
  // Get Firebase API key
68
62
  spinner.text = 'Fetching configuration...';
69
63
  let firebaseApiKey;
70
64
  try {
71
- firebaseApiKey = await (0, config_js_1.fetchFirebaseApiKey)(options);
65
+ firebaseApiKey = await fetchFirebaseApiKey(options);
72
66
  }
73
67
  catch (error) {
74
- spinner.fail(chalk_1.default.red('Configuration error'));
75
- console.error(chalk_1.default.red(` ${error instanceof Error ? error.message : 'Failed to get Firebase API key'}`));
68
+ spinner.fail(chalk.red('Configuration error'));
69
+ console.error(chalk.red(` ${error instanceof Error ? error.message : 'Failed to get Firebase API key'}`));
76
70
  process.exit(1);
77
71
  }
78
72
  // Exchange refresh token for ID token
@@ -81,18 +75,18 @@ async function statusCommand(options) {
81
75
  // Decode JWT payload (base64) to get user info - no verification needed, just display
82
76
  const payloadBase64 = idToken.split('.')[1];
83
77
  const decoded = JSON.parse(Buffer.from(payloadBase64, 'base64').toString());
84
- spinner.succeed(chalk_1.default.green('Authenticated'));
85
- console.log(chalk_1.default.gray(' User:'), chalk_1.default.white(decoded.email || decoded.user_id || decoded.sub));
86
- console.log(chalk_1.default.gray(' UID:'), chalk_1.default.dim(decoded.user_id || decoded.sub));
78
+ spinner.succeed(chalk.green('Authenticated'));
79
+ console.log(chalk.gray(' User:'), chalk.white(decoded.email || decoded.user_id || decoded.sub));
80
+ console.log(chalk.gray(' UID:'), chalk.dim(decoded.user_id || decoded.sub));
87
81
  // Calculate token expiry
88
82
  const expiresIn = Math.floor((decoded.exp * 1000 - Date.now()) / 1000 / 60);
89
- console.log(chalk_1.default.gray(' Token expires in:'), chalk_1.default.white(`${expiresIn} minutes`));
83
+ console.log(chalk.gray(' Token expires in:'), chalk.white(`${expiresIn} minutes`));
90
84
  // Show custom claims if present
91
85
  if (decoded.mcp) {
92
- console.log(chalk_1.default.gray(' MCP enabled:'), chalk_1.default.green('✓'));
86
+ console.log(chalk.gray(' MCP enabled:'), chalk.green('✓'));
93
87
  }
94
88
  if (decoded.deviceId) {
95
- console.log(chalk_1.default.gray(' Device ID:'), chalk_1.default.dim(decoded.deviceId));
89
+ console.log(chalk.gray(' Device ID:'), chalk.dim(decoded.deviceId));
96
90
  }
97
91
  // Check subscription status via Cloud Function
98
92
  spinner.start('Checking subscription...');
@@ -100,33 +94,33 @@ async function statusCommand(options) {
100
94
  spinner.stop();
101
95
  if (subscription.status === 'active' || subscription.status === 'trialing') {
102
96
  const statusLabel = subscription.status === 'trialing' ? ' (trial)' : '';
103
- console.log(chalk_1.default.gray(' Plan:'), chalk_1.default.green(`✓ ${subscription.planName || 'Premium'}${statusLabel}`));
97
+ console.log(chalk.gray(' Plan:'), chalk.green(`✓ ${subscription.planName || 'Premium'}${statusLabel}`));
104
98
  if (subscription.periodEnd) {
105
99
  const periodEndDate = new Date(subscription.periodEnd);
106
100
  const daysLeft = Math.ceil((periodEndDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
107
- console.log(chalk_1.default.gray(' Renews:'), chalk_1.default.white(`${periodEndDate.toLocaleDateString()} (${daysLeft} days)`));
101
+ console.log(chalk.gray(' Renews:'), chalk.white(`${periodEndDate.toLocaleDateString()} (${daysLeft} days)`));
108
102
  }
109
103
  }
110
104
  else if (subscription.status === 'past_due') {
111
- console.log(chalk_1.default.gray(' Plan:'), chalk_1.default.yellow('⚠ Premium (payment past due)'));
105
+ console.log(chalk.gray(' Plan:'), chalk.yellow('⚠ Premium (payment past due)'));
112
106
  }
113
107
  else if (subscription.status === 'canceled') {
114
- console.log(chalk_1.default.gray(' Plan:'), chalk_1.default.yellow('Premium (canceled)'));
108
+ console.log(chalk.gray(' Plan:'), chalk.yellow('Premium (canceled)'));
115
109
  if (subscription.periodEnd) {
116
- console.log(chalk_1.default.gray(' Access until:'), chalk_1.default.white(new Date(subscription.periodEnd).toLocaleDateString()));
110
+ console.log(chalk.gray(' Access until:'), chalk.white(new Date(subscription.periodEnd).toLocaleDateString()));
117
111
  }
118
112
  }
119
113
  else {
120
- console.log(chalk_1.default.gray(' Plan:'), chalk_1.default.white('Free'));
121
- console.log(chalk_1.default.dim(' Upgrade at'), chalk_1.default.cyan('https://thecutline.ai/pricing'));
114
+ console.log(chalk.gray(' Plan:'), chalk.white('Free'));
115
+ console.log(chalk.dim(' Upgrade at'), chalk.cyan('https://thecutline.ai/pricing'));
122
116
  }
123
117
  console.log();
124
118
  }
125
119
  catch (error) {
126
- spinner.fail(chalk_1.default.red('Status check failed'));
120
+ spinner.fail(chalk.red('Status check failed'));
127
121
  if (error instanceof Error) {
128
- console.error(chalk_1.default.red(` ${error.message}`));
129
- console.log(chalk_1.default.gray(' Try running'), chalk_1.default.cyan('cutline-mcp login'), chalk_1.default.gray('again\n'));
122
+ console.error(chalk.red(` ${error.message}`));
123
+ console.log(chalk.gray(' Try running'), chalk.cyan('cutline-mcp login'), chalk.gray('again\n'));
130
124
  }
131
125
  process.exit(1);
132
126
  }
@@ -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.upgradeCommand = upgradeCommand;
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 exchangeCustomToken(customToken, apiKey) {
15
9
  const response = await fetch(`https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apiKey}`, {
16
10
  method: 'POST',
@@ -30,35 +24,35 @@ async function exchangeCustomToken(customToken, apiKey) {
30
24
  email: data.email,
31
25
  };
32
26
  }
33
- async function upgradeCommand(options) {
34
- const config = (0, config_js_1.getConfig)(options);
35
- console.log(chalk_1.default.bold('\n⬆️ Cutline MCP - Upgrade to Premium\n'));
27
+ export async function upgradeCommand(options) {
28
+ const config = getConfig(options);
29
+ console.log(chalk.bold('\n⬆️ Cutline MCP - Upgrade to Premium\n'));
36
30
  if (options.staging) {
37
- console.log(chalk_1.default.yellow(' ⚠️ Using STAGING environment\n'));
31
+ console.log(chalk.yellow(' ⚠️ Using STAGING environment\n'));
38
32
  }
39
33
  // Fetch Firebase API key
40
34
  let firebaseApiKey;
41
35
  try {
42
- firebaseApiKey = await (0, config_js_1.fetchFirebaseApiKey)(options);
36
+ firebaseApiKey = await fetchFirebaseApiKey(options);
43
37
  }
44
38
  catch (error) {
45
- console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : 'Failed to get Firebase API key'}`));
39
+ console.error(chalk.red(`Error: ${error instanceof Error ? error.message : 'Failed to get Firebase API key'}`));
46
40
  process.exit(1);
47
41
  }
48
42
  // Determine upgrade URL based on environment
49
43
  const baseUrl = options.staging
50
44
  ? 'https://cutline-staging.web.app'
51
45
  : 'https://thecutline.ai';
52
- console.log(chalk_1.default.gray(' Opening upgrade page in your browser...\n'));
53
- console.log(chalk_1.default.dim(' After upgrading, your MCP session will be refreshed automatically.\n'));
54
- const spinner = (0, ora_1.default)('Waiting for upgrade and re-authentication...').start();
46
+ console.log(chalk.gray(' Opening upgrade page in your browser...\n'));
47
+ console.log(chalk.dim(' After upgrading, your MCP session will be refreshed automatically.\n'));
48
+ const spinner = ora('Waiting for upgrade and re-authentication...').start();
55
49
  try {
56
50
  // Start callback server for re-auth after upgrade
57
- const serverPromise = (0, callback_js_1.startCallbackServer)();
51
+ const serverPromise = startCallbackServer();
58
52
  // Open upgrade page with callback for re-auth
59
53
  // The upgrade page will redirect to mcp-auth after successful upgrade
60
54
  const upgradeUrl = `${baseUrl}/upgrade?mcp_callback=${encodeURIComponent(config.CALLBACK_URL)}`;
61
- await (0, open_1.default)(upgradeUrl);
55
+ await open(upgradeUrl);
62
56
  spinner.text = 'Browser opened - complete your upgrade, then re-authenticate';
63
57
  // Wait for callback with new token (after upgrade + re-auth)
64
58
  const result = await serverPromise;
@@ -67,37 +61,49 @@ async function upgradeCommand(options) {
67
61
  const { refreshToken, email } = await exchangeCustomToken(result.token, firebaseApiKey);
68
62
  // Store refresh token
69
63
  try {
70
- await (0, keychain_js_1.storeRefreshToken)(refreshToken);
64
+ await storeRefreshToken(refreshToken);
71
65
  }
72
66
  catch (error) {
73
- console.warn(chalk_1.default.yellow(' ⚠️ Could not save to Keychain (skipping)'));
67
+ console.warn(chalk.yellow(' ⚠️ Could not save to Keychain (skipping)'));
74
68
  }
75
69
  // Save to file config (API key is fetched at runtime, not stored)
76
70
  try {
77
- (0, config_store_js_1.saveConfig)({
71
+ saveConfig({
78
72
  refreshToken,
79
73
  environment: options.staging ? 'staging' : 'production',
80
74
  });
81
75
  }
82
76
  catch (error) {
83
- console.error(chalk_1.default.red(' ✗ Failed to save config file:'), error);
77
+ console.error(chalk.red(' ✗ Failed to save config file:'), error);
84
78
  }
85
- spinner.succeed(chalk_1.default.green('Upgrade complete! Session refreshed.'));
86
- const envLabel = options.staging ? chalk_1.default.yellow('STAGING') : chalk_1.default.green('PRODUCTION');
87
- console.log(chalk_1.default.gray(` Environment: ${envLabel}`));
79
+ spinner.succeed(chalk.green('Upgrade complete! Session refreshed.'));
80
+ const envLabel = options.staging ? chalk.yellow('STAGING') : chalk.green('PRODUCTION');
81
+ console.log(chalk.gray(` Environment: ${envLabel}`));
88
82
  if (email || result.email) {
89
- console.log(chalk_1.default.gray(` Account: ${email || result.email}`));
83
+ console.log(chalk.gray(` Account: ${email || result.email}`));
90
84
  }
91
- console.log(chalk_1.default.green('\n Premium features are now available!\n'));
92
- console.log(chalk_1.default.dim(' Try:'), chalk_1.default.cyan('exploration_graduate'), chalk_1.default.dim('to run a full Deep Dive\n'));
85
+ console.log(chalk.green('\n Premium features are now available!\n'));
86
+ console.log(chalk.bold(' Re-run init to update your IDE rules:'));
87
+ console.log(chalk.cyan(' cutline-mcp init\n'));
88
+ console.log(chalk.bold(' Then ask your AI agent:\n'));
89
+ const items = [
90
+ { cmd: 'Run a deep dive on my product idea', desc: 'Pre-mortem analysis — risks, assumptions, experiments' },
91
+ { cmd: 'Run a code audit for my product', desc: 'Security scan + RGR remediation plan' },
92
+ { cmd: 'Generate .cutline.md for my product', desc: 'Write the constraint routing engine' },
93
+ ];
94
+ for (const item of items) {
95
+ console.log(` ${chalk.cyan('→')} ${chalk.white(`"${item.cmd}"`)}`);
96
+ console.log(` ${chalk.dim(item.desc)}`);
97
+ }
98
+ console.log();
93
99
  }
94
100
  catch (error) {
95
- spinner.fail(chalk_1.default.red('Upgrade flow failed'));
101
+ spinner.fail(chalk.red('Upgrade flow failed'));
96
102
  if (error instanceof Error) {
97
- console.error(chalk_1.default.red(` ${error.message}`));
103
+ console.error(chalk.red(` ${error.message}`));
98
104
  }
99
- console.log(chalk_1.default.gray('\n You can also upgrade at:'), chalk_1.default.cyan(`${baseUrl}/upgrade`));
100
- console.log(chalk_1.default.gray(' Then run:'), chalk_1.default.cyan('cutline-mcp login'), chalk_1.default.gray('to refresh your session\n'));
105
+ console.log(chalk.gray('\n You can also upgrade at:'), chalk.cyan(`${baseUrl}/upgrade`));
106
+ console.log(chalk.gray(' Then run:'), chalk.cyan('cutline-mcp login'), chalk.gray('to refresh your session\n'));
101
107
  process.exit(1);
102
108
  }
103
109
  }
package/dist/index.js CHANGED
@@ -1,35 +1,59 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- const commander_1 = require("commander");
5
- const login_js_1 = require("./commands/login.js");
6
- const logout_js_1 = require("./commands/logout.js");
7
- const status_js_1 = require("./commands/status.js");
8
- const upgrade_js_1 = require("./commands/upgrade.js");
9
- const program = new commander_1.Command();
2
+ import { Command } from 'commander';
3
+ import { readFileSync } from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
6
+ import { loginCommand } from './commands/login.js';
7
+ import { logoutCommand } from './commands/logout.js';
8
+ import { statusCommand } from './commands/status.js';
9
+ import { upgradeCommand } from './commands/upgrade.js';
10
+ import { serveCommand } from './commands/serve.js';
11
+ import { setupCommand } from './commands/setup.js';
12
+ import { initCommand } from './commands/init.js';
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
16
+ const program = new Command();
10
17
  program
11
18
  .name('cutline-mcp')
12
- .description('CLI tool for authenticating with Cutline MCP servers')
13
- .version('0.1.0');
19
+ .description('CLI and MCP servers for Cutline')
20
+ .version(pkg.version);
14
21
  program
15
22
  .command('login')
16
23
  .description('Authenticate with Cutline and store credentials')
17
24
  .option('--staging', 'Use staging environment')
18
25
  .option('--signup', 'Open sign-up page instead of sign-in')
19
26
  .option('--email <address>', 'Request sign-in with specific email address')
20
- .action(login_js_1.loginCommand);
27
+ .action(loginCommand);
21
28
  program
22
29
  .command('logout')
23
30
  .description('Remove stored credentials')
24
- .action(logout_js_1.logoutCommand);
31
+ .action(logoutCommand);
25
32
  program
26
33
  .command('status')
27
34
  .description('Show current authentication status')
28
35
  .option('--staging', 'Use staging environment')
29
- .action(status_js_1.statusCommand);
36
+ .action(statusCommand);
30
37
  program
31
38
  .command('upgrade')
32
39
  .description('Upgrade to Premium and refresh your session')
33
40
  .option('--staging', 'Use staging environment')
34
- .action(upgrade_js_1.upgradeCommand);
41
+ .action(upgradeCommand);
42
+ program
43
+ .command('serve <server>')
44
+ .description('Start an MCP server (constraints, premortem, exploration, tools, output, integrations)')
45
+ .action(serveCommand);
46
+ program
47
+ .command('setup')
48
+ .description('One-command onboarding: authenticate, write IDE MCP config, generate rules')
49
+ .option('--staging', 'Use staging environment')
50
+ .option('--skip-login', 'Skip authentication (use existing credentials)')
51
+ .option('--project-root <path>', 'Project root directory for IDE rules (default: cwd)')
52
+ .action((opts) => setupCommand({ staging: opts.staging, skipLogin: opts.skipLogin, projectRoot: opts.projectRoot }));
53
+ program
54
+ .command('init')
55
+ .description('Generate IDE rules only (setup runs this automatically)')
56
+ .option('--project-root <path>', 'Project root directory (default: cwd)')
57
+ .option('--staging', 'Use staging environment')
58
+ .action((opts) => initCommand({ projectRoot: opts.projectRoot, staging: opts.staging }));
35
59
  program.parse();