@kylewadegrove/cutline-mcp-cli 0.6.2 → 0.7.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.
@@ -4,6 +4,7 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
4
4
  import { resolve, join } from 'node:path';
5
5
  import { getRefreshToken } from '../auth/keychain.js';
6
6
  import { fetchFirebaseApiKey } from '../utils/config.js';
7
+ import { saveConfig, loadConfig } from '../utils/config-store.js';
7
8
  const CUTLINE_CONFIG = '.cutline/config.json';
8
9
  async function authenticate(options) {
9
10
  const refreshToken = await getRefreshToken();
@@ -33,6 +34,8 @@ async function authenticate(options) {
33
34
  tier: isPremium ? 'premium' : 'free',
34
35
  email: payload.email,
35
36
  uid: payload.user_id || payload.sub,
37
+ idToken,
38
+ baseUrl,
36
39
  };
37
40
  }
38
41
  catch {
@@ -206,6 +209,42 @@ export async function initCommand(options) {
206
209
  else {
207
210
  console.log(chalk.yellow(' No .cutline/config.json found — generating generic rules.'));
208
211
  }
212
+ // Generate API key for authenticated users (skips if one already exists)
213
+ let generatedApiKey = null;
214
+ if (auth?.idToken && auth.baseUrl) {
215
+ const existing = loadConfig();
216
+ if (existing.apiKey) {
217
+ console.log(chalk.dim(' API key: already configured'));
218
+ }
219
+ else {
220
+ const keySpinner = ora('Generating API key...').start();
221
+ try {
222
+ const keyRes = await fetch(`${auth.baseUrl}/mcpDataProxy`, {
223
+ method: 'POST',
224
+ headers: {
225
+ 'Content-Type': 'application/json',
226
+ Authorization: `Bearer ${auth.idToken}`,
227
+ },
228
+ body: JSON.stringify({ action: 'apiKey.create', params: { name: 'cursor-ide' } }),
229
+ });
230
+ if (keyRes.ok) {
231
+ const keyData = await keyRes.json();
232
+ generatedApiKey = keyData.key;
233
+ saveConfig({ apiKey: generatedApiKey });
234
+ keySpinner.succeed(chalk.green('API key generated and saved'));
235
+ }
236
+ else {
237
+ const errBody = await keyRes.text().catch(() => '');
238
+ keySpinner.warn(chalk.yellow(`API key generation skipped (${keyRes.status})`));
239
+ if (errBody)
240
+ console.log(chalk.dim(` ${errBody.slice(0, 120)}`));
241
+ }
242
+ }
243
+ catch (e) {
244
+ keySpinner.warn(chalk.yellow('API key generation skipped (network error)'));
245
+ }
246
+ }
247
+ }
209
248
  console.log();
210
249
  const filesWritten = [];
211
250
  // 1. Cursor rules
@@ -240,6 +279,16 @@ export async function initCommand(options) {
240
279
  console.log(chalk.dim('\n Upgrade to Premium for product-specific constraint graphs and .cutline.md'));
241
280
  console.log(chalk.dim(' →'), chalk.cyan('cutline-mcp upgrade'), chalk.dim('or https://thecutline.ai/upgrade'));
242
281
  }
282
+ if (generatedApiKey) {
283
+ console.log();
284
+ console.log(chalk.bold(' API Key (shown once):'));
285
+ console.log(chalk.cyan(` ${generatedApiKey}`));
286
+ console.log();
287
+ console.log(chalk.dim(' Add to your Cursor MCP config for best performance:'));
288
+ console.log(chalk.dim(' "env": { "CUTLINE_API_KEY": "') + chalk.cyan(generatedApiKey) + chalk.dim('" }'));
289
+ console.log();
290
+ console.log(chalk.dim(' Or it will be used automatically from ~/.cutline-mcp/config.json'));
291
+ }
243
292
  console.log();
244
293
  console.log(chalk.bold(' Next step:'));
245
294
  console.log(chalk.dim(' Run'), chalk.cyan('cutline-mcp setup'), chalk.dim('to get the MCP server config for your IDE.\n'));
@@ -245,7 +245,9 @@ export async function setupCommand(options) {
245
245
  }
246
246
  const items = [
247
247
  { cmd: 'Run a deep dive on my product idea', desc: 'Pre-mortem analysis — risks, assumptions, experiments' },
248
- { cmd: 'Run a code audit for my product', desc: 'Security scan + RGR remediation plan' },
248
+ { cmd: 'Plan this feature with constraints from my product', desc: 'RGR plan constraint-aware implementation roadmap' },
249
+ { cmd: 'Run an audit over my code', desc: 'Quick security scan — safe for any codebase' },
250
+ { cmd: 'Run a code audit for my product', desc: 'Full engineering audit + RGR remediation plan' },
249
251
  { cmd: 'Check constraints for src/api/upload.ts', desc: 'Get NFR boundaries for a specific file' },
250
252
  { cmd: 'Generate .cutline.md for my product', desc: 'Write the constraint routing engine' },
251
253
  { cmd: 'What does my persona think about X?', desc: 'AI persona feedback on features' },
@@ -88,7 +88,9 @@ export async function upgradeCommand(options) {
88
88
  console.log(chalk.bold(' Then ask your AI agent:\n'));
89
89
  const items = [
90
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' },
91
+ { cmd: 'Plan this feature with constraints from my product', desc: 'RGR plan constraint-aware implementation roadmap' },
92
+ { cmd: 'Run an audit over my code', desc: 'Quick security scan — safe for any codebase' },
93
+ { cmd: 'Run a code audit for my product', desc: 'Full engineering audit + RGR remediation plan' },
92
94
  { cmd: 'Generate .cutline.md for my product', desc: 'Write the constraint routing engine' },
93
95
  ];
94
96
  for (const item of items) {
@@ -287,6 +287,25 @@ async function exchangeRefreshToken(refreshToken, firebaseApiKey, maxRetries = 3
287
287
  }
288
288
  throw lastError || new Error("Token exchange failed after retries");
289
289
  }
290
+ function getStoredApiKey() {
291
+ if (process.env.CUTLINE_API_KEY) {
292
+ return {
293
+ apiKey: process.env.CUTLINE_API_KEY,
294
+ environment: process.env.CUTLINE_ENV === "staging" ? "staging" : "production"
295
+ };
296
+ }
297
+ try {
298
+ const configPath = path.join(os.homedir(), ".cutline-mcp", "config.json");
299
+ if (fs.existsSync(configPath)) {
300
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
301
+ if (config.apiKey) {
302
+ return { apiKey: config.apiKey, environment: config.environment };
303
+ }
304
+ }
305
+ } catch {
306
+ }
307
+ return null;
308
+ }
290
309
  async function getStoredToken() {
291
310
  if (process.env.CUTLINE_MCP_REFRESH_TOKEN) {
292
311
  console.error("[MCP Auth] Using token from CUTLINE_MCP_REFRESH_TOKEN env var");
@@ -433,12 +452,19 @@ async function exchangeRefreshForId(refreshToken, environment) {
433
452
  return data.id_token;
434
453
  }
435
454
  async function resolveAuth() {
455
+ const apiKeyInfo = getStoredApiKey();
456
+ if (apiKeyInfo) {
457
+ return {
458
+ baseUrl: getBaseUrl(apiKeyInfo.environment),
459
+ idToken: apiKeyInfo.apiKey
460
+ };
461
+ }
436
462
  if (cachedIdToken && Date.now() < tokenExpiresAt && cachedBaseUrl) {
437
463
  return { baseUrl: cachedBaseUrl, idToken: cachedIdToken };
438
464
  }
439
465
  const stored = await getStoredToken();
440
466
  if (!stored) {
441
- throw new Error("Not authenticated. Run 'cutline-mcp login' first.");
467
+ throw new Error("Not authenticated. Run 'cutline-mcp login' or set CUTLINE_API_KEY.");
442
468
  }
443
469
  const env = stored.environment || "production";
444
470
  const baseUrl = getBaseUrl(env);
@@ -866,12 +892,24 @@ async function cfPremortemRun(input) {
866
892
  async function cfPremortemStart(input) {
867
893
  return callCF("premortemStart", input, 3e4);
868
894
  }
895
+ async function cfPremortemKick(jobId) {
896
+ return callCF("premortemKick", { id: jobId }, 6e5);
897
+ }
898
+ async function cfPremortemChatAgent(message, context) {
899
+ return callCF("premortemChatAgent", { message, context }, 12e4);
900
+ }
869
901
  async function cfRegenAssumptions(input, doc) {
870
902
  return callCF("regenAssumptions", { input, doc }, 9e4);
871
903
  }
872
904
  async function cfRegenExperiments(input, doc) {
873
905
  return callCF("regenExperiments", { input, doc }, 9e4);
874
906
  }
907
+ async function cfExplorationAgent(message, context) {
908
+ return callCF("explorationAgent", { message, context }, 12e4);
909
+ }
910
+ async function cfConsultingDiscoveryAgent(message, context) {
911
+ return callCF("consultingDiscoveryAgent", { message, context }, 12e4);
912
+ }
875
913
  async function cfCreateLinearIssues(schema_json, limit) {
876
914
  return callCF("integrationsIssues", { schema_json, limit });
877
915
  }
@@ -1017,8 +1055,12 @@ export {
1017
1055
  cfApplyEdits,
1018
1056
  cfPremortemRun,
1019
1057
  cfPremortemStart,
1058
+ cfPremortemKick,
1059
+ cfPremortemChatAgent,
1020
1060
  cfRegenAssumptions,
1021
1061
  cfRegenExperiments,
1062
+ cfExplorationAgent,
1063
+ cfConsultingDiscoveryAgent,
1022
1064
  cfCreateLinearIssues,
1023
1065
  getPodcastIntroductions
1024
1066
  };
@@ -71,7 +71,7 @@ import {
71
71
  upsertEntities,
72
72
  upsertNodes,
73
73
  validateRequestSize
74
- } from "./chunk-7N4HJ3KR.js";
74
+ } from "./chunk-XKS3J74N.js";
75
75
  import {
76
76
  GraphTraverser,
77
77
  computeGenericGraphMetrics,
@@ -6,13 +6,17 @@ import {
6
6
  cfApplyEdits,
7
7
  cfBuildAndUploadPdf,
8
8
  cfChatWithPersona,
9
+ cfConsultingDiscoveryAgent,
9
10
  cfCreateLinearIssues,
11
+ cfExplorationAgent,
10
12
  cfGenerateAnswer,
11
13
  cfGenerateChatSuggestion,
12
14
  cfGenerateExplorationResponse,
13
15
  cfGenerateTemplateResponse,
14
16
  cfGenerateTrialRun,
15
17
  cfGetWikiMarkdown,
18
+ cfPremortemChatAgent,
19
+ cfPremortemKick,
16
20
  cfPremortemRun,
17
21
  cfPremortemStart,
18
22
  cfRegenAssumptions,
@@ -71,7 +75,7 @@ import {
71
75
  upsertEdges,
72
76
  upsertEntities,
73
77
  upsertNodes
74
- } from "./chunk-7N4HJ3KR.js";
78
+ } from "./chunk-XKS3J74N.js";
75
79
  export {
76
80
  addEdges,
77
81
  addEntity,
@@ -80,13 +84,17 @@ export {
80
84
  cfApplyEdits,
81
85
  cfBuildAndUploadPdf,
82
86
  cfChatWithPersona,
87
+ cfConsultingDiscoveryAgent,
83
88
  cfCreateLinearIssues,
89
+ cfExplorationAgent,
84
90
  cfGenerateAnswer,
85
91
  cfGenerateChatSuggestion,
86
92
  cfGenerateExplorationResponse,
87
93
  cfGenerateTemplateResponse,
88
94
  cfGenerateTrialRun,
89
95
  cfGetWikiMarkdown,
96
+ cfPremortemChatAgent,
97
+ cfPremortemKick,
90
98
  cfPremortemRun,
91
99
  cfPremortemStart,
92
100
  cfRegenAssumptions,
@@ -5,6 +5,8 @@ import {
5
5
  withPerfTracking
6
6
  } from "./chunk-OP4EO6FV.js";
7
7
  import {
8
+ cfConsultingDiscoveryAgent,
9
+ cfExplorationAgent,
8
10
  createExplorationSession,
9
11
  getExplorationSession,
10
12
  listExplorationSessions,
@@ -12,35 +14,12 @@ import {
12
14
  requirePremiumWithAutoAuth,
13
15
  updateExplorationSession,
14
16
  validateRequestSize
15
- } from "./chunk-7N4HJ3KR.js";
17
+ } from "./chunk-XKS3J74N.js";
16
18
 
17
19
  // ../mcp/dist/mcp/src/exploration-server.js
18
20
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
19
21
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
20
22
  import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
21
- import { GoogleAuth } from "google-auth-library";
22
- var EXPLORATION_AGENT_FUNCTION_URL = process.env.EXPLORATION_AGENT_FUNCTION_URL || "https://us-central1-cutline-prod.cloudfunctions.net/explorationAgent";
23
- var CONSULTING_DISCOVERY_AGENT_FUNCTION_URL = process.env.CONSULTING_DISCOVERY_AGENT_FUNCTION_URL || "https://us-central1-cutline-prod.cloudfunctions.net/consultingDiscoveryAgent";
24
- async function callExplorationAgent(message, context) {
25
- const auth = new GoogleAuth();
26
- const client = await auth.getIdTokenClient(EXPLORATION_AGENT_FUNCTION_URL);
27
- const response = await client.request({
28
- url: EXPLORATION_AGENT_FUNCTION_URL,
29
- method: "POST",
30
- data: { message, context }
31
- });
32
- return response.data;
33
- }
34
- async function callConsultingDiscoveryAgent(message, context) {
35
- const auth = new GoogleAuth();
36
- const client = await auth.getIdTokenClient(CONSULTING_DISCOVERY_AGENT_FUNCTION_URL);
37
- const response = await client.request({
38
- url: CONSULTING_DISCOVERY_AGENT_FUNCTION_URL,
39
- method: "POST",
40
- data: { message, context }
41
- });
42
- return response.data;
43
- }
44
23
  var sessionCache = /* @__PURE__ */ new Map();
45
24
  function generateId(mode = "product") {
46
25
  const prefix = mode === "consulting" ? "cdisc" : "exp";
@@ -423,7 +402,7 @@ And what draws you to explore this area?`;
423
402
  fallbackCount: session.fallbackCount || 0
424
403
  };
425
404
  console.error("[Discovery] Calling Consulting Discovery Cloud Function...");
426
- response = await callConsultingDiscoveryAgent(message, context);
405
+ response = await cfConsultingDiscoveryAgent(message, context);
427
406
  } else {
428
407
  const context = {
429
408
  currentAct: session.currentAct,
@@ -435,7 +414,7 @@ And what draws you to explore this area?`;
435
414
  fallbackCount: session.fallbackCount || 0
436
415
  };
437
416
  console.error("[Discovery] Calling Exploration Cloud Function...");
438
- response = await callExplorationAgent(message, context);
417
+ response = await cfExplorationAgent(message, context);
439
418
  }
440
419
  console.error("[Discovery] Cloud Function response received");
441
420
  if (response.wasFallback) {
@@ -13,7 +13,7 @@ import {
13
13
  requirePremiumWithAutoAuth,
14
14
  validateAuth,
15
15
  validateRequestSize
16
- } from "./chunk-7N4HJ3KR.js";
16
+ } from "./chunk-XKS3J74N.js";
17
17
 
18
18
  // ../mcp/dist/mcp/src/integrations-server.js
19
19
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -13,7 +13,7 @@ import {
13
13
  mapErrorToMcp,
14
14
  requirePremiumWithAutoAuth,
15
15
  validateRequestSize
16
- } from "./chunk-7N4HJ3KR.js";
16
+ } from "./chunk-XKS3J74N.js";
17
17
 
18
18
  // ../mcp/dist/mcp/src/output-server.js
19
19
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -11,6 +11,8 @@ import {
11
11
  withPerfTracking
12
12
  } from "./chunk-OP4EO6FV.js";
13
13
  import {
14
+ cfPremortemChatAgent,
15
+ cfPremortemKick,
14
16
  cfRegenAssumptions,
15
17
  cfRegenExperiments,
16
18
  createPremortem,
@@ -25,7 +27,7 @@ import {
25
27
  updatePremortem,
26
28
  validateAuth,
27
29
  validateRequestSize
28
- } from "./chunk-7N4HJ3KR.js";
30
+ } from "./chunk-XKS3J74N.js";
29
31
 
30
32
  // ../mcp/dist/mcp/src/premortem-server.js
31
33
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -142,19 +144,6 @@ function buildGraduationMetadata(sessionId, ctx, currentAct) {
142
144
  }
143
145
 
144
146
  // ../mcp/dist/mcp/src/premortem-server.js
145
- import { GoogleAuth } from "google-auth-library";
146
- var PREMORTEM_CHAT_FUNCTION_URL = process.env.PREMORTEM_CHAT_AGENT_FUNCTION_URL || "https://us-central1-cutline-prod.cloudfunctions.net/premortemChatAgent";
147
- var PREMORTEM_KICK_FUNCTION_URL = process.env.PREMORTEM_KICK_FUNCTION_URL || "https://us-central1-cutline-prod.cloudfunctions.net/premortemKick";
148
- async function callPremortemChatAgent(message, context) {
149
- const auth = new GoogleAuth();
150
- const client = await auth.getIdTokenClient(PREMORTEM_CHAT_FUNCTION_URL);
151
- const response = await client.request({
152
- url: PREMORTEM_CHAT_FUNCTION_URL,
153
- method: "POST",
154
- data: { message, context }
155
- });
156
- return response.data;
157
- }
158
147
  var ACT_NAMES = {
159
148
  1: "Product Understanding",
160
149
  2: "Assumption Surfacing",
@@ -517,18 +506,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
517
506
  if (!kickedFallback && data.status === "queued" && Date.now() - startTime > 1e4) {
518
507
  console.error(`[Premortem] Job still queued after 10s, kicking via Cloud Function...`);
519
508
  kickedFallback = true;
520
- try {
521
- const auth = new GoogleAuth();
522
- const client = await auth.getIdTokenClient(PREMORTEM_KICK_FUNCTION_URL);
523
- client.request({
524
- url: PREMORTEM_KICK_FUNCTION_URL,
525
- method: "POST",
526
- data: { id: jobId },
527
- timeout: 6e5
528
- }).catch((e) => console.error(`[Premortem] Kick fallback error:`, e?.message));
529
- } catch (e) {
530
- console.error(`[Premortem] Kick fallback failed:`, e?.message);
531
- }
509
+ cfPremortemKick(jobId).catch((e) => console.error(`[Premortem] Kick fallback error:`, e?.message));
532
510
  }
533
511
  const stage = data.stage_label || data.stage;
534
512
  const progress = data.progress;
@@ -603,22 +581,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
603
581
  console.error(`[Premortem Kick] Authenticated: UID=${decoded?.uid}`);
604
582
  console.error(`[Premortem Kick] Calling Cloud Function for job ${jobId}...`);
605
583
  try {
606
- const auth = new GoogleAuth();
607
- const client = await auth.getIdTokenClient(PREMORTEM_KICK_FUNCTION_URL);
608
- const response = await client.request({
609
- url: PREMORTEM_KICK_FUNCTION_URL,
610
- method: "POST",
611
- data: { id: jobId },
612
- timeout: 6e5
613
- // 10 minute timeout for full analysis
614
- });
615
- console.error(`[Premortem Kick] Cloud Function returned:`, response.data);
584
+ const result = await cfPremortemKick(jobId);
585
+ console.error(`[Premortem Kick] Cloud Function returned:`, result);
616
586
  return {
617
587
  content: [{ type: "text", text: JSON.stringify({
618
- kicked: response.data.kicked,
619
- status: response.data.status,
588
+ kicked: result.kicked,
589
+ status: result.status,
620
590
  jobId,
621
- message: response.data.status === "completed" ? "\u2615 Full analysis complete! Use premortem_status to view results." : "Job processed. Use premortem_status to check results."
591
+ message: result.status === "completed" ? "\u2615 Full analysis complete! Use premortem_status to view results." : "Job processed. Use premortem_status to check results."
622
592
  }) }]
623
593
  };
624
594
  } catch (e) {
@@ -788,7 +758,7 @@ Let's start with the basics. Tell me more about what you're building:
788
758
  try {
789
759
  const context = sessionToContext(session);
790
760
  console.error("[PremortemChat] Calling Cloud Function...");
791
- response = await callPremortemChatAgent(message, context);
761
+ response = await cfPremortemChatAgent(message, context);
792
762
  console.error("[PremortemChat] Cloud Function response received");
793
763
  } catch (e) {
794
764
  console.error("[PremortemChat] Cloud Function error:", e?.message || e);
@@ -21,7 +21,7 @@ import {
21
21
  requirePremiumWithAutoAuth,
22
22
  validateAuth,
23
23
  validateRequestSize
24
- } from "./chunk-7N4HJ3KR.js";
24
+ } from "./chunk-XKS3J74N.js";
25
25
 
26
26
  // ../mcp/dist/mcp/src/tools-server.js
27
27
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -1,6 +1,7 @@
1
1
  export interface McpConfig {
2
2
  refreshToken?: string;
3
3
  environment?: 'production' | 'staging';
4
+ apiKey?: string;
4
5
  }
5
6
  export declare function saveConfig(config: McpConfig): void;
6
7
  export declare function loadConfig(): McpConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kylewadegrove/cutline-mcp-cli",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "description": "CLI and MCP servers for Cutline — authenticate, then run constraint-aware MCP servers in Cursor or any MCP client.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",