@kylewadegrove/cutline-mcp-cli 0.7.1 → 0.7.3

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.
@@ -1,12 +1,16 @@
1
- import keytar from 'keytar';
2
- const SERVICE_NAME = 'cutline-mcp';
3
- const ACCOUNT_NAME = 'refresh-token';
1
+ import { saveConfig, loadConfig } from '../utils/config-store.js';
4
2
  export async function storeRefreshToken(token) {
5
- await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, token);
3
+ saveConfig({ refreshToken: token });
6
4
  }
7
5
  export async function getRefreshToken() {
8
- return await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
6
+ const config = loadConfig();
7
+ return config.refreshToken || null;
9
8
  }
10
9
  export async function deleteRefreshToken() {
11
- return await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
10
+ const config = loadConfig();
11
+ if (!config.refreshToken)
12
+ return false;
13
+ delete config.refreshToken;
14
+ saveConfig(config);
15
+ return true;
12
16
  }
@@ -84,10 +84,12 @@ function prompt(question) {
84
84
  });
85
85
  }
86
86
  function buildServerConfig() {
87
+ const voltaNpx = join(homedir(), '.volta', 'bin', 'npx');
88
+ const npxCommand = existsSync(voltaNpx) ? voltaNpx : 'npx';
87
89
  const config = {};
88
90
  for (const name of SERVER_NAMES) {
89
91
  config[`cutline-${name}`] = {
90
- command: 'npx',
92
+ command: npxCommand,
91
93
  args: ['-y', '@kylewadegrove/cutline-mcp-cli@latest', 'serve', name],
92
94
  };
93
95
  }
@@ -200,21 +202,17 @@ export async function setupCommand(options) {
200
202
  const home = homedir();
201
203
  const ideConfigs = [
202
204
  { name: 'Cursor', path: join(home, '.cursor', 'mcp.json') },
203
- { name: 'Claude Code', path: join(home, '.claude', 'settings.json') },
205
+ { name: 'Claude Code', path: join(home, '.claude.json') },
204
206
  ];
205
207
  let wroteAny = false;
206
208
  for (const ide of ideConfigs) {
207
- // Write to Cursor always (primary target); write to Claude if dir exists
208
- const dirExists = existsSync(join(ide.path, '..'));
209
- if (ide.name === 'Cursor' || dirExists) {
210
- try {
211
- mergeIdeConfig(ide.path, serverConfig);
212
- console.log(chalk.green(` ✓ ${ide.name}`), chalk.dim(ide.path));
213
- wroteAny = true;
214
- }
215
- catch (err) {
216
- console.log(chalk.red(` ✗ ${ide.name}`), chalk.dim(err.message));
217
- }
209
+ try {
210
+ mergeIdeConfig(ide.path, serverConfig);
211
+ console.log(chalk.green(` ✓ ${ide.name}`), chalk.dim(ide.path));
212
+ wroteAny = true;
213
+ }
214
+ catch (err) {
215
+ console.log(chalk.red(` ✗ ${ide.name}`), chalk.dim(err.message));
218
216
  }
219
217
  }
220
218
  if (wroteAny) {
@@ -230,7 +228,7 @@ export async function setupCommand(options) {
230
228
  await initCommand({ projectRoot: options.projectRoot, staging: options.staging });
231
229
  // ── 5. Claude Code one-liners ────────────────────────────────────────────
232
230
  console.log(chalk.bold(' Claude Code one-liner alternative:\n'));
233
- console.log(chalk.dim(' If you prefer `claude mcp add` instead of settings.json:\n'));
231
+ console.log(chalk.dim(' If you prefer `claude mcp add` instead of ~/.claude.json:\n'));
234
232
  const coreServers = ['constraints', 'premortem', 'tools', 'exploration'];
235
233
  for (const name of coreServers) {
236
234
  console.log(chalk.cyan(` claude mcp add cutline-${name} -- npx -y @kylewadegrove/cutline-mcp-cli serve ${name}`));
@@ -261,13 +259,15 @@ export async function setupCommand(options) {
261
259
  else {
262
260
  const items = [
263
261
  { cmd: 'Run an engineering audit on this codebase', desc: 'Security, reliability, and scalability scan (3/month free)' },
262
+ { cmd: 'Explore a product idea', desc: 'Free 6-act discovery flow to identify pain points and opportunities' },
263
+ { cmd: 'Continue my exploration session', desc: 'Resume and refine an existing free exploration conversation' },
264
264
  ];
265
265
  for (const item of items) {
266
266
  console.log(` ${chalk.cyan('→')} ${chalk.white(`"${item.cmd}"`)}`);
267
267
  console.log(` ${chalk.dim(item.desc)}`);
268
268
  }
269
269
  console.log();
270
- console.log(chalk.dim(' Upgrade to Premium for deep dives, code audits, constraint graphs, and personas'));
270
+ console.log(chalk.dim(' Want product-linked constraints, full code audit + RGR plans, and pre-mortem deep dives?'));
271
271
  console.log(chalk.dim(' →'), chalk.cyan('cutline-mcp upgrade'), chalk.dim('or https://thecutline.ai/upgrade'));
272
272
  }
273
273
  console.log();
@@ -330,18 +330,6 @@ async function getStoredToken() {
330
330
  } catch (e) {
331
331
  console.error("[MCP Auth] Failed to read config file:", e);
332
332
  }
333
- try {
334
- console.error("[MCP Auth Debug] Attempting to import keytar...");
335
- const keytar = await import("keytar");
336
- console.error("[MCP Auth Debug] Keytar imported successfully, getting password...");
337
- const token = await keytar.getPassword("cutline-mcp", "refresh-token");
338
- console.error("[MCP Auth Debug] Token retrieved:", token ? "YES (length: " + token.length + ")" : "NO TOKEN FOUND");
339
- if (token) {
340
- return { refreshToken: token };
341
- }
342
- } catch (error) {
343
- console.error("[MCP Auth Error] Failed to access keychain:", error);
344
- }
345
333
  return null;
346
334
  }
347
335
  async function requirePremiumWithAutoAuth(authToken) {
@@ -904,6 +892,10 @@ async function cfRegenAssumptions(input, doc) {
904
892
  async function cfRegenExperiments(input, doc) {
905
893
  return callCF("regenExperiments", { input, doc }, 9e4);
906
894
  }
895
+ async function cfGenerateStructuredContent(options) {
896
+ const res = await proxy("llm.generate", options);
897
+ return res.text;
898
+ }
907
899
  async function cfGenerateEmbeddings(texts, taskType = "RETRIEVAL_DOCUMENT") {
908
900
  return proxy("embeddings.generate", { texts, taskType });
909
901
  }
@@ -1062,6 +1054,7 @@ export {
1062
1054
  cfPremortemChatAgent,
1063
1055
  cfRegenAssumptions,
1064
1056
  cfRegenExperiments,
1057
+ cfGenerateStructuredContent,
1065
1058
  cfGenerateEmbeddings,
1066
1059
  cfExplorationAgent,
1067
1060
  cfConsultingDiscoveryAgent,
@@ -25,6 +25,7 @@ import {
25
25
  cfGenerateChatSuggestion,
26
26
  cfGenerateEmbeddings,
27
27
  cfGenerateExplorationResponse,
28
+ cfGenerateStructuredContent,
28
29
  cfGenerateTemplateResponse,
29
30
  cfGenerateTrialRun,
30
31
  cfGetWikiMarkdown,
@@ -72,7 +73,7 @@ import {
72
73
  upsertEntities,
73
74
  upsertNodes,
74
75
  validateRequestSize
75
- } from "./chunk-TJTE5Q2C.js";
76
+ } from "./chunk-ZVWDXO6M.js";
76
77
  import {
77
78
  GraphTraverser,
78
79
  computeGenericGraphMetrics,
@@ -2752,512 +2753,6 @@ async function propagateConstraints(productId, sourceEntityId, targetEntityId, s
2752
2753
  return propagated;
2753
2754
  }
2754
2755
 
2755
- // ../mcp/dist/src/orchestrator/agents/shared/vertex.js
2756
- import { VertexAI } from "@google-cloud/vertexai";
2757
- import { GoogleAuth } from "google-auth-library";
2758
- import { Buffer } from "node:buffer";
2759
-
2760
- // ../mcp/dist/src/shared/circuit-breaker.js
2761
- var DEFAULT_OPTIONS = {
2762
- failureThreshold: 5,
2763
- resetTimeoutMs: 6e4,
2764
- halfOpenMaxAttempts: 1,
2765
- name: "default"
2766
- };
2767
- var CircuitBreakerOpenError = class extends Error {
2768
- circuitName;
2769
- retriesAt;
2770
- constructor(circuitName, retriesAt) {
2771
- super(`Circuit "${circuitName}" is open \u2014 fast-failing. Retries at ${new Date(retriesAt).toISOString()}`);
2772
- this.circuitName = circuitName;
2773
- this.retriesAt = retriesAt;
2774
- this.name = "CircuitBreakerOpenError";
2775
- }
2776
- };
2777
- var CircuitBreaker = class {
2778
- state = "closed";
2779
- failureCount = 0;
2780
- lastFailureTime = 0;
2781
- halfOpenAttempts = 0;
2782
- opts;
2783
- constructor(options) {
2784
- this.opts = { ...DEFAULT_OPTIONS, ...options };
2785
- }
2786
- getState() {
2787
- this.maybeTransition();
2788
- return this.state;
2789
- }
2790
- getStats() {
2791
- return { state: this.getState(), failureCount: this.failureCount, lastFailureTime: this.lastFailureTime };
2792
- }
2793
- /**
2794
- * Execute a function through the circuit breaker.
2795
- * Throws CircuitBreakerOpenError if the circuit is open.
2796
- */
2797
- async execute(fn) {
2798
- this.maybeTransition();
2799
- if (this.state === "open") {
2800
- throw new CircuitBreakerOpenError(this.opts.name, this.lastFailureTime + this.opts.resetTimeoutMs);
2801
- }
2802
- if (this.state === "half_open" && this.halfOpenAttempts >= this.opts.halfOpenMaxAttempts) {
2803
- throw new CircuitBreakerOpenError(this.opts.name, this.lastFailureTime + this.opts.resetTimeoutMs);
2804
- }
2805
- if (this.state === "half_open") {
2806
- this.halfOpenAttempts++;
2807
- }
2808
- try {
2809
- const result = await fn();
2810
- this.onSuccess();
2811
- return result;
2812
- } catch (err) {
2813
- this.onFailure();
2814
- throw err;
2815
- }
2816
- }
2817
- /** Force the circuit into a specific state (useful for testing) */
2818
- reset() {
2819
- this.state = "closed";
2820
- this.failureCount = 0;
2821
- this.halfOpenAttempts = 0;
2822
- this.lastFailureTime = 0;
2823
- }
2824
- onSuccess() {
2825
- this.failureCount = 0;
2826
- this.halfOpenAttempts = 0;
2827
- if (this.state === "half_open") {
2828
- this.state = "closed";
2829
- console.log(JSON.stringify({
2830
- severity: "INFO",
2831
- circuit_breaker: this.opts.name,
2832
- event: "circuit_closed",
2833
- message: `Circuit "${this.opts.name}" recovered \u2014 closing`
2834
- }));
2835
- }
2836
- }
2837
- onFailure() {
2838
- this.failureCount++;
2839
- this.lastFailureTime = Date.now();
2840
- if (this.state === "half_open") {
2841
- this.state = "open";
2842
- console.log(JSON.stringify({
2843
- severity: "WARNING",
2844
- circuit_breaker: this.opts.name,
2845
- event: "circuit_reopened",
2846
- message: `Circuit "${this.opts.name}" probe failed \u2014 re-opening`
2847
- }));
2848
- return;
2849
- }
2850
- if (this.failureCount >= this.opts.failureThreshold) {
2851
- this.state = "open";
2852
- console.log(JSON.stringify({
2853
- severity: "WARNING",
2854
- circuit_breaker: this.opts.name,
2855
- event: "circuit_opened",
2856
- failure_count: this.failureCount,
2857
- message: `Circuit "${this.opts.name}" opened after ${this.failureCount} consecutive failures`
2858
- }));
2859
- }
2860
- }
2861
- maybeTransition() {
2862
- if (this.state === "open") {
2863
- const elapsed = Date.now() - this.lastFailureTime;
2864
- if (elapsed >= this.opts.resetTimeoutMs) {
2865
- this.state = "half_open";
2866
- this.halfOpenAttempts = 0;
2867
- console.log(JSON.stringify({
2868
- severity: "INFO",
2869
- circuit_breaker: this.opts.name,
2870
- event: "circuit_half_open",
2871
- message: `Circuit "${this.opts.name}" entering half-open after ${Math.round(elapsed / 1e3)}s`
2872
- }));
2873
- }
2874
- }
2875
- }
2876
- };
2877
-
2878
- // ../mcp/dist/src/shared/metrics.js
2879
- function emitMetric(name2, value, labels) {
2880
- const record = {
2881
- metric: true,
2882
- severity: "INFO",
2883
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2884
- name: name2,
2885
- value,
2886
- ...labels && { labels: stripUndefined(labels) }
2887
- };
2888
- console.log(JSON.stringify(record));
2889
- }
2890
- function stripUndefined(obj) {
2891
- const out = {};
2892
- for (const [k, v] of Object.entries(obj)) {
2893
- if (v !== void 0)
2894
- out[k] = v;
2895
- }
2896
- return out;
2897
- }
2898
-
2899
- // ../mcp/dist/src/orchestrator/agents/shared/vertex.js
2900
- var MODEL_PRICING = {
2901
- "gemini-2.5-pro": { inputPerM: 1.25, outputPerM: 10 },
2902
- "gemini-2.5-flash": { inputPerM: 0.15, outputPerM: 0.6 },
2903
- "gemini-2.0-flash": { inputPerM: 0.1, outputPerM: 0.4 },
2904
- "gemini-1.5-pro": { inputPerM: 1.25, outputPerM: 5 },
2905
- "gemini-1.5-flash": { inputPerM: 0.075, outputPerM: 0.3 }
2906
- };
2907
- function estimateCostUsd(model, tokensIn, tokensOut) {
2908
- const key = Object.keys(MODEL_PRICING).find((k) => model.startsWith(k)) ?? "";
2909
- const price = MODEL_PRICING[key];
2910
- if (!price)
2911
- return 0;
2912
- return Math.round((tokensIn * price.inputPerM + tokensOut * price.outputPerM) / 1e6 * 1e6) / 1e6;
2913
- }
2914
- var DEFAULT_MODEL_ID = process.env.MODEL_ID || "gemini-2.5-pro";
2915
- var DEFAULT_LOCATION = process.env.VERTEX_LOCATION || "us-central1";
2916
- var VERBOSE = (process.env.AGENT_LOG_VERBOSE ?? process.env.PREMORTEM_LOG_VERBOSE) === "true";
2917
- var PREVIEW = process.env.AGENT_LOG_PREVIEW === "true";
2918
- var PREVIEW_LIMIT = Math.max(0, parseInt(process.env.AGENT_LOG_PREVIEW_LIMIT || "400", 10) || 400);
2919
- function vlog(...args) {
2920
- if (VERBOSE) {
2921
- try {
2922
- console.log(...args);
2923
- } catch {
2924
- }
2925
- }
2926
- }
2927
- function safeStringify(x) {
2928
- try {
2929
- return JSON.stringify(x);
2930
- } catch {
2931
- return String(x);
2932
- }
2933
- }
2934
- function previewOf(x) {
2935
- const s = typeof x === "string" ? x : safeStringify(x);
2936
- return s.length > PREVIEW_LIMIT ? s.slice(0, PREVIEW_LIMIT) + "\u2026" : s;
2937
- }
2938
- var vertexCircuit = new CircuitBreaker({
2939
- name: "vertex-ai",
2940
- failureThreshold: 5,
2941
- resetTimeoutMs: 6e4,
2942
- halfOpenMaxAttempts: 1
2943
- });
2944
- var LAST_DIAG = null;
2945
- function asVertexResponseSchema(schema) {
2946
- const convert = (s) => {
2947
- const out = {};
2948
- if (s.type)
2949
- out.type = String(s.type).toLowerCase();
2950
- if (s.description != null)
2951
- out.description = s.description;
2952
- if (Array.isArray(s.enum))
2953
- out.enum = s.enum;
2954
- if (s.items)
2955
- out.items = convert(s.items);
2956
- if (s.properties) {
2957
- out.properties = {};
2958
- for (const [k, v] of Object.entries(s.properties)) {
2959
- out.properties[k] = convert(v);
2960
- }
2961
- }
2962
- if (Array.isArray(s.required))
2963
- out.required = s.required;
2964
- if (s.minItems != null)
2965
- out.minItems = s.minItems;
2966
- if (s.maxItems != null)
2967
- out.maxItems = s.maxItems;
2968
- if (s.minimum != null)
2969
- out.minimum = s.minimum;
2970
- if (s.maximum != null)
2971
- out.maximum = s.maximum;
2972
- if (s.minLength != null)
2973
- out.minLength = s.minLength;
2974
- if (s.maxLength != null)
2975
- out.maxLength = s.maxLength;
2976
- if (s.pattern != null)
2977
- out.pattern = s.pattern;
2978
- if (s.nullable != null)
2979
- out.nullable = s.nullable;
2980
- return out;
2981
- };
2982
- return convert(schema);
2983
- }
2984
- async function retryWithBackoff(fn, opts) {
2985
- let lastErr;
2986
- for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
2987
- try {
2988
- return await fn();
2989
- } catch (err) {
2990
- lastErr = err;
2991
- if (attempt >= opts.maxRetries || !opts.shouldRetry(err))
2992
- throw err;
2993
- const delayMs = opts.baseDelayMs * Math.pow(2, attempt) + Math.random() * 500;
2994
- vlog("vertex_retry", { attempt: attempt + 1, delayMs: Math.round(delayMs), error: String(err?.message || err).slice(0, 200) });
2995
- await new Promise((resolve) => setTimeout(resolve, delayMs));
2996
- }
2997
- }
2998
- throw lastErr;
2999
- }
3000
- async function generateStructuredContent(options) {
3001
- const project = await resolveProjectId();
3002
- const modelId = options.modelId || DEFAULT_MODEL_ID;
3003
- const location = options.location || DEFAULT_LOCATION;
3004
- const vertex = new VertexAI({ project, location });
3005
- const fullSystem = `${GLOBAL_SYSTEM_PROMPT}
3006
-
3007
- ${options.system || ""}`.trim();
3008
- const model = vertex.getGenerativeModel({
3009
- model: modelId,
3010
- systemInstruction: { role: "system", parts: [{ text: fullSystem }] }
3011
- });
3012
- const t0 = Date.now();
3013
- if (PREVIEW) {
3014
- try {
3015
- vlog("vertex_prompt", { model: modelId, location, system_preview: previewOf(fullSystem), user_preview: previewOf(options.user) });
3016
- } catch {
3017
- }
3018
- }
3019
- const tools = options.useGoogleSearch ? [{
3020
- googleSearch: {}
3021
- }] : void 0;
3022
- const useStructuredOutput = options.responseSchema && !options.useGoogleSearch;
3023
- const requestPayload = {
3024
- contents: [{ role: "user", parts: [{ text: options.user }] }],
3025
- generationConfig: {
3026
- temperature: options.temperature ?? 0.2,
3027
- maxOutputTokens: options.maxOutputTokens ?? 400,
3028
- responseMimeType: useStructuredOutput ? "application/json" : void 0,
3029
- responseSchema: useStructuredOutput ? asVertexResponseSchema(options.responseSchema) : void 0,
3030
- topP: options.topP ?? 0.9,
3031
- topK: options.topK ?? 40
3032
- },
3033
- tools
3034
- };
3035
- const response = await vertexCircuit.execute(() => retryWithBackoff(() => model.generateContent(requestPayload), {
3036
- maxRetries: 3,
3037
- baseDelayMs: 2e3,
3038
- shouldRetry: (err) => {
3039
- const msg = String(err?.message || err || "");
3040
- const name2 = String(err?.name || "");
3041
- if (msg.includes("429") || msg.includes("RESOURCE_EXHAUSTED"))
3042
- return true;
3043
- if (msg.includes("503") || msg.includes("500") || msg.includes("UNAVAILABLE"))
3044
- return true;
3045
- if (msg.includes("exception posting request") || msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("socket hang up") || msg.includes("network") || msg.includes("fetch failed"))
3046
- return true;
3047
- if (name2 === "GoogleGenerativeAIError" || name2 === "GoogleGenerativeAIFetchError")
3048
- return true;
3049
- return false;
3050
- }
3051
- }));
3052
- const candidate = response.response.candidates?.[0];
3053
- const parts = candidate?.content?.parts ?? [];
3054
- const finishReason = candidate?.finishReason ?? candidate?.finish_reason;
3055
- const usage = response?.response?.usageMetadata ?? response?.usageMetadata;
3056
- const safety = candidate?.safetyRatings ?? candidate?.safety_ratings;
3057
- const partTypes = parts.map((p) => p?.functionCall ? "functionCall" : p?.structValue ? "structValue" : p?.inlineData ? "inlineData" : typeof p?.text === "string" ? "text" : "other");
3058
- const groundingMeta = candidate?.groundingMetadata;
3059
- const groundingMetadata = groundingMeta ? {
3060
- searchQueries: groundingMeta.searchEntryPoint?.renderedContent ? [groundingMeta.searchEntryPoint.renderedContent] : void 0,
3061
- webSearchQueries: groundingMeta.webSearchQueries,
3062
- groundingChunks: groundingMeta.groundingChunks?.length
3063
- } : void 0;
3064
- if (groundingMetadata?.groundingChunks) {
3065
- vlog("vertex_grounding", {
3066
- searchQueries: groundingMetadata.webSearchQueries,
3067
- chunks: groundingMetadata.groundingChunks
3068
- });
3069
- }
3070
- const pickNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
3071
- const tokens_in = pickNum(usage?.promptTokenCount ?? usage?.inputTokenCount ?? usage?.promptTokens ?? usage?.input_tokens);
3072
- const tokens_out = pickNum(usage?.candidatesTokenCount ?? usage?.outputTokenCount ?? usage?.completionTokens ?? usage?.output_tokens);
3073
- const total_tokens = pickNum(usage?.totalTokenCount ?? usage?.totalTokens) ?? (tokens_in ?? 0) + (tokens_out ?? 0);
3074
- const cost_usd = estimateCostUsd(modelId, tokens_in ?? 0, tokens_out ?? 0);
3075
- emitMetric("vertex.call.tokens", total_tokens ?? 0, {
3076
- model: modelId,
3077
- tokens_in: tokens_in ?? 0,
3078
- tokens_out: tokens_out ?? 0,
3079
- cost_usd,
3080
- latency_ms: Date.now() - t0
3081
- });
3082
- const logVertex = (selected, lengths) => {
3083
- if (!VERBOSE)
3084
- return;
3085
- vlog("vertex_done", { model: modelId, location, tokens_in, tokens_out, total_tokens, finishReason, selected, parts_count: partTypes.length, latency_ms: Date.now() - t0, lengths });
3086
- };
3087
- if (!parts.length) {
3088
- try {
3089
- const out = JSON.stringify(response.response);
3090
- LAST_DIAG = { finishReason, usage, safety, parts: { count: 0, types: [] }, selected: "text", lengths: { funcArgs: 0, inline: 0, text: out.length }, groundingMetadata };
3091
- logVertex("text", { funcArgs: 0, inline: 0, text: out.length });
3092
- return out;
3093
- } catch {
3094
- }
3095
- LAST_DIAG = { finishReason, usage, safety, parts: { count: 0, types: [] }, selected: "text", lengths: { funcArgs: 0, inline: 0, text: 0 }, groundingMetadata };
3096
- logVertex("text", { funcArgs: 0, inline: 0, text: 0 });
3097
- return "";
3098
- }
3099
- const textSegments = [];
3100
- let funcArgs = "";
3101
- let sawFuncArgs = false;
3102
- let structCandidate = void 0;
3103
- const inlineSegments = [];
3104
- const preferText = !!options.preferText && !options.responseSchema;
3105
- for (const p of parts) {
3106
- if (p?.functionCall?.arguments !== void 0) {
3107
- const args = p.functionCall.arguments;
3108
- sawFuncArgs = true;
3109
- if (typeof args === "string") {
3110
- funcArgs += args;
3111
- } else {
3112
- try {
3113
- structCandidate = args;
3114
- } catch {
3115
- }
3116
- }
3117
- continue;
3118
- }
3119
- if (p?.structValue) {
3120
- structCandidate = p.structValue;
3121
- continue;
3122
- }
3123
- if (p?.inlineData?.data) {
3124
- try {
3125
- const decoded = Buffer.from(p.inlineData.data, "base64").toString("utf8");
3126
- inlineSegments.push(decoded);
3127
- } catch {
3128
- }
3129
- continue;
3130
- }
3131
- if (typeof p?.text === "string") {
3132
- textSegments.push(p.text);
3133
- }
3134
- }
3135
- if (!preferText && sawFuncArgs && funcArgs.trim().length) {
3136
- let out = funcArgs.trim();
3137
- if (out.startsWith("```")) {
3138
- out = out.replace(/^```[a-zA-Z]*\n?/, "");
3139
- if (out.endsWith("```"))
3140
- out = out.replace(/```\s*$/, "");
3141
- }
3142
- LAST_DIAG = { finishReason, usage, safety, parts: { count: partTypes.length, types: partTypes }, selected: "args", lengths: { funcArgs: funcArgs.length, inline: inlineSegments.join("").length, text: textSegments.join("").length }, groundingMetadata };
3143
- logVertex("args", { funcArgs: funcArgs.length, inline: inlineSegments.join("").length, text: textSegments.join("").length });
3144
- return out;
3145
- }
3146
- if (!preferText && structCandidate !== void 0) {
3147
- try {
3148
- const out = JSON.stringify(structCandidate);
3149
- LAST_DIAG = { finishReason, usage, safety, parts: { count: partTypes.length, types: partTypes }, selected: "struct", lengths: { funcArgs: funcArgs.length, inline: inlineSegments.join("").length, text: textSegments.join("").length }, groundingMetadata };
3150
- logVertex("struct", { funcArgs: funcArgs.length, inline: inlineSegments.join("").length, text: textSegments.join("").length });
3151
- return out;
3152
- } catch {
3153
- }
3154
- }
3155
- if (inlineSegments.length) {
3156
- let inlineText = inlineSegments.join("").trim();
3157
- if (inlineText.startsWith("```")) {
3158
- inlineText = inlineText.replace(/^```[a-zA-Z]*\n?/, "");
3159
- if (inlineText.endsWith("```"))
3160
- inlineText = inlineText.replace(/```\s*$/, "");
3161
- }
3162
- if (options.responseSchema) {
3163
- try {
3164
- JSON.parse(inlineText);
3165
- LAST_DIAG = { finishReason, usage, safety, parts: { count: partTypes.length, types: partTypes }, selected: "inline", lengths: { funcArgs: funcArgs.length, inline: inlineText.length, text: textSegments.join("").length }, groundingMetadata };
3166
- logVertex("inline", { funcArgs: funcArgs.length, inline: inlineText.length, text: textSegments.join("").length });
3167
- return inlineText;
3168
- } catch {
3169
- }
3170
- const start = inlineText.indexOf("{");
3171
- const end = inlineText.lastIndexOf("}");
3172
- if (start !== -1 && end > start) {
3173
- const inner = inlineText.slice(start, end + 1).trim();
3174
- try {
3175
- JSON.parse(inner);
3176
- LAST_DIAG = { finishReason, usage, safety, parts: { count: partTypes.length, types: partTypes }, selected: "inline", lengths: { funcArgs: funcArgs.length, inline: inner.length, text: textSegments.join("").length }, groundingMetadata };
3177
- logVertex("inline", { funcArgs: funcArgs.length, inline: inner.length, text: textSegments.join("").length });
3178
- return inner;
3179
- } catch {
3180
- }
3181
- }
3182
- }
3183
- if (inlineText) {
3184
- textSegments.push(inlineText);
3185
- }
3186
- }
3187
- let text = textSegments.join("").trim();
3188
- if (!text) {
3189
- LAST_DIAG = { finishReason, usage, safety, parts: { count: partTypes.length, types: partTypes }, selected: "text", lengths: { funcArgs: funcArgs.length, inline: inlineSegments.join("").length, text: 0 }, groundingMetadata };
3190
- logVertex("text", { funcArgs: funcArgs.length, inline: inlineSegments.join("").length, text: 0 });
3191
- return "";
3192
- }
3193
- if (text.startsWith("```")) {
3194
- text = text.replace(/^```[a-zA-Z]*\n?/, "");
3195
- if (text.endsWith("```"))
3196
- text = text.replace(/```\s*$/, "");
3197
- }
3198
- if (options.responseSchema) {
3199
- try {
3200
- JSON.parse(text);
3201
- LAST_DIAG = { finishReason, usage, safety, parts: { count: partTypes.length, types: partTypes }, selected: "text", lengths: { funcArgs: funcArgs.length, inline: inlineSegments.join("").length, text: text.length }, groundingMetadata };
3202
- logVertex("text", { funcArgs: funcArgs.length, inline: inlineSegments.join("").length, text: text.length });
3203
- return text;
3204
- } catch {
3205
- }
3206
- const start = text.indexOf("{");
3207
- const end = text.lastIndexOf("}");
3208
- if (start !== -1 && end > start) {
3209
- const inner = text.slice(start, end + 1).trim();
3210
- try {
3211
- JSON.parse(inner);
3212
- LAST_DIAG = { finishReason, usage, safety, parts: { count: partTypes.length, types: partTypes }, selected: "text", lengths: { funcArgs: funcArgs.length, inline: inlineSegments.join("").length, text: inner.length }, groundingMetadata };
3213
- logVertex("text", { funcArgs: funcArgs.length, inline: inlineSegments.join("").length, text: inner.length });
3214
- return inner;
3215
- } catch {
3216
- }
3217
- }
3218
- }
3219
- LAST_DIAG = { finishReason, usage, safety, parts: { count: partTypes.length, types: partTypes }, selected: "text", lengths: { funcArgs: funcArgs.length, inline: inlineSegments.join("").length, text: text.length }, groundingMetadata };
3220
- logVertex("text", { funcArgs: funcArgs.length, inline: inlineSegments.join("").length, text: text.length });
3221
- return text;
3222
- }
3223
- var GLOBAL_SYSTEM_PROMPT = [
3224
- "You are a research-first, skepticism-driven product analyst. Your job is to reduce optimism bias and avoid guesswork.",
3225
- "",
3226
- "Behavioral guardrails:",
3227
- "- Evidence > opinions. Prefer verifiable facts, base rates, and conservative assumptions.",
3228
- "- Do not fabricate specifics (data points, sources, user counts, dates). If unknown, say so.",
3229
- "- Calibrate: reflect uncertainty explicitly and note assumptions driving your outputs.",
3230
- "- Prefer breadth from reference classes over narrow anecdotes. De-duplicate aggressively.",
3231
- "",
3232
- "When context is insufficient:",
3233
- "- Identify Information Gaps (what is missing to answer confidently).",
3234
- "- Propose How to Research with 3\u20136 concrete queries and credible source types to check.",
3235
- "- Use analogous reference classes to ground estimates, but flag them as assumptions.",
3236
- "",
3237
- "Estimation policy:",
3238
- "- For any numeric estimate required by the schema, return a realistic P50 value.",
3239
- "- In rationale, include a short range and the main driver(s). Default conservative when uncertainty is high.",
3240
- "",
3241
- "MoSCoW policy:",
3242
- "- MUST only if critical to core value or risk mitigation; SHOULD for strong value; COULD if deferrable; WONT if low value now or blocked (say why).",
3243
- "",
3244
- "Output hygiene:",
3245
- "- Follow the requested output schema exactly. If a field is unknown, omit it or use null if allowed.",
3246
- "- Keep rationales crisp (8\u201330 words), focusing on evidence, base rates, and key assumptions.",
3247
- "- No marketing language, no boilerplate, no code fences, no markdown\u2014only the required JSON/plain text."
3248
- ].join("\n");
3249
- async function resolveProjectId() {
3250
- if (process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT) {
3251
- return process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
3252
- }
3253
- try {
3254
- const auth = new GoogleAuth();
3255
- return await auth.getProjectId();
3256
- } catch (err) {
3257
- throw new Error(`Unable to resolve Google Cloud project id: ${err?.message ?? err}`);
3258
- }
3259
- }
3260
-
3261
2756
  // ../mcp/dist/mcp/src/constraints/constraint-matcher.js
3262
2757
  var MATCH_RULES = [
3263
2758
  {
@@ -7697,7 +7192,7 @@ function mcpAudit(entry) {
7697
7192
  }));
7698
7193
  }
7699
7194
  var DEFAULT_MODEL = process.env.MODEL_ID || "gemini-2.5-pro";
7700
- var generateStructuredContent2 = (options) => withLlmMonitor(options.modelId || DEFAULT_MODEL, () => generateStructuredContent(options));
7195
+ var generateStructuredContent = (options) => withLlmMonitor(options.modelId || DEFAULT_MODEL, () => cfGenerateStructuredContent(options));
7701
7196
  var explorationSessions = /* @__PURE__ */ new Map();
7702
7197
  function generateId(prefix = "id") {
7703
7198
  return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
@@ -8747,7 +8242,7 @@ Why AI: ${idea.whyAI}`
8747
8242
  getAllEdges: (pid) => getAllEdges(pid),
8748
8243
  getAllNodes: (pid) => getAllNodes(pid),
8749
8244
  getAllBindings: (pid) => getAllBindings(pid),
8750
- generateStructuredContent: generateStructuredContent2,
8245
+ generateStructuredContent,
8751
8246
  upsertNodes: (pid, sourceType, sourceId, nodes) => upsertNodes(pid, sourceType, sourceId, nodes),
8752
8247
  addEntity: (pid, entity) => addEntity(pid, entity),
8753
8248
  addEdges: (pid, edges) => addEdges(pid, edges),
@@ -9165,7 +8660,7 @@ Meta: ${JSON.stringify(output.meta)}` }
9165
8660
  throw new McpError(ErrorCode.InvalidParams, "Document text too short (min 50 chars)");
9166
8661
  }
9167
8662
  const finalDocId = doc_id || `doc_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
9168
- const nodes = await extractConstraintsFromDoc(finalDocId, text, generateStructuredContent2, { documentTitle: title, documentUrl: url });
8663
+ const nodes = await extractConstraintsFromDoc(finalDocId, text, generateStructuredContent, { documentTitle: title, documentUrl: url });
9169
8664
  if (nodes.length === 0) {
9170
8665
  return {
9171
8666
  content: [{
@@ -9902,7 +9397,7 @@ Meta: ${JSON.stringify(output.meta)}` }
9902
9397
  }
9903
9398
  const ingestionId = source_id || `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
9904
9399
  const existingEntities = await getAllEntities(product_id);
9905
- const graph = await ingestRequirements(ingestionId, text, generateStructuredContent2, existingEntities, { documentTitle: title });
9400
+ const graph = await ingestRequirements(ingestionId, text, generateStructuredContent, existingEntities, { documentTitle: title });
9906
9401
  if (graph.entities.length > 0) {
9907
9402
  await upsertEntities(product_id, "doc", ingestionId, graph.entities);
9908
9403
  }
@@ -10058,7 +9553,7 @@ Meta: ${JSON.stringify(meta)}`
10058
9553
  }]
10059
9554
  };
10060
9555
  }
10061
- const result = await inferBindings(entities, file_paths, use_llm ? generateStructuredContent2 : void 0);
9556
+ const result = await inferBindings(entities, file_paths, use_llm ? generateStructuredContent : void 0);
10062
9557
  if (result.bindings.length > 0) {
10063
9558
  await upsertBindings(product_id, result.bindings);
10064
9559
  }
@@ -10732,7 +10227,7 @@ Meta: ${JSON.stringify({
10732
10227
  getAllEdges: (pid) => getAllEdges(pid),
10733
10228
  getAllNodes: (pid) => getAllNodes(pid),
10734
10229
  getAllBindings: (pid) => getAllBindings(pid),
10735
- generateStructuredContent: generateStructuredContent2,
10230
+ generateStructuredContent,
10736
10231
  upsertNodes: (pid, sourceType, sourceId, nodes) => upsertNodes(pid, sourceType, sourceId, nodes),
10737
10232
  addEntity: (pid, entity) => addEntity(pid, entity),
10738
10233
  addEdges: (pid, edges) => addEdges(pid, edges),
@@ -13,6 +13,7 @@ import {
13
13
  cfGenerateChatSuggestion,
14
14
  cfGenerateEmbeddings,
15
15
  cfGenerateExplorationResponse,
16
+ cfGenerateStructuredContent,
16
17
  cfGenerateTemplateResponse,
17
18
  cfGenerateTrialRun,
18
19
  cfGetWikiMarkdown,
@@ -76,7 +77,7 @@ import {
76
77
  upsertEdges,
77
78
  upsertEntities,
78
79
  upsertNodes
79
- } from "./chunk-TJTE5Q2C.js";
80
+ } from "./chunk-ZVWDXO6M.js";
80
81
  export {
81
82
  addEdges,
82
83
  addEntity,
@@ -92,6 +93,7 @@ export {
92
93
  cfGenerateChatSuggestion,
93
94
  cfGenerateEmbeddings,
94
95
  cfGenerateExplorationResponse,
96
+ cfGenerateStructuredContent,
95
97
  cfGenerateTemplateResponse,
96
98
  cfGenerateTrialRun,
97
99
  cfGetWikiMarkdown,
@@ -14,7 +14,7 @@ import {
14
14
  requirePremiumWithAutoAuth,
15
15
  updateExplorationSession,
16
16
  validateRequestSize
17
- } from "./chunk-TJTE5Q2C.js";
17
+ } from "./chunk-ZVWDXO6M.js";
18
18
 
19
19
  // ../mcp/dist/mcp/src/exploration-server.js
20
20
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -13,7 +13,7 @@ import {
13
13
  requirePremiumWithAutoAuth,
14
14
  validateAuth,
15
15
  validateRequestSize
16
- } from "./chunk-TJTE5Q2C.js";
16
+ } from "./chunk-ZVWDXO6M.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-TJTE5Q2C.js";
16
+ } from "./chunk-ZVWDXO6M.js";
17
17
 
18
18
  // ../mcp/dist/mcp/src/output-server.js
19
19
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -27,7 +27,7 @@ import {
27
27
  updatePremortem,
28
28
  validateAuth,
29
29
  validateRequestSize
30
- } from "./chunk-TJTE5Q2C.js";
30
+ } from "./chunk-ZVWDXO6M.js";
31
31
 
32
32
  // ../mcp/dist/mcp/src/premortem-server.js
33
33
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -21,7 +21,7 @@ import {
21
21
  requirePremiumWithAutoAuth,
22
22
  validateAuth,
23
23
  validateRequestSize
24
- } from "./chunk-TJTE5Q2C.js";
24
+ } from "./chunk-ZVWDXO6M.js";
25
25
 
26
26
  // ../mcp/dist/mcp/src/tools-server.js
27
27
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kylewadegrove/cutline-mcp-cli",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
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",
@@ -53,13 +53,10 @@
53
53
  "author": "Cutline",
54
54
  "license": "MIT",
55
55
  "dependencies": {
56
- "@google-cloud/vertexai": "^1.9.2",
57
56
  "@modelcontextprotocol/sdk": "^1.0.0",
58
57
  "chalk": "^4.1.2",
59
58
  "commander": "^11.1.0",
60
59
  "dotenv": "^16.4.5",
61
- "google-auth-library": "^9.14.2",
62
- "keytar": "^7.9.0",
63
60
  "open": "^9.1.0",
64
61
  "ora": "^5.4.1",
65
62
  "zod": "^3.23.8"